From 1d42e564d9bd1e026e8d635880336dd0528c2c29 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Mon, 1 Sep 2025 13:27:05 +0200 Subject: [PATCH 1/3] fix: drop use of non-standard pkg_resources API Use the canonical `importlib.metadata.entry_points` instead, and a well defined fallback for older Python versions. Fix #2404 --- isort/settings.py | 17 ++++++++--------- pyproject.toml | 8 +++++++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/isort/settings.py b/isort/settings.py index 1ce832299..2077574b6 100644 --- a/isort/settings.py +++ b/isort/settings.py @@ -45,6 +45,11 @@ from .wrap_modes import WrapModes from .wrap_modes import from_string as wrap_mode_from_string +if sys.version_info < (3, 10): # pragma: no cover + from importlib_metadata import entry_points +else: + from importlib.metadata import entry_points + if TYPE_CHECKING: tomllib: Any else: @@ -356,9 +361,7 @@ def __init__( profile: Dict[str, Any] = {} if profile_name: if profile_name not in profiles: - import pkg_resources - - for plugin in pkg_resources.iter_entry_points("isort.profiles"): + for plugin in entry_points(group="isort.profiles"): profiles.setdefault(plugin.name, plugin.load()) if profile_name not in profiles: @@ -473,9 +476,7 @@ def __init__( combined_config["src_paths"] = tuple(src_paths) if "formatter" in combined_config: - import pkg_resources - - for plugin in pkg_resources.iter_entry_points("isort.formatters"): + for plugin in entry_points(group="isort.formatters"): if plugin.name == combined_config["formatter"]: combined_config["formatting_function"] = plugin.load() break @@ -715,9 +716,7 @@ def sorting_function(self) -> Callable[..., List[str]]: self._sorting_function = sorted else: available_sort_orders = ["natural", "native"] - import pkg_resources - - for sort_plugin in pkg_resources.iter_entry_points("isort.sort_function"): + for sort_plugin in entry_points(group="isort.sort_function"): available_sort_orders.append(sort_plugin.name) if sort_plugin.name == self.sort_order: self._sorting_function = sort_plugin.load() diff --git a/pyproject.toml b/pyproject.toml index 6614569fa..2b39d5558 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,13 @@ include = [ ] requires-python = ">=3.9.0" -dependencies = [] +dependencies = [ + # we only need the entry_points() function, which appeared in the stdlib + # in Python 3.10. + # importlib_metadata 4.6 is the version matching stdlib 3.10 + # See grid at https://pypi.org/project/importlib-metadata/ + "importlib_metadata >= 4.6.0; python_version < '3.10'", +] [project.urls] Homepage = "https://pycqa.github.io/isort/index.html" From 18ecd0c3bf0bc7d6eb9bb5b9a72297f172a57663 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sat, 6 Sep 2025 19:46:07 +0200 Subject: [PATCH 2/3] chore: drop branch guarding unsupported Python versions --- tests/unit/test_regressions.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/unit/test_regressions.py b/tests/unit/test_regressions.py index 4180f81c0..1d97fa5a2 100644 --- a/tests/unit/test_regressions.py +++ b/tests/unit/test_regressions.py @@ -1875,11 +1875,7 @@ class Bar: def test_isort_should_produce_the_same_code_on_subsequent_runs_issue_1799(tmpdir): code = """import sys -if sys.version_info[:2] >= (3, 8): - # TODO: Import directly (no need for conditional) when `python_requires = >= 3.8` - from importlib.metadata import PackageNotFoundError, version # pragma: no cover -else: - from importlib_metadata import PackageNotFoundError, version # pragma: no cover +from importlib.metadata import PackageNotFoundError, version """ config_file = tmpdir.join(".isort.cfg") config_file.write( From be46cd476d1a61996a6a4469177edf0869df531e Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sat, 6 Sep 2025 19:43:52 +0200 Subject: [PATCH 3/3] refactor: make importlib metadata package import lazy No reason I can discern for it, just a matter of traditions I guess. https://github.com/PyCQA/isort/pull/2405#discussion_r2327233278 --- isort/settings.py | 23 ++++++++++++++++++----- pyproject.toml | 4 ++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/isort/settings.py b/isort/settings.py index 2077574b6..9c6bc3b24 100644 --- a/isort/settings.py +++ b/isort/settings.py @@ -45,12 +45,12 @@ from .wrap_modes import WrapModes from .wrap_modes import from_string as wrap_mode_from_string -if sys.version_info < (3, 10): # pragma: no cover - from importlib_metadata import entry_points -else: - from importlib.metadata import entry_points - if TYPE_CHECKING: + if sys.version_info < (3, 10): # pragma: no cover + EntryPoints = Any + else: + from importlib.metadata import EntryPoints + tomllib: Any else: if sys.version_info >= (3, 11): @@ -937,4 +937,17 @@ def _as_bool(value: str) -> bool: raise ValueError(f"invalid truth value {value}") +def entry_points(group: str) -> "EntryPoints": + """Call entry_point after lazy loading it. + + TODO: The reason for lazy loading here are unknown. + """ + if sys.version_info < (3, 10): # pragma: no cover + from importlib_metadata import entry_points + else: + from importlib.metadata import entry_points + + return entry_points(group=group) + + DEFAULT_CONFIG = Config() diff --git a/pyproject.toml b/pyproject.toml index 2b39d5558..9152b2045 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -180,6 +180,10 @@ allow_untyped_defs = true allow_incomplete_defs = true allow_untyped_calls = true +[[tool.mypy.overrides]] +module = "importlib_metadata.*" +ignore_missing_imports = true + [tool.ruff] line-length = 100 lint.select = [