From 95654e0b584d5aa63fa67693b6774b6b4adeec43 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 17 Apr 2023 19:49:32 -0400 Subject: [PATCH 1/3] 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,), {})() From 0e4bd94e20d6e3308ab336762299909f49809e9e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 17 Apr 2023 21:16:08 -0400 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=9A=A1=20Toil=20the=20docs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 8e7762d0..164564aa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -68,4 +68,6 @@ ('py:class', 'importlib_metadata._meta._T'), # Workaround for #435 ('py:class', '_T'), + # Other workarounds + ('py:class', 'importlib_metadata.DeprecatedNonAbstract'), ] From 41240d0bc9307a3e07ead49d6e7139d362baeb72 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 17 Apr 2023 21:17:43 -0400 Subject: [PATCH 3/3] Update changelog --- CHANGES.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 744d2d4e..bd9cd385 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +v6.5.0 +====== + +* #422: Removed ABC metaclass from ``Distribution`` and instead + deprecated construction of ``Distribution`` objects without + concrete methods. + v6.4.1 ======