From 8d3459bda7b726384afc01a120a2cb567fffeaae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 22:19:59 +0000 Subject: [PATCH 1/6] Initial plan From ac614f6a2d4cd61433a8fd7bc91507194f3a1dc7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 22:23:12 +0000 Subject: [PATCH 2/6] fix(core): support importlib.metadata.entry_points on Python <3.12 Agent-Logs-Url: https://github.com/NiklasRosenstein/python-databind/sessions/7bb05856-0ab9-441a-8997-2e48bf9ff284 Co-authored-by: NiklasRosenstein <1318438+NiklasRosenstein@users.noreply.github.com> --- .../src/databind/core/tests/union_test.py | 43 +++++++++++++++++++ databind/src/databind/core/union.py | 5 ++- 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 databind/src/databind/core/tests/union_test.py diff --git a/databind/src/databind/core/tests/union_test.py b/databind/src/databind/core/tests/union_test.py new file mode 100644 index 0000000..6b914fd --- /dev/null +++ b/databind/src/databind/core/tests/union_test.py @@ -0,0 +1,43 @@ +import typing as t + +from _pytest.monkeypatch import MonkeyPatch + +import databind.core.union as union_module + + +def test_iter_entry_points_uses_select_api_without_call_arguments(monkeypatch: MonkeyPatch) -> None: + result = [object(), object()] + + class EntryPoints: + def __init__(self) -> None: + self.selected_group: str | None = None + + def select(self, *, group: str) -> list[object]: + self.selected_group = group + return result + + eps = EntryPoints() + + def fake_entry_points(*args: object, **kwargs: object) -> object: + assert args == () + assert kwargs == {} + return eps + + monkeypatch.setattr(union_module, "entry_points", fake_entry_points) + + assert list(union_module.iter_entry_points("my.group")) == t.cast(t.Any, result) + assert eps.selected_group == "my.group" + + +def test_iter_entry_points_falls_back_to_mapping_result(monkeypatch: MonkeyPatch) -> None: + result = [object(), object()] + + def fake_entry_points(*args: object, **kwargs: object) -> object: + assert args == () + assert kwargs == {} + return {"my.group": result} + + monkeypatch.setattr(union_module, "entry_points", fake_entry_points) + + assert list(union_module.iter_entry_points("my.group")) == t.cast(t.Any, result) + assert list(union_module.iter_entry_points("missing")) == [] diff --git a/databind/src/databind/core/union.py b/databind/src/databind/core/union.py index 379ffc8..16653f4 100644 --- a/databind/src/databind/core/union.py +++ b/databind/src/databind/core/union.py @@ -17,7 +17,10 @@ from importlib.metadata import EntryPoint, entry_points def iter_entry_points(group: str) -> t.Iterator[EntryPoint]: - return iter(entry_points(group=group)) + eps = entry_points() + if hasattr(eps, "select"): + return iter(eps.select(group=group)) + return iter(eps.get(group, ())) __all__ = ["UnionMembers", "StaticUnionMembers", "EntrypointUnionMembers", "ImportUnionMembers", "ChainUnionMembers"] From 56f9c4f554261b8c40b205076c52dc003f40fb17 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 07:48:28 +0000 Subject: [PATCH 3/6] test(core): skip union entry point tests on Python 3.8 and add changelog The new union_test.py used PEP 604 (`str | None`) annotations that fail to evaluate on Python 3.9, and exercised the importlib.metadata-backed `iter_entry_points` which only exists on Python >= 3.9 (3.8 uses the pkg_resources path, so `monkeypatch.setattr` had no `entry_points` attribute to patch). Defer annotation evaluation via `from __future__ import annotations` and skip the module on Python < 3.9. Also add the missing changelog entry required by the Changelog CI check. https://claude.ai/code/session_01VxEPWDAPMXVUE4XhbDjNFT --- .changelog/_unreleased.toml | 6 ++++++ databind/src/databind/core/tests/union_test.py | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/.changelog/_unreleased.toml b/.changelog/_unreleased.toml index e69de29..b13c3d3 100644 --- a/.changelog/_unreleased.toml +++ b/.changelog/_unreleased.toml @@ -0,0 +1,6 @@ +[[entries]] +id = "67afd70b-b66e-4aeb-ab30-becf3f121681" +type = "fix" +description = "Fix `Union` entry point discovery on Python <3.12 by no longer passing the `group` keyword to `importlib.metadata.entry_points()`" +author = "@NiklasRosenstein" +pr = "https://github.com/NiklasRosenstein/python-databind/pull/84" diff --git a/databind/src/databind/core/tests/union_test.py b/databind/src/databind/core/tests/union_test.py index 6b914fd..462cb93 100644 --- a/databind/src/databind/core/tests/union_test.py +++ b/databind/src/databind/core/tests/union_test.py @@ -1,9 +1,18 @@ +from __future__ import annotations + +import sys import typing as t +import pytest from _pytest.monkeypatch import MonkeyPatch import databind.core.union as union_module +pytestmark = pytest.mark.skipif( + sys.version_info[:2] < (3, 9), + reason="iter_entry_points is only backed by importlib.metadata on Python >= 3.9", +) + def test_iter_entry_points_uses_select_api_without_call_arguments(monkeypatch: MonkeyPatch) -> None: result = [object(), object()] From c21a505046aae6dccfbfdb1578bd5616c1906eb4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 16:16:26 +0000 Subject: [PATCH 4/6] refactor(core): drop pkg_resources fallback in favor of importlib.metadata importlib.metadata has been available since Python 3.8, and the new entry_points() + .select/.get handling works across all supported versions, so the version-gated pkg_resources branch is no longer needed. This removes a dependency on the deprecated pkg_resources and lets the union entry point tests run on every Python version without a skip. https://claude.ai/code/session_01VxEPWDAPMXVUE4XhbDjNFT --- .changelog/_unreleased.toml | 2 +- databind/src/databind/core/tests/union_test.py | 7 ------- databind/src/databind/core/union.py | 16 ++++++---------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/.changelog/_unreleased.toml b/.changelog/_unreleased.toml index b13c3d3..c25342c 100644 --- a/.changelog/_unreleased.toml +++ b/.changelog/_unreleased.toml @@ -1,6 +1,6 @@ [[entries]] id = "67afd70b-b66e-4aeb-ab30-becf3f121681" type = "fix" -description = "Fix `Union` entry point discovery on Python <3.12 by no longer passing the `group` keyword to `importlib.metadata.entry_points()`" +description = "Fix `Union` entry point discovery by no longer passing the `group` keyword to `importlib.metadata.entry_points()`, and drop the deprecated `pkg_resources` fallback (`importlib.metadata` is used on all supported Python versions)" author = "@NiklasRosenstein" pr = "https://github.com/NiklasRosenstein/python-databind/pull/84" diff --git a/databind/src/databind/core/tests/union_test.py b/databind/src/databind/core/tests/union_test.py index 462cb93..7560bc7 100644 --- a/databind/src/databind/core/tests/union_test.py +++ b/databind/src/databind/core/tests/union_test.py @@ -1,18 +1,11 @@ from __future__ import annotations -import sys import typing as t -import pytest from _pytest.monkeypatch import MonkeyPatch import databind.core.union as union_module -pytestmark = pytest.mark.skipif( - sys.version_info[:2] < (3, 9), - reason="iter_entry_points is only backed by importlib.metadata on Python >= 3.9", -) - def test_iter_entry_points_uses_select_api_without_call_arguments(monkeypatch: MonkeyPatch) -> None: result = [object(), object()] diff --git a/databind/src/databind/core/union.py b/databind/src/databind/core/union.py index 16653f4..7f624cc 100644 --- a/databind/src/databind/core/union.py +++ b/databind/src/databind/core/union.py @@ -3,24 +3,20 @@ import abc import dataclasses import importlib -import sys import types import typing as t +from importlib.metadata import EntryPoint, entry_points from typeapi import ClassTypeHint, LiteralTypeHint, TypeHint from databind.core.utils import T -if sys.version_info[:2] < (3, 9): - from pkg_resources import EntryPoint, iter_entry_points # type: ignore[import-not-found,unused-ignore] -else: - from importlib.metadata import EntryPoint, entry_points - def iter_entry_points(group: str) -> t.Iterator[EntryPoint]: - eps = entry_points() - if hasattr(eps, "select"): - return iter(eps.select(group=group)) - return iter(eps.get(group, ())) +def iter_entry_points(group: str) -> t.Iterator[EntryPoint]: + eps = entry_points() + if hasattr(eps, "select"): + return iter(eps.select(group=group)) + return iter(eps.get(group, ())) __all__ = ["UnionMembers", "StaticUnionMembers", "EntrypointUnionMembers", "ImportUnionMembers", "ChainUnionMembers"] From 9258e18558d8932430bdefa6ea20d52061fd4c3b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 16:37:25 +0000 Subject: [PATCH 5/6] chore: remove dead setuptools dependency and types-setuptools With the pkg_resources fallback in union.py gone, the runtime setuptools dependency (declared only for Python <3.9 to provide pkg_resources) and the types-setuptools mypy stub are no longer used. https://claude.ai/code/session_01VxEPWDAPMXVUE4XhbDjNFT --- .changelog/_unreleased.toml | 2 +- databind/pyproject.toml | 1 - pyproject.toml | 1 - uv.lock | 53 ------------------------------------- 4 files changed, 1 insertion(+), 56 deletions(-) diff --git a/.changelog/_unreleased.toml b/.changelog/_unreleased.toml index c25342c..9da389f 100644 --- a/.changelog/_unreleased.toml +++ b/.changelog/_unreleased.toml @@ -1,6 +1,6 @@ [[entries]] id = "67afd70b-b66e-4aeb-ab30-becf3f121681" type = "fix" -description = "Fix `Union` entry point discovery by no longer passing the `group` keyword to `importlib.metadata.entry_points()`, and drop the deprecated `pkg_resources` fallback (`importlib.metadata` is used on all supported Python versions)" +description = "Fix `Union` entry point discovery by no longer passing the `group` keyword to `importlib.metadata.entry_points()`, drop the deprecated `pkg_resources` fallback (`importlib.metadata` is used on all supported Python versions), and remove the now-unused `setuptools` runtime dependency on Python <3.9" author = "@NiklasRosenstein" pr = "https://github.com/NiklasRosenstein/python-databind/pull/84" diff --git a/databind/pyproject.toml b/databind/pyproject.toml index a161ce9..4a02357 100644 --- a/databind/pyproject.toml +++ b/databind/pyproject.toml @@ -14,7 +14,6 @@ dependencies = [ "Deprecated>=1.2.12,<2", "nr-date>=2.0.0,<3", "nr-stream>=1.0.0,<2", - "setuptools>=40.8.0,<82; python_version < '3.9'", "typeapi>=2.0.1,<3", "typing-extensions>=3.10.0,<5", ] diff --git a/pyproject.toml b/pyproject.toml index e8688a4..9c1bccb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,6 @@ dev = [ "mypy>=1.9.0,<2.0.0", "types-dataclasses", "types-deprecated", - "types-setuptools", "types-termcolor", ] docs = [ diff --git a/uv.lock b/uv.lock index 0ac9fb6..a631c9a 100644 --- a/uv.lock +++ b/uv.lock @@ -278,7 +278,6 @@ dependencies = [ { name = "deprecated" }, { name = "nr-date" }, { name = "nr-stream" }, - { name = "setuptools", marker = "python_full_version < '3.9'" }, { name = "typeapi" }, { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, @@ -289,7 +288,6 @@ requires-dist = [ { name = "deprecated", specifier = ">=1.2.12,<2" }, { name = "nr-date", specifier = ">=2.0.0,<3" }, { name = "nr-stream", specifier = ">=1.0.0,<2" }, - { name = "setuptools", marker = "python_full_version < '3.9'", specifier = ">=40.8.0,<82" }, { name = "typeapi", specifier = ">=2.0.1,<3" }, { name = "typing-extensions", specifier = ">=3.10.0,<5" }, ] @@ -340,9 +338,6 @@ dev = [ { name = "types-dataclasses" }, { name = "types-deprecated", version = "1.2.15.20241117", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "types-deprecated", version = "1.3.1.20260130", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "types-setuptools", version = "75.8.0.20250110", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "types-setuptools", version = "81.0.0.20260209", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "types-setuptools", version = "82.0.0.20260210", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "types-termcolor" }, ] docs = [ @@ -368,7 +363,6 @@ dev = [ { name = "pytest", specifier = ">=8.1.1,<9.0.0" }, { name = "types-dataclasses" }, { name = "types-deprecated" }, - { name = "types-setuptools" }, { name = "types-termcolor" }, ] docs = [ @@ -1435,15 +1429,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, ] -[[package]] -name = "setuptools" -version = "75.3.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0e/93/d2622cbf262418995140dfc1ddb890badd6322893fa122302577c82b9617/setuptools-75.3.4.tar.gz", hash = "sha256:b4ea3f76e1633c4d2d422a5d68ab35fd35402ad71e6acaa5d7e5956eb47e8887", size = 1354595, upload-time = "2026-02-08T14:12:34.287Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/b1/961ba076c7d3732e3a97e6681c55ef647afb795fe8bfcd27becec8a762ce/setuptools-75.3.4-py3-none-any.whl", hash = "sha256:2dd50a7f42dddfa1d02a36f275dbe716f38ed250224f609d35fb60a09593d93e", size = 1251633, upload-time = "2026-02-08T14:12:32.364Z" }, -] - [[package]] name = "six" version = "1.17.0" @@ -1556,44 +1541,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/b2/6f920582af7efcd37165cd6321707f3ad5839dd24565a8a982f2bd9c6fd1/types_deprecated-1.3.1.20260130-py3-none-any.whl", hash = "sha256:593934d85c38ca321a9d301f00c42ffe13e4cf830b71b10579185ba0ce172d9a", size = 9077, upload-time = "2026-01-30T03:58:16.633Z" }, ] -[[package]] -name = "types-setuptools" -version = "75.8.0.20250110" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.8.1' and python_full_version < '3.9'", - "python_full_version < '3.8.1'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f7/42/5713e90d4f9683f2301d900f33e4fc2405ad8ac224dda30f6cb7f4cd215b/types_setuptools-75.8.0.20250110.tar.gz", hash = "sha256:96f7ec8bbd6e0a54ea180d66ad68ad7a1d7954e7281a710ea2de75e355545271", size = 48185, upload-time = "2025-01-10T02:45:52.085Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/a3/dbfd106751b11c728cec21cc62cbfe7ff7391b935c4b6e8f0bdc2e6fd541/types_setuptools-75.8.0.20250110-py3-none-any.whl", hash = "sha256:a9f12980bbf9bcdc23ecd80755789085bad6bfce4060c2275bc2b4ca9f2bc480", size = 71521, upload-time = "2025-01-10T02:45:49.873Z" }, -] - -[[package]] -name = "types-setuptools" -version = "81.0.0.20260209" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/9e/57/f1f7992d6d7bded78d1f14dc23d59e87601920852bf10ece2325e49bacae/types_setuptools-81.0.0.20260209.tar.gz", hash = "sha256:2c2eb64499b41b672c387f6f45678a28d20a143a81b45a5c77acbfd4da0df3e1", size = 43201, upload-time = "2026-02-09T04:14:15.505Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/87/90c9143af95850bdaf7eb0d47c59e5c3a8b55fc5a49aca0eb7f98cb964d5/types_setuptools-81.0.0.20260209-py3-none-any.whl", hash = "sha256:4facf71e3f953f8f5ac0020cd6c1b5e493aaff0183e85830bc34870b6abf8475", size = 64194, upload-time = "2026-02-09T04:14:14.278Z" }, -] - -[[package]] -name = "types-setuptools" -version = "82.0.0.20260210" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", - "python_full_version == '3.10.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/4b/90/796ac8c774a7f535084aacbaa6b7053d16fff5c630eff87c3ecff7896c37/types_setuptools-82.0.0.20260210.tar.gz", hash = "sha256:d9719fbbeb185254480ade1f25327c4654f8c00efda3fec36823379cebcdee58", size = 44768, upload-time = "2026-02-10T04:22:02.107Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/54/3489432b1d9bc713c9d8aa810296b8f5b0088403662959fb63a8acdbd4fc/types_setuptools-82.0.0.20260210-py3-none-any.whl", hash = "sha256:5124a7daf67f195c6054e0f00f1d97c69caad12fdcf9113eba33eff0bce8cd2b", size = 68433, upload-time = "2026-02-10T04:22:00.876Z" }, -] - [[package]] name = "types-termcolor" version = "1.1.6.2" From 90887c3d8aef8c0476f414666bc7759770d364cc Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 17:05:49 +0000 Subject: [PATCH 6/6] test(core): import MonkeyPatch from the public pytest API Use the public `pytest.MonkeyPatch` entrypoint instead of the private `_pytest.monkeypatch` module, which is not part of pytest's public API. https://claude.ai/code/session_01VxEPWDAPMXVUE4XhbDjNFT --- databind/src/databind/core/tests/union_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databind/src/databind/core/tests/union_test.py b/databind/src/databind/core/tests/union_test.py index 7560bc7..4b979e5 100644 --- a/databind/src/databind/core/tests/union_test.py +++ b/databind/src/databind/core/tests/union_test.py @@ -2,7 +2,7 @@ import typing as t -from _pytest.monkeypatch import MonkeyPatch +from pytest import MonkeyPatch import databind.core.union as union_module