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
This commit is contained in:
Hynek Schlawack 2024-08-06 13:37:35 +02:00 committed by GitHub
parent f520d9a89f
commit 7373d88f9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 35 additions and 10 deletions

View File

@ -0,0 +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.

View File

@ -15,6 +15,7 @@ PY_3_9_PLUS = sys.version_info[:2] >= (3, 9)
PY_3_10_PLUS = sys.version_info[:2] >= (3, 10) PY_3_10_PLUS = sys.version_info[:2] >= (3, 10)
PY_3_12_PLUS = sys.version_info[:2] >= (3, 12) PY_3_12_PLUS = sys.version_info[:2] >= (3, 12)
PY_3_13_PLUS = sys.version_info[:2] >= (3, 13) 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): if sys.version_info < (3, 8):
@ -25,6 +26,19 @@ if sys.version_info < (3, 8):
else: else:
from typing import Protocol # noqa: F401 from typing import Protocol # noqa: F401
if PY_3_14_PLUS: # pragma: no cover
import annotationlib
_get_annotations = annotationlib.get_annotations
else:
def _get_annotations(cls):
"""
Get annotations for *cls*.
"""
return cls.__dict__.get("__annotations__", {})
class _AnnotationExtractor: class _AnnotationExtractor:
""" """

View File

@ -23,6 +23,7 @@ from ._compat import (
PY_3_8_PLUS, PY_3_8_PLUS,
PY_3_10_PLUS, PY_3_10_PLUS,
_AnnotationExtractor, _AnnotationExtractor,
_get_annotations,
get_generic_base, get_generic_base,
) )
from .exceptions import ( from .exceptions import (
@ -308,13 +309,6 @@ def _has_own_attribute(cls, attrib_name):
return attrib_name in cls.__dict__ 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): def _collect_base_attrs(cls, taken_attr_names):
""" """
Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.

View File

@ -8,12 +8,17 @@ import pytest
from hypothesis import given from hypothesis import given
from attr._compat import PY_3_14_PLUS
from .strategies import simple_classes from .strategies import simple_classes
cloudpickle = pytest.importorskip("cloudpickle") cloudpickle = pytest.importorskip("cloudpickle")
@pytest.mark.xfail(
PY_3_14_PLUS, reason="cloudpickle is currently broken on 3.14."
)
class TestCloudpickleCompat: class TestCloudpickleCompat:
""" """
Tests for compatibility with ``cloudpickle``. Tests for compatibility with ``cloudpickle``.

View File

@ -12,6 +12,7 @@ import pytest
import attr import attr
from attr._compat import PY_3_14_PLUS
from attr._make import _is_class_var from attr._make import _is_class_var
from attr.exceptions import UnannotatedAttributeError from attr.exceptions import UnannotatedAttributeError
@ -588,6 +589,8 @@ class TestAnnotations:
""" """
References to self class using quotes can be resolved. References to self class using quotes can be resolved.
""" """
if PY_3_14_PLUS and not slots:
pytest.xfail("References are changing a lot in 3.14.")
@attr.s(slots=slots, auto_attribs=True) @attr.s(slots=slots, auto_attribs=True)
class A: class A:
@ -603,6 +606,8 @@ class TestAnnotations:
""" """
Forward references can be resolved. Forward references can be resolved.
""" """
if PY_3_14_PLUS and not slots:
pytest.xfail("Forward references are changing a lot in 3.14.")
@attr.s(slots=slots, auto_attribs=True) @attr.s(slots=slots, auto_attribs=True)
class A: class A:

View File

@ -21,7 +21,7 @@ from hypothesis.strategies import booleans, integers, lists, sampled_from, text
import attr import attr
from attr import _config 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 ( from attr._make import (
Attribute, Attribute,
Factory, Factory,
@ -1838,9 +1838,11 @@ class TestClassBuilder:
assert [C2] == C.__subclasses__() assert [C2] == C.__subclasses__()
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") @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): 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) @attr.s(slots=True)

View File

@ -3,6 +3,7 @@
""" """
Unit tests for slots-related functionality. Unit tests for slots-related functionality.
""" """
import functools import functools
import pickle import pickle
import weakref import weakref
@ -14,7 +15,7 @@ import pytest
import attr import attr
import attrs 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. # Pympler doesn't work on PyPy.
@ -774,6 +775,9 @@ def test_slots_cached_property_works_on_frozen_isntances():
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") @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(): def test_slots_cached_property_infers_type():
""" """
Infers type of cached property. Infers type of cached property.