From 034d797e28d64acb10abd1349df0c0b9d68a82fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20Tvrtkovi=C4=87?= Date: Tue, 29 Jul 2025 22:45:39 +0200 Subject: [PATCH 1/9] Request stringified annotations --- src/attr/_compat.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/attr/_compat.py b/src/attr/_compat.py index 130de4303..475450ddb 100644 --- a/src/attr/_compat.py +++ b/src/attr/_compat.py @@ -17,10 +17,16 @@ PY_3_14_PLUS = sys.version_info[:2] >= (3, 14) -if PY_3_14_PLUS: # pragma: no cover +if PY_3_14_PLUS: import annotationlib - _get_annotations = annotationlib.get_annotations + # We request stringified annotations to not break in the presence of + # forward references. + + def _get_annotations(cls): + return annotationlib.get_annotations( + cls, format=annotationlib.Format.STRING + ) else: From 074f64d5598317b3ef9ff46421b966e7d6ae87ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20Tvrtkovi=C4=87?= Date: Tue, 29 Jul 2025 22:51:45 +0200 Subject: [PATCH 2/9] Add test --- conftest.py => tests/conftest.py | 6 ++++-- tests/test_forward_references.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) rename conftest.py => tests/conftest.py (79%) create mode 100644 tests/test_forward_references.py diff --git a/conftest.py b/tests/conftest.py similarity index 79% rename from conftest.py rename to tests/conftest.py index e22f79d53..3e48c3fdf 100644 --- a/conftest.py +++ b/tests/conftest.py @@ -6,7 +6,7 @@ from hypothesis import HealthCheck, settings -from attr._compat import PY_3_10_PLUS +from attr._compat import PY_3_10_PLUS, PY_3_14_PLUS @pytest.fixture(name="slots", params=(True, False)) @@ -33,4 +33,6 @@ def pytest_configure(config): collect_ignore = [] if not PY_3_10_PLUS: - collect_ignore.extend(["tests/test_pattern_matching.py"]) + collect_ignore.extend(["test_pattern_matching.py"]) +if not PY_3_14_PLUS: + collect_ignore.extend(["test_forward_references.py"]) diff --git a/tests/test_forward_references.py b/tests/test_forward_references.py new file mode 100644 index 000000000..172d6a2d6 --- /dev/null +++ b/tests/test_forward_references.py @@ -0,0 +1,14 @@ +"""Tests for behavior specific to forward references via PEP 749.""" + +from attrs import define + + +def test_forward_class_reference(): + """Class A should be able to reference B even though it is defined later.""" + + @define + class A: + b: B + + class B: + pass From 7aaeb6322fcd626a1b34ea5d443baf52d6bf3de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20Tvrtkovi=C4=87?= Date: Tue, 29 Jul 2025 23:01:20 +0200 Subject: [PATCH 3/9] ruff: mark test_forward_references as 3.14 --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 2d665ffce..bdb832e3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -196,6 +196,7 @@ line-length = 79 [tool.ruff.per-file-target-version] "tests/test_pattern_matching.py" = "py310" +"tests/test_forward_references.py" = "py314" [tool.ruff.lint] select = ["ALL"] From 9e8e94e7f5f501be4560b7cfbd299345d1034f98 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Wed, 30 Jul 2025 11:00:56 +0200 Subject: [PATCH 4/9] Use FORWARDREF instead See https://github.com/python/cpython/blob/9d3b53c47fab9ebf1f40d6f21b7d1ad391c14cd7/Lib/dataclasses.py#L989-L990 --- src/attr/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/attr/_compat.py b/src/attr/_compat.py index 475450ddb..006e4c6ad 100644 --- a/src/attr/_compat.py +++ b/src/attr/_compat.py @@ -25,7 +25,7 @@ def _get_annotations(cls): return annotationlib.get_annotations( - cls, format=annotationlib.Format.STRING + cls, format=annotationlib.Format.FORWARDREF ) else: From 1e710b5f92278d3f3e11c1d6ec101d708463f9e6 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Wed, 30 Jul 2025 11:02:04 +0200 Subject: [PATCH 5/9] Fix comment --- src/attr/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/attr/_compat.py b/src/attr/_compat.py index 006e4c6ad..bc68ed9ea 100644 --- a/src/attr/_compat.py +++ b/src/attr/_compat.py @@ -20,7 +20,7 @@ if PY_3_14_PLUS: import annotationlib - # We request stringified annotations to not break in the presence of + # We request forward-ref annotations to not break in the presence of # forward references. def _get_annotations(cls): From 5a9fdf53bf591961949255eea989ccef10d2edaa Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Wed, 30 Jul 2025 11:03:04 +0200 Subject: [PATCH 6/9] Apply docstring standard --- tests/test_forward_references.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_forward_references.py b/tests/test_forward_references.py index 172d6a2d6..edc25085d 100644 --- a/tests/test_forward_references.py +++ b/tests/test_forward_references.py @@ -1,10 +1,14 @@ -"""Tests for behavior specific to forward references via PEP 749.""" +""" +Tests for behavior specific to forward references via PEP 749. +""" from attrs import define def test_forward_class_reference(): - """Class A should be able to reference B even though it is defined later.""" + """ + Class A can reference B even though it is defined later. + """ @define class A: From d73b58e368e2d6e29b5a39d0d2eb67af05a96763 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Wed, 30 Jul 2025 11:05:00 +0200 Subject: [PATCH 7/9] Add news fragment Unite with the other one to avoid confusion. --- changelog.d/1446.change.md | 3 +-- changelog.d/1451.change.md | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 changelog.d/1451.change.md diff --git a/changelog.d/1446.change.md b/changelog.d/1446.change.md index 2a069ea36..bd929c9e9 100644 --- a/changelog.d/1446.change.md +++ b/changelog.d/1446.change.md @@ -1,2 +1 @@ -On 3.14, the cycles in slotted classes are now manually broken. -An explicit call to `gc.collect()` is still necessary, unfortunately. +Add support for Python 3.14. diff --git a/changelog.d/1451.change.md b/changelog.d/1451.change.md new file mode 100644 index 000000000..bd929c9e9 --- /dev/null +++ b/changelog.d/1451.change.md @@ -0,0 +1 @@ +Add support for Python 3.14. From d8aab6731577d0b2ccda411de6d74641f76bc7d2 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Wed, 30 Jul 2025 11:32:15 +0200 Subject: [PATCH 8/9] Temporal form --- changelog.d/1446.change.md | 2 +- changelog.d/1451.change.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.d/1446.change.md b/changelog.d/1446.change.md index bd929c9e9..6c0754987 100644 --- a/changelog.d/1446.change.md +++ b/changelog.d/1446.change.md @@ -1 +1 @@ -Add support for Python 3.14. +Added support for Python 3.14. diff --git a/changelog.d/1451.change.md b/changelog.d/1451.change.md index bd929c9e9..6c0754987 100644 --- a/changelog.d/1451.change.md +++ b/changelog.d/1451.change.md @@ -1 +1 @@ -Add support for Python 3.14. +Added support for Python 3.14. From b003cd344ef7c3310f4944f31d3b5671efb77b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20Tvrtkovi=C4=87?= Date: Wed, 30 Jul 2025 17:55:24 +0200 Subject: [PATCH 9/9] Flesh out test --- tests/test_forward_references.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_forward_references.py b/tests/test_forward_references.py index edc25085d..90dfc2b0c 100644 --- a/tests/test_forward_references.py +++ b/tests/test_forward_references.py @@ -2,7 +2,7 @@ Tests for behavior specific to forward references via PEP 749. """ -from attrs import define +from attrs import define, fields, resolve_types def test_forward_class_reference(): @@ -16,3 +16,7 @@ class A: class B: pass + + resolve_types(A) + + assert fields(A).b.type is B