Drop Python 3.7 (#1340)
* Drop Python 3.7 * Add news fragment * update Ruff
This commit is contained in:
parent
5c3af47603
commit
a8e24b0186
|
@ -63,7 +63,7 @@ jobs:
|
||||||
V=${{ matrix.python-version }}
|
V=${{ matrix.python-version }}
|
||||||
DO_MYPY=1
|
DO_MYPY=1
|
||||||
|
|
||||||
if [[ "$V" == "3.7" || "$V" == "3.8" ]]; then
|
if [[ "$V" == "3.8" ]]; then
|
||||||
DO_MYPY=0
|
DO_MYPY=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -73,7 +73,6 @@ jobs:
|
||||||
uv pip install --system tox
|
uv pip install --system tox
|
||||||
|
|
||||||
- run: uv pip install --system tox-uv
|
- run: uv pip install --system tox-uv
|
||||||
if: matrix.python-version != '3.7'
|
|
||||||
|
|
||||||
- run: python -Im tox run -e ${{ env.TOX_PYTHON }}-mypy
|
- run: python -Im tox run -e ${{ env.TOX_PYTHON }}-mypy
|
||||||
if: env.DO_MYPY == '1'
|
if: env.DO_MYPY == '1'
|
||||||
|
|
|
@ -9,7 +9,7 @@ repos:
|
||||||
- id: black
|
- id: black
|
||||||
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.6.2
|
rev: v0.6.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Python 3.7 has been dropped.
|
|
@ -9,13 +9,12 @@ build-backend = "hatchling.build"
|
||||||
name = "attrs"
|
name = "attrs"
|
||||||
authors = [{ name = "Hynek Schlawack", email = "hs@ox.cx" }]
|
authors = [{ name = "Hynek Schlawack", email = "hs@ox.cx" }]
|
||||||
license = { text = "MIT" }
|
license = { text = "MIT" }
|
||||||
requires-python = ">=3.7"
|
requires-python = ">=3.8"
|
||||||
description = "Classes Without Boilerplate"
|
description = "Classes Without Boilerplate"
|
||||||
keywords = ["class", "attribute", "boilerplate"]
|
keywords = ["class", "attribute", "boilerplate"]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
"Programming Language :: Python :: 3.7",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
|
@ -26,7 +25,7 @@ classifiers = [
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
"Typing :: Typed",
|
"Typing :: Typed",
|
||||||
]
|
]
|
||||||
dependencies = ["importlib_metadata;python_version<'3.8'"]
|
dependencies = []
|
||||||
dynamic = ["version", "readme"]
|
dynamic = ["version", "readme"]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|
|
@ -5,11 +5,10 @@ Classes Without Boilerplate
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Callable
|
from typing import Callable, Protocol
|
||||||
|
|
||||||
from . import converters, exceptions, filters, setters, validators
|
from . import converters, exceptions, filters, setters, validators
|
||||||
from ._cmp import cmp_using
|
from ._cmp import cmp_using
|
||||||
from ._compat import Protocol
|
|
||||||
from ._config import get_run_validators, set_run_validators
|
from ._config import get_run_validators, set_run_validators
|
||||||
from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types
|
from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types
|
||||||
from ._make import (
|
from ._make import (
|
||||||
|
@ -85,10 +84,7 @@ def _make_getattr(mod_name: str) -> Callable:
|
||||||
msg = f"module {mod_name} has no attribute {name}"
|
msg = f"module {mod_name} has no attribute {name}"
|
||||||
raise AttributeError(msg)
|
raise AttributeError(msg)
|
||||||
|
|
||||||
try:
|
from importlib.metadata import metadata
|
||||||
from importlib.metadata import metadata
|
|
||||||
except ImportError:
|
|
||||||
from importlib_metadata import metadata
|
|
||||||
|
|
||||||
meta = metadata("attrs")
|
meta = metadata("attrs")
|
||||||
|
|
||||||
|
|
|
@ -76,29 +76,20 @@ NOTHING = _Nothing.NOTHING
|
||||||
# NOTE: Factory lies about its return type to make this possible:
|
# NOTE: Factory lies about its return type to make this possible:
|
||||||
# `x: List[int] # = Factory(list)`
|
# `x: List[int] # = Factory(list)`
|
||||||
# Work around mypy issue #4554 in the common case by using an overload.
|
# Work around mypy issue #4554 in the common case by using an overload.
|
||||||
if sys.version_info >= (3, 8):
|
from typing import Literal
|
||||||
from typing import Literal
|
|
||||||
@overload
|
|
||||||
def Factory(factory: Callable[[], _T]) -> _T: ...
|
|
||||||
@overload
|
|
||||||
def Factory(
|
|
||||||
factory: Callable[[Any], _T],
|
|
||||||
takes_self: Literal[True],
|
|
||||||
) -> _T: ...
|
|
||||||
@overload
|
|
||||||
def Factory(
|
|
||||||
factory: Callable[[], _T],
|
|
||||||
takes_self: Literal[False],
|
|
||||||
) -> _T: ...
|
|
||||||
|
|
||||||
else:
|
@overload
|
||||||
@overload
|
def Factory(factory: Callable[[], _T]) -> _T: ...
|
||||||
def Factory(factory: Callable[[], _T]) -> _T: ...
|
@overload
|
||||||
@overload
|
def Factory(
|
||||||
def Factory(
|
factory: Callable[[Any], _T],
|
||||||
factory: Union[Callable[[Any], _T], Callable[[], _T]],
|
takes_self: Literal[True],
|
||||||
takes_self: bool = ...,
|
) -> _T: ...
|
||||||
) -> _T: ...
|
@overload
|
||||||
|
def Factory(
|
||||||
|
factory: Callable[[], _T],
|
||||||
|
takes_self: Literal[False],
|
||||||
|
) -> _T: ...
|
||||||
|
|
||||||
In = TypeVar("In")
|
In = TypeVar("In")
|
||||||
Out = TypeVar("Out")
|
Out = TypeVar("Out")
|
||||||
|
|
|
@ -10,7 +10,6 @@ from typing import _GenericAlias
|
||||||
|
|
||||||
|
|
||||||
PYPY = platform.python_implementation() == "PyPy"
|
PYPY = platform.python_implementation() == "PyPy"
|
||||||
PY_3_8_PLUS = sys.version_info[:2] >= (3, 8)
|
|
||||||
PY_3_9_PLUS = sys.version_info[:2] >= (3, 9)
|
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_11_PLUS = sys.version_info[:2] >= (3, 11)
|
PY_3_11_PLUS = sys.version_info[:2] >= (3, 11)
|
||||||
|
@ -19,14 +18,6 @@ PY_3_13_PLUS = sys.version_info[:2] >= (3, 13)
|
||||||
PY_3_14_PLUS = sys.version_info[:2] >= (3, 14)
|
PY_3_14_PLUS = sys.version_info[:2] >= (3, 14)
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info < (3, 8):
|
|
||||||
try:
|
|
||||||
from typing_extensions import Protocol
|
|
||||||
except ImportError: # pragma: no cover
|
|
||||||
Protocol = object
|
|
||||||
else:
|
|
||||||
from typing import Protocol # noqa: F401
|
|
||||||
|
|
||||||
if PY_3_14_PLUS: # pragma: no cover
|
if PY_3_14_PLUS: # pragma: no cover
|
||||||
import annotationlib
|
import annotationlib
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ from operator import itemgetter
|
||||||
# having the thread-local in the globals here.
|
# having the thread-local in the globals here.
|
||||||
from . import _compat, _config, setters
|
from . import _compat, _config, setters
|
||||||
from ._compat import (
|
from ._compat import (
|
||||||
PY_3_8_PLUS,
|
|
||||||
PY_3_10_PLUS,
|
PY_3_10_PLUS,
|
||||||
PY_3_11_PLUS,
|
PY_3_11_PLUS,
|
||||||
_AnnotationExtractor,
|
_AnnotationExtractor,
|
||||||
|
@ -790,16 +789,11 @@ class _ClassBuilder:
|
||||||
):
|
):
|
||||||
names += ("__weakref__",)
|
names += ("__weakref__",)
|
||||||
|
|
||||||
if PY_3_8_PLUS:
|
cached_properties = {
|
||||||
cached_properties = {
|
name: cached_property.func
|
||||||
name: cached_property.func
|
for name, cached_property in cd.items()
|
||||||
for name, cached_property in cd.items()
|
if isinstance(cached_property, functools.cached_property)
|
||||||
if isinstance(cached_property, functools.cached_property)
|
}
|
||||||
}
|
|
||||||
else:
|
|
||||||
# `functools.cached_property` was introduced in 3.8.
|
|
||||||
# So can't be used before this.
|
|
||||||
cached_properties = {}
|
|
||||||
|
|
||||||
# Collect methods with a `__class__` reference that are shadowed in the new class.
|
# Collect methods with a `__class__` reference that are shadowed in the new class.
|
||||||
# To know to update them.
|
# To know to update them.
|
||||||
|
@ -2213,7 +2207,7 @@ def _attrs_to_init_script(
|
||||||
# If pre init method has arguments, pass same arguments as `__init__`.
|
# If pre init method has arguments, pass same arguments as `__init__`.
|
||||||
lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
|
lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
|
||||||
|
|
||||||
# Python 3.7 doesn't allow backslashes in f strings.
|
# Python <3.12 doesn't allow backslashes in f-strings.
|
||||||
NL = "\n "
|
NL = "\n "
|
||||||
return (
|
return (
|
||||||
f"""def {method_name}(self, {args}):
|
f"""def {method_name}(self, {args}):
|
||||||
|
|
|
@ -13,8 +13,6 @@ from hypothesis import strategies as st
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
from attr._compat import PY_3_8_PLUS
|
|
||||||
|
|
||||||
from .utils import make_class
|
from .utils import make_class
|
||||||
|
|
||||||
|
|
||||||
|
@ -189,9 +187,7 @@ def simple_classes(
|
||||||
cls_dict["__init__"] = init
|
cls_dict["__init__"] = init
|
||||||
|
|
||||||
bases = (object,)
|
bases = (object,)
|
||||||
if cached_property or (
|
if cached_property or (cached_property is None and cached_property_flag):
|
||||||
PY_3_8_PLUS and cached_property is None and cached_property_flag
|
|
||||||
):
|
|
||||||
|
|
||||||
class BaseWithCachedProperty:
|
class BaseWithCachedProperty:
|
||||||
@functools.cached_property
|
@functools.cached_property
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
@ -59,5 +61,5 @@ def test_attrsinstance_subclass_protocol():
|
||||||
It's possible to subclass AttrsInstance and Protocol at once.
|
It's possible to subclass AttrsInstance and Protocol at once.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Foo(attr.AttrsInstance, attr._compat.Protocol):
|
class Foo(attr.AttrsInstance, Protocol):
|
||||||
def attribute(self) -> int: ...
|
def attribute(self) -> int: ...
|
||||||
|
|
|
@ -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, PY_3_14_PLUS
|
from attr._compat import PY_3_10_PLUS, PY_3_14_PLUS
|
||||||
from attr._make import (
|
from attr._make import (
|
||||||
Attribute,
|
Attribute,
|
||||||
Factory,
|
Factory,
|
||||||
|
@ -1837,7 +1837,6 @@ 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.xfail(PY_3_14_PLUS, reason="Currently broken on nightly.")
|
@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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import sys
|
|
||||||
|
from importlib import metadata
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -8,12 +9,6 @@ import attr
|
||||||
import attrs
|
import attrs
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info < (3, 8):
|
|
||||||
import importlib_metadata as metadata
|
|
||||||
else:
|
|
||||||
from importlib import metadata
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="mod", params=(attr, attrs))
|
@pytest.fixture(name="mod", params=(attr, attrs))
|
||||||
def _mod(request):
|
def _mod(request):
|
||||||
return request.param
|
return request.param
|
||||||
|
|
|
@ -15,7 +15,7 @@ import pytest
|
||||||
import attr
|
import attr
|
||||||
import attrs
|
import attrs
|
||||||
|
|
||||||
from attr._compat import PY_3_8_PLUS, PY_3_14_PLUS, PYPY
|
from attr._compat import PY_3_14_PLUS, PYPY
|
||||||
|
|
||||||
|
|
||||||
# Pympler doesn't work on PyPy.
|
# Pympler doesn't work on PyPy.
|
||||||
|
@ -722,7 +722,6 @@ def test_slots_super_property_get_shortcut():
|
||||||
assert B(17).f == 289
|
assert B(17).f == 289
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_cached_property_allows_call():
|
def test_slots_cached_property_allows_call():
|
||||||
"""
|
"""
|
||||||
cached_property in slotted class allows call.
|
cached_property in slotted class allows call.
|
||||||
|
@ -739,7 +738,6 @@ def test_slots_cached_property_allows_call():
|
||||||
assert A(11).f == 11
|
assert A(11).f == 11
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_cached_property_class_does_not_have__dict__():
|
def test_slots_cached_property_class_does_not_have__dict__():
|
||||||
"""
|
"""
|
||||||
slotted class with cached property has no __dict__ attribute.
|
slotted class with cached property has no __dict__ attribute.
|
||||||
|
@ -757,7 +755,6 @@ def test_slots_cached_property_class_does_not_have__dict__():
|
||||||
assert "__dict__" not in dir(A)
|
assert "__dict__" not in dir(A)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_cached_property_works_on_frozen_isntances():
|
def test_slots_cached_property_works_on_frozen_isntances():
|
||||||
"""
|
"""
|
||||||
Infers type of cached property.
|
Infers type of cached property.
|
||||||
|
@ -774,7 +771,6 @@ def test_slots_cached_property_works_on_frozen_isntances():
|
||||||
assert A(x=1).f == 1
|
assert A(x=1).f == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
@pytest.mark.xfail(
|
@pytest.mark.xfail(
|
||||||
PY_3_14_PLUS, reason="3.14 returns weird annotation for cached_properies"
|
PY_3_14_PLUS, reason="3.14 returns weird annotation for cached_properies"
|
||||||
)
|
)
|
||||||
|
@ -794,7 +790,6 @@ def test_slots_cached_property_infers_type():
|
||||||
assert A.__annotations__ == {"x": int, "f": int}
|
assert A.__annotations__ == {"x": int, "f": int}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_cached_property_with_empty_getattr_raises_attribute_error_of_requested():
|
def test_slots_cached_property_with_empty_getattr_raises_attribute_error_of_requested():
|
||||||
"""
|
"""
|
||||||
Ensures error information is not lost.
|
Ensures error information is not lost.
|
||||||
|
@ -815,7 +810,6 @@ def test_slots_cached_property_with_empty_getattr_raises_attribute_error_of_requ
|
||||||
a.z
|
a.z
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_cached_property_raising_attributeerror():
|
def test_slots_cached_property_raising_attributeerror():
|
||||||
"""
|
"""
|
||||||
Ensures AttributeError raised by a property is preserved by __getattr__()
|
Ensures AttributeError raised by a property is preserved by __getattr__()
|
||||||
|
@ -854,7 +848,6 @@ def test_slots_cached_property_raising_attributeerror():
|
||||||
assert a.q == 2
|
assert a.q == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_cached_property_with_getattr_calls_getattr_for_missing_attributes():
|
def test_slots_cached_property_with_getattr_calls_getattr_for_missing_attributes():
|
||||||
"""
|
"""
|
||||||
Ensure __getattr__ implementation is maintained for non cached_properties.
|
Ensure __getattr__ implementation is maintained for non cached_properties.
|
||||||
|
@ -876,7 +869,6 @@ def test_slots_cached_property_with_getattr_calls_getattr_for_missing_attributes
|
||||||
assert a.z == "z"
|
assert a.z == "z"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_getattr_in_superclass__is_called_for_missing_attributes_when_cached_property_present():
|
def test_slots_getattr_in_superclass__is_called_for_missing_attributes_when_cached_property_present():
|
||||||
"""
|
"""
|
||||||
Ensure __getattr__ implementation is maintained in subclass.
|
Ensure __getattr__ implementation is maintained in subclass.
|
||||||
|
@ -900,7 +892,6 @@ def test_slots_getattr_in_superclass__is_called_for_missing_attributes_when_cach
|
||||||
assert b.z == "z"
|
assert b.z == "z"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_getattr_in_subclass_gets_superclass_cached_property():
|
def test_slots_getattr_in_subclass_gets_superclass_cached_property():
|
||||||
"""
|
"""
|
||||||
Ensure super() in __getattr__ is not broken through cached_property re-write.
|
Ensure super() in __getattr__ is not broken through cached_property re-write.
|
||||||
|
@ -931,7 +922,6 @@ def test_slots_getattr_in_subclass_gets_superclass_cached_property():
|
||||||
assert b.z == "z"
|
assert b.z == "z"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_sub_class_with_independent_cached_properties_both_work():
|
def test_slots_sub_class_with_independent_cached_properties_both_work():
|
||||||
"""
|
"""
|
||||||
Subclassing shouldn't break cached properties.
|
Subclassing shouldn't break cached properties.
|
||||||
|
@ -955,7 +945,6 @@ def test_slots_sub_class_with_independent_cached_properties_both_work():
|
||||||
assert B(1).g == 2
|
assert B(1).g == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_with_multiple_cached_property_subclasses_works():
|
def test_slots_with_multiple_cached_property_subclasses_works():
|
||||||
"""
|
"""
|
||||||
Multiple sub-classes shouldn't break cached properties.
|
Multiple sub-classes shouldn't break cached properties.
|
||||||
|
@ -991,7 +980,6 @@ def test_slots_with_multiple_cached_property_subclasses_works():
|
||||||
assert ab.h == "h"
|
assert ab.h == "h"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slotted_cached_property_can_access_super():
|
def test_slotted_cached_property_can_access_super():
|
||||||
"""
|
"""
|
||||||
Multiple sub-classes shouldn't break cached properties.
|
Multiple sub-classes shouldn't break cached properties.
|
||||||
|
@ -1010,7 +998,6 @@ def test_slotted_cached_property_can_access_super():
|
||||||
assert B(x=1).f == 2
|
assert B(x=1).f == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_sub_class_avoids_duplicated_slots():
|
def test_slots_sub_class_avoids_duplicated_slots():
|
||||||
"""
|
"""
|
||||||
Duplicating the slots is a waste of memory.
|
Duplicating the slots is a waste of memory.
|
||||||
|
@ -1034,7 +1021,6 @@ def test_slots_sub_class_avoids_duplicated_slots():
|
||||||
assert B.__slots__ == ()
|
assert B.__slots__ == ()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_sub_class_with_actual_slot():
|
def test_slots_sub_class_with_actual_slot():
|
||||||
"""
|
"""
|
||||||
A sub-class can have an explicit attrs field that replaces a cached property.
|
A sub-class can have an explicit attrs field that replaces a cached property.
|
||||||
|
@ -1056,7 +1042,6 @@ def test_slots_sub_class_with_actual_slot():
|
||||||
assert B.__slots__ == ()
|
assert B.__slots__ == ()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_cached_property_is_not_called_at_construction():
|
def test_slots_cached_property_is_not_called_at_construction():
|
||||||
"""
|
"""
|
||||||
A cached property function should only be called at property access point.
|
A cached property function should only be called at property access point.
|
||||||
|
@ -1077,7 +1062,6 @@ def test_slots_cached_property_is_not_called_at_construction():
|
||||||
assert call_count == 0
|
assert call_count == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_cached_property_repeat_call_only_once():
|
def test_slots_cached_property_repeat_call_only_once():
|
||||||
"""
|
"""
|
||||||
A cached property function should be called only once, on repeated attribute access.
|
A cached property function should be called only once, on repeated attribute access.
|
||||||
|
@ -1100,7 +1084,6 @@ def test_slots_cached_property_repeat_call_only_once():
|
||||||
assert call_count == 1
|
assert call_count == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_cached_property_called_independent_across_instances():
|
def test_slots_cached_property_called_independent_across_instances():
|
||||||
"""
|
"""
|
||||||
A cached property value should be specific to the given instance.
|
A cached property value should be specific to the given instance.
|
||||||
|
@ -1121,7 +1104,6 @@ def test_slots_cached_property_called_independent_across_instances():
|
||||||
assert obj_2.f == 2
|
assert obj_2.f == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
|
||||||
def test_slots_cached_properties_work_independently():
|
def test_slots_cached_properties_work_independently():
|
||||||
"""
|
"""
|
||||||
Multiple cached properties should work independently.
|
Multiple cached properties should work independently.
|
||||||
|
|
6
tox.ini
6
tox.ini
|
@ -2,7 +2,7 @@
|
||||||
min_version = 4
|
min_version = 4
|
||||||
env_list =
|
env_list =
|
||||||
pre-commit,
|
pre-commit,
|
||||||
py3{7,8,9,10,11,12,13}-tests,
|
py3{8,9,10,11,12,13}-tests,
|
||||||
py3{9,10,11,12,13}-mypy,
|
py3{9,10,11,12,13}-mypy,
|
||||||
pypy3,
|
pypy3,
|
||||||
pyright,
|
pyright,
|
||||||
|
@ -26,7 +26,7 @@ commands =
|
||||||
mypy: mypy tests/typing_example.py
|
mypy: mypy tests/typing_example.py
|
||||||
mypy: mypy src/attrs/__init__.pyi src/attr/__init__.pyi src/attr/_typing_compat.pyi src/attr/_version_info.pyi src/attr/converters.pyi src/attr/exceptions.pyi src/attr/filters.pyi src/attr/setters.pyi src/attr/validators.pyi
|
mypy: mypy src/attrs/__init__.pyi src/attr/__init__.pyi src/attr/_typing_compat.pyi src/attr/_version_info.pyi src/attr/converters.pyi src/attr/exceptions.pyi src/attr/filters.pyi src/attr/setters.pyi src/attr/validators.pyi
|
||||||
|
|
||||||
[testenv:py3{7,10,12}-tests]
|
[testenv:py3{8,10,12}-tests]
|
||||||
extras = cov
|
extras = cov
|
||||||
# Python 3.6+ has a number of compile-time warnings on invalid string escapes.
|
# Python 3.6+ has a number of compile-time warnings on invalid string escapes.
|
||||||
# PYTHONWARNINGS=d makes them visible during the tox run.
|
# PYTHONWARNINGS=d makes them visible during the tox run.
|
||||||
|
@ -41,7 +41,7 @@ commands = coverage run -m pytest {posargs:-n auto --dist loadfile}
|
||||||
# Keep base_python in-sync with .python-version-default
|
# Keep base_python in-sync with .python-version-default
|
||||||
base_python = py312
|
base_python = py312
|
||||||
# Keep depends in-sync with testenv above that has cov extra.
|
# Keep depends in-sync with testenv above that has cov extra.
|
||||||
depends = py3{7,10,12}
|
depends = py3{8,10,12}
|
||||||
skip_install = true
|
skip_install = true
|
||||||
deps = coverage[toml]>=5.3
|
deps = coverage[toml]>=5.3
|
||||||
commands =
|
commands =
|
||||||
|
|
Loading…
Reference in New Issue