From 95654e0b584d5aa63fa67693b6774b6b4adeec43 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 17 Apr 2023 19:49:32 -0400 Subject: [PATCH] Deprecate construction of Distribution and subclasses without implementing abstract methods. Fixes #422. --- importlib_metadata/__init__.py | 21 ++++++++++++++++++++- tests/_context.py | 13 +++++++++++++ tests/test_main.py | 13 +++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/_context.py diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 96571f4a..e9ae0d19 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -348,7 +348,26 @@ def __repr__(self): return f'' -class Distribution(metaclass=abc.ABCMeta): +class DeprecatedNonAbstract: + def __new__(cls, *args, **kwargs): + all_names = { + name for subclass in inspect.getmro(cls) for name in vars(subclass) + } + abstract = { + name + for name in all_names + if getattr(getattr(cls, name), '__isabstractmethod__', False) + } + if abstract: + warnings.warn( + f"Unimplemented abstract methods {abstract}", + DeprecationWarning, + stacklevel=2, + ) + return super().__new__(cls) + + +class Distribution(DeprecatedNonAbstract): """A Python distribution package.""" @abc.abstractmethod diff --git a/tests/_context.py b/tests/_context.py new file mode 100644 index 00000000..8a53eb55 --- /dev/null +++ b/tests/_context.py @@ -0,0 +1,13 @@ +import contextlib + + +# from jaraco.context 4.3 +class suppress(contextlib.suppress, contextlib.ContextDecorator): + """ + A version of contextlib.suppress with decorator support. + + >>> @suppress(KeyError) + ... def key_error(): + ... {}[''] + >>> key_error() + """ diff --git a/tests/test_main.py b/tests/test_main.py index 7d6c79a4..a7650172 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,12 +1,15 @@ import re import pickle import unittest +import warnings import importlib import importlib_metadata +import contextlib import itertools import pyfakefs.fake_filesystem_unittest as ffs from . import fixtures +from ._context import suppress from importlib_metadata import ( Distribution, EntryPoint, @@ -20,6 +23,13 @@ ) +@contextlib.contextmanager +def suppress_known_deprecation(): + with warnings.catch_warnings(record=True) as ctx: + warnings.simplefilter('default', category=DeprecationWarning) + yield ctx + + class BasicTests(fixtures.DistInfoPkg, unittest.TestCase): version_pattern = r'\d+\.\d+(\.\d)?' @@ -44,6 +54,9 @@ def test_package_not_found_mentions_metadata(self): assert "metadata" in str(ctx.exception) + # expected to fail until ABC is enforced + @suppress(AssertionError) + @suppress_known_deprecation() def test_abc_enforced(self): with self.assertRaises(TypeError): type('DistributionSubclass', (Distribution,), {})()