Skip to content

Commit 7373d88

Browse files
authored
Restore basic functionality on 3.14[sic] (#1329)
* Restore basic functionality on 3.14[sic] Essentially switch to PEP 649 / 749 for annotations. Some tests need to be skipped for now, but the rest is working. Fixes #1326 * Add news fragment * We have not 3.14 CI yet * Use imprerative xfails instead of skips
1 parent f520d9a commit 7373d88

File tree

7 files changed

+35
-10
lines changed

7 files changed

+35
-10
lines changed

changelog.d/1329.change.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Restored support for PEP [649](https://peps.python.org/pep-0649/) / [749](https://peps.python.org/pep-0749/)-implementing Pythons -- currently 3.14-dev.

src/attr/_compat.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
PY_3_10_PLUS = sys.version_info[:2] >= (3, 10)
1616
PY_3_12_PLUS = sys.version_info[:2] >= (3, 12)
1717
PY_3_13_PLUS = sys.version_info[:2] >= (3, 13)
18+
PY_3_14_PLUS = sys.version_info[:2] >= (3, 14)
1819

1920

2021
if sys.version_info < (3, 8):
@@ -25,6 +26,19 @@
2526
else:
2627
from typing import Protocol # noqa: F401
2728

29+
if PY_3_14_PLUS: # pragma: no cover
30+
import annotationlib
31+
32+
_get_annotations = annotationlib.get_annotations
33+
34+
else:
35+
36+
def _get_annotations(cls):
37+
"""
38+
Get annotations for *cls*.
39+
"""
40+
return cls.__dict__.get("__annotations__", {})
41+
2842

2943
class _AnnotationExtractor:
3044
"""

src/attr/_make.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
PY_3_8_PLUS,
2424
PY_3_10_PLUS,
2525
_AnnotationExtractor,
26+
_get_annotations,
2627
get_generic_base,
2728
)
2829
from .exceptions import (
@@ -308,13 +309,6 @@ def _has_own_attribute(cls, attrib_name):
308309
return attrib_name in cls.__dict__
309310

310311

311-
def _get_annotations(cls):
312-
"""
313-
Get annotations for *cls*.
314-
"""
315-
return cls.__dict__.get("__annotations__", {})
316-
317-
318312
def _collect_base_attrs(cls, taken_attr_names):
319313
"""
320314
Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.

tests/test_3rd_party.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88

99
from hypothesis import given
1010

11+
from attr._compat import PY_3_14_PLUS
12+
1113
from .strategies import simple_classes
1214

1315

1416
cloudpickle = pytest.importorskip("cloudpickle")
1517

1618

19+
@pytest.mark.xfail(
20+
PY_3_14_PLUS, reason="cloudpickle is currently broken on 3.14."
21+
)
1722
class TestCloudpickleCompat:
1823
"""
1924
Tests for compatibility with ``cloudpickle``.

tests/test_annotations.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import attr
1414

15+
from attr._compat import PY_3_14_PLUS
1516
from attr._make import _is_class_var
1617
from attr.exceptions import UnannotatedAttributeError
1718

@@ -588,6 +589,8 @@ def test_self_reference(self, slots):
588589
"""
589590
References to self class using quotes can be resolved.
590591
"""
592+
if PY_3_14_PLUS and not slots:
593+
pytest.xfail("References are changing a lot in 3.14.")
591594

592595
@attr.s(slots=slots, auto_attribs=True)
593596
class A:
@@ -603,6 +606,8 @@ def test_forward_reference(self, slots):
603606
"""
604607
Forward references can be resolved.
605608
"""
609+
if PY_3_14_PLUS and not slots:
610+
pytest.xfail("Forward references are changing a lot in 3.14.")
606611

607612
@attr.s(slots=slots, auto_attribs=True)
608613
class A:

tests/test_make.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import attr
2222

2323
from attr import _config
24-
from attr._compat import PY_3_8_PLUS, PY_3_10_PLUS
24+
from attr._compat import PY_3_8_PLUS, PY_3_10_PLUS, PY_3_14_PLUS
2525
from attr._make import (
2626
Attribute,
2727
Factory,
@@ -1838,9 +1838,11 @@ class C2(C):
18381838
assert [C2] == C.__subclasses__()
18391839

18401840
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
1841+
@pytest.mark.xfail(PY_3_14_PLUS, reason="Currently broken on nightly.")
18411842
def test_no_references_to_original_when_using_cached_property(self):
18421843
"""
1843-
When subclassing a slotted class and using cached property, there are no stray references to the original class.
1844+
When subclassing a slotted class and using cached property, there are
1845+
no stray references to the original class.
18441846
"""
18451847

18461848
@attr.s(slots=True)

tests/test_slots.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
Unit tests for slots-related functionality.
55
"""
6+
67
import functools
78
import pickle
89
import weakref
@@ -14,7 +15,7 @@
1415
import attr
1516
import attrs
1617

17-
from attr._compat import PY_3_8_PLUS, PYPY
18+
from attr._compat import PY_3_8_PLUS, PY_3_14_PLUS, PYPY
1819

1920

2021
# Pympler doesn't work on PyPy.
@@ -774,6 +775,9 @@ def f(self) -> int:
774775

775776

776777
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
778+
@pytest.mark.xfail(
779+
PY_3_14_PLUS, reason="3.14 returns weird annotation for cached_properies"
780+
)
777781
def test_slots_cached_property_infers_type():
778782
"""
779783
Infers type of cached property.

0 commit comments

Comments
 (0)