Skip to content

Commit

Permalink
Restore basic functionality on 3.14[sic]
Browse files Browse the repository at this point in the history
Essentially switch to PEP 649 / 749 for annotations. Some tests need to
be skipped for now, but the rest is working.

Fixes #1326
  • Loading branch information
hynek committed Aug 6, 2024
1 parent 69d6fd1 commit f58093b
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 10 deletions.
14 changes: 14 additions & 0 deletions src/attr/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
PY_3_10_PLUS = sys.version_info[:2] >= (3, 10)
PY_3_12_PLUS = sys.version_info[:2] >= (3, 12)
PY_3_13_PLUS = sys.version_info[:2] >= (3, 13)
PY_3_14_PLUS = sys.version_info[:2] >= (3, 14)


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

if PY_3_14_PLUS:
import annotationlib

_get_annotations = annotationlib.get_annotations

else:

def _get_annotations(cls):
"""
Get annotations for *cls*.
"""
return cls.__dict__.get("__annotations__", {})


class _AnnotationExtractor:
"""
Expand Down
8 changes: 1 addition & 7 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
PY_3_8_PLUS,
PY_3_10_PLUS,
_AnnotationExtractor,
_get_annotations,
get_generic_base,
)
from .exceptions import (
Expand Down Expand Up @@ -308,13 +309,6 @@ def _has_own_attribute(cls, attrib_name):
return attrib_name in cls.__dict__


def _get_annotations(cls):
"""
Get annotations for *cls*.
"""
return cls.__dict__.get("__annotations__", {})


def _collect_base_attrs(cls, taken_attr_names):
"""
Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.
Expand Down
5 changes: 5 additions & 0 deletions tests/test_3rd_party.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@

from hypothesis import given

from attr._compat import PY_3_14_PLUS

from .strategies import simple_classes


cloudpickle = pytest.importorskip("cloudpickle")


@pytest.mark.xfail(
PY_3_14_PLUS, reason="cloudpickle is currently broken on 3.14."
)
class TestCloudpickleCompat:
"""
Tests for compatibility with ``cloudpickle``.
Expand Down
11 changes: 11 additions & 0 deletions tests/test_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import attr

from attr._compat import PY_3_14_PLUS
from attr._make import _is_class_var
from attr.exceptions import UnannotatedAttributeError

Expand Down Expand Up @@ -584,6 +585,11 @@ class A:
assert typing.List[int] == attr.fields(A).b.type
assert typing.List[int] == attr.fields(A).c.type

@pytest.mark.skipif(
PY_3_14_PLUS,
reason="Forward references are changing a lot in 3.14. "
"Passes only for slots=True",
)
def test_self_reference(self, slots):
"""
References to self class using quotes can be resolved.
Expand All @@ -599,6 +605,11 @@ class A:
assert A == attr.fields(A).a.type
assert typing.Optional[A] == attr.fields(A).b.type

@pytest.mark.skipif(
PY_3_14_PLUS,
reason="Forward references are changing a lot in 3.14."
"Passes only for slots=True",
)
def test_forward_reference(self, slots):
"""
Forward references can be resolved.
Expand Down
6 changes: 4 additions & 2 deletions tests/test_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import attr

from attr import _config
from attr._compat import PY_3_8_PLUS, PY_3_10_PLUS
from attr._compat import PY_3_8_PLUS, PY_3_10_PLUS, PY_3_14_PLUS
from attr._make import (
Attribute,
Factory,
Expand Down Expand Up @@ -1859,9 +1859,11 @@ class C2(C):
assert [C2] == C.__subclasses__()

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

@attr.s(slots=True)
Expand Down
6 changes: 5 additions & 1 deletion tests/test_slots.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
Unit tests for slots-related functionality.
"""

import functools
import pickle
import weakref
Expand All @@ -14,7 +15,7 @@
import attr
import attrs

from attr._compat import PY_3_8_PLUS, PYPY
from attr._compat import PY_3_8_PLUS, PY_3_14_PLUS, PYPY


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


@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
@pytest.mark.xfail(
PY_3_14_PLUS, reason="3.14 returns weird annotation for cached_properies"
)
def test_slots_cached_property_infers_type():
"""
Infers type of cached property.
Expand Down

0 comments on commit f58093b

Please sign in to comment.