From 6702b62cca03e463a51eacdf487615cbc71d016e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 13 Jan 2026 14:15:04 +0200 Subject: [PATCH] Drop support for EOL Python 3.9 --- .github/workflows/main.yml | 4 +- importlib_metadata/__init__.py | 6 +-- importlib_metadata/_functools.py | 3 +- importlib_metadata/compat/py39.py | 42 ------------------ pyproject.toml | 2 +- tests/compat/py312.py | 6 ++- tests/compat/py39.py | 8 ---- tests/compat/test_py39_compat.py | 74 ------------------------------- tests/fixtures.py | 7 ++- tests/test_main.py | 2 +- 10 files changed, 19 insertions(+), 135 deletions(-) delete mode 100644 importlib_metadata/compat/py39.py delete mode 100644 tests/compat/py39.py delete mode 100644 tests/compat/test_py39_compat.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 53513eee..2a7899c0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,15 +34,13 @@ jobs: # https://blog.jaraco.com/efficient-use-of-ci-resources/ matrix: python: - - "3.9" + - "3.10" - "3.13" platform: - ubuntu-latest - macos-latest - windows-latest include: - - python: "3.10" - platform: ubuntu-latest - python: "3.11" platform: ubuntu-latest - python: "3.12" diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 508b02e4..334a0916 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -39,7 +39,7 @@ from ._itertools import always_iterable, bucket, unique_everseen from ._meta import PackageMetadata, SimplePath from ._typing import md_none -from .compat import py39, py311 +from .compat import py311 __all__ = [ 'Distribution', @@ -340,7 +340,7 @@ def select(self, **params) -> EntryPoints: Select entry points from self that match the given parameters (typically group and/or name). """ - return EntryPoints(ep for ep in self if py39.ep_matches(ep, **params)) + return EntryPoints(ep for ep in self if ep.matches(**params)) @property def names(self) -> set[str]: @@ -1088,7 +1088,7 @@ def version(distribution_name: str) -> str: _unique = functools.partial( unique_everseen, - key=py39.normalized_name, + key=operator.attrgetter('_normalized_name'), ) """ Wrapper for ``distributions`` to return unique distributions by name. diff --git a/importlib_metadata/_functools.py b/importlib_metadata/_functools.py index b1fd04a8..c159b46e 100644 --- a/importlib_metadata/_functools.py +++ b/importlib_metadata/_functools.py @@ -1,6 +1,7 @@ import functools import types -from typing import Callable, TypeVar +from collections.abc import Callable +from typing import TypeVar # from jaraco.functools 3.3 diff --git a/importlib_metadata/compat/py39.py b/importlib_metadata/compat/py39.py deleted file mode 100644 index 3eb9c01e..00000000 --- a/importlib_metadata/compat/py39.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -Compatibility layer with Python 3.8/3.9 -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: # pragma: no cover - # Prevent circular imports on runtime. - from .. import Distribution, EntryPoint -else: - Distribution = EntryPoint = Any - -from .._typing import md_none - - -def normalized_name(dist: Distribution) -> str | None: - """ - Honor name normalization for distributions that don't provide ``_normalized_name``. - """ - try: - return dist._normalized_name - except AttributeError: - from .. import Prepared # -> delay to prevent circular imports. - - return Prepared.normalize( - getattr(dist, "name", None) or md_none(dist.metadata)['Name'] - ) - - -def ep_matches(ep: EntryPoint, **params) -> bool: - """ - Workaround for ``EntryPoint`` objects without the ``matches`` method. - """ - try: - return ep.matches(**params) - except AttributeError: - from .. import EntryPoint # -> delay to prevent circular imports. - - # Reconstruct the EntryPoint object to make sure it is compatible. - return EntryPoint(ep.name, ep.value, ep.group).matches(**params) diff --git a/pyproject.toml b/pyproject.toml index b71b9a9b..1e83bde9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", ] -requires-python = ">=3.9" +requires-python = ">=3.10" license = "Apache-2.0" dependencies = [ "zipp>=3.20", diff --git a/tests/compat/py312.py b/tests/compat/py312.py index ea9a58ba..c246641d 100644 --- a/tests/compat/py312.py +++ b/tests/compat/py312.py @@ -1,6 +1,10 @@ import contextlib -from .py39 import import_helper +from jaraco.test.cpython import from_test_support, try_import + +import_helper = try_import('import_helper') or from_test_support( + 'modules_setup', 'modules_cleanup' +) @contextlib.contextmanager diff --git a/tests/compat/py39.py b/tests/compat/py39.py deleted file mode 100644 index 4e45d7cc..00000000 --- a/tests/compat/py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from jaraco.test.cpython import from_test_support, try_import - -os_helper = try_import('os_helper') or from_test_support( - 'FS_NONASCII', 'skip_unless_symlink', 'temp_dir' -) -import_helper = try_import('import_helper') or from_test_support( - 'modules_setup', 'modules_cleanup' -) diff --git a/tests/compat/test_py39_compat.py b/tests/compat/test_py39_compat.py deleted file mode 100644 index db9fb1b7..00000000 --- a/tests/compat/test_py39_compat.py +++ /dev/null @@ -1,74 +0,0 @@ -import pathlib -import sys -import unittest - -from importlib_metadata import ( - distribution, - distributions, - entry_points, - metadata, - version, -) - -from .. import fixtures - - -class OldStdlibFinderTests(fixtures.DistInfoPkgOffPath, unittest.TestCase): - def setUp(self): - if sys.version_info >= (3, 10): - self.skipTest("Tests specific for Python 3.8/3.9") - super().setUp() - - def _meta_path_finder(self): - from importlib.metadata import ( - Distribution, - DistributionFinder, - PathDistribution, - ) - from importlib.util import spec_from_file_location - - path = pathlib.Path(self.site_dir) - - class CustomDistribution(Distribution): - def __init__(self, name, path): - self.name = name - self._path_distribution = PathDistribution(path) - - def read_text(self, filename): - return self._path_distribution.read_text(filename) - - def locate_file(self, path): - return self._path_distribution.locate_file(path) - - class CustomFinder: - @classmethod - def find_spec(cls, fullname, _path=None, _target=None): - candidate = pathlib.Path(path, *fullname.split(".")).with_suffix(".py") - if candidate.exists(): - return spec_from_file_location(fullname, candidate) - - @classmethod - def find_distributions(self, context=DistributionFinder.Context()): - for dist_info in path.glob("*.dist-info"): - yield PathDistribution(dist_info) - name, _, _ = str(dist_info).partition("-") - yield CustomDistribution(name + "_custom", dist_info) - - return CustomFinder - - def test_compatibility_with_old_stdlib_path_distribution(self): - """ - Given a custom finder that uses Python 3.8/3.9 importlib.metadata is installed, - when importlib_metadata functions are called, there should be no exceptions. - Ref python/importlib_metadata#396. - """ - self.fixtures.enter_context(fixtures.install_finder(self._meta_path_finder())) - - assert list(distributions()) - assert distribution("distinfo_pkg") - assert distribution("distinfo_pkg_custom") - assert version("distinfo_pkg") > "0" - assert version("distinfo_pkg_custom") > "0" - assert list(metadata("distinfo_pkg")) - assert list(metadata("distinfo_pkg_custom")) - assert list(entry_points(group="entries")) diff --git a/tests/fixtures.py b/tests/fixtures.py index 021eb811..bf4f8c40 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -8,11 +8,16 @@ import textwrap from importlib import resources +from jaraco.test.cpython import from_test_support, try_import + from . import _path from ._path import FilesSpec -from .compat.py39 import os_helper from .compat.py312 import import_helper +os_helper = try_import('os_helper') or from_test_support( + 'FS_NONASCII', 'skip_unless_symlink', 'temp_dir' +) + @contextlib.contextmanager def tmp_path(): diff --git a/tests/test_main.py b/tests/test_main.py index 5ed08c89..f4ae69a7 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -20,7 +20,7 @@ from . import fixtures from ._path import Symlink -from .compat.py39 import os_helper +from .fixtures import os_helper class BasicTests(fixtures.DistInfoPkg, unittest.TestCase):