Skip to content

Commit a8e24b0

Browse files
authored
Drop Python 3.7 (#1340)
* Drop Python 3.7 * Add news fragment * update Ruff
1 parent 5c3af47 commit a8e24b0

File tree

14 files changed

+37
-92
lines changed

14 files changed

+37
-92
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ jobs:
6363
V=${{ matrix.python-version }}
6464
DO_MYPY=1
6565
66-
if [[ "$V" == "3.7" || "$V" == "3.8" ]]; then
66+
if [[ "$V" == "3.8" ]]; then
6767
DO_MYPY=0
6868
fi
6969
@@ -73,7 +73,6 @@ jobs:
7373
uv pip install --system tox
7474
7575
- run: uv pip install --system tox-uv
76-
if: matrix.python-version != '3.7'
7776

7877
- run: python -Im tox run -e ${{ env.TOX_PYTHON }}-mypy
7978
if: env.DO_MYPY == '1'

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repos:
99
- id: black
1010

1111
- repo: https://github.com/astral-sh/ruff-pre-commit
12-
rev: v0.6.2
12+
rev: v0.6.3
1313
hooks:
1414
- id: ruff
1515
args: [--fix, --exit-non-zero-on-fix]

changelog.d/1340.breaking.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Python 3.7 has been dropped.

pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@ build-backend = "hatchling.build"
99
name = "attrs"
1010
authors = [{ name = "Hynek Schlawack", email = "hs@ox.cx" }]
1111
license = { text = "MIT" }
12-
requires-python = ">=3.7"
12+
requires-python = ">=3.8"
1313
description = "Classes Without Boilerplate"
1414
keywords = ["class", "attribute", "boilerplate"]
1515
classifiers = [
1616
"Development Status :: 5 - Production/Stable",
1717
"License :: OSI Approved :: MIT License",
18-
"Programming Language :: Python :: 3.7",
1918
"Programming Language :: Python :: 3.8",
2019
"Programming Language :: Python :: 3.9",
2120
"Programming Language :: Python :: 3.10",
@@ -26,7 +25,7 @@ classifiers = [
2625
"Programming Language :: Python :: Implementation :: PyPy",
2726
"Typing :: Typed",
2827
]
29-
dependencies = ["importlib_metadata;python_version<'3.8'"]
28+
dependencies = []
3029
dynamic = ["version", "readme"]
3130

3231
[project.optional-dependencies]

src/attr/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
"""
66

77
from functools import partial
8-
from typing import Callable
8+
from typing import Callable, Protocol
99

1010
from . import converters, exceptions, filters, setters, validators
1111
from ._cmp import cmp_using
12-
from ._compat import Protocol
1312
from ._config import get_run_validators, set_run_validators
1413
from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types
1514
from ._make import (
@@ -85,10 +84,7 @@ def __getattr__(name: str) -> str:
8584
msg = f"module {mod_name} has no attribute {name}"
8685
raise AttributeError(msg)
8786

88-
try:
89-
from importlib.metadata import metadata
90-
except ImportError:
91-
from importlib_metadata import metadata
87+
from importlib.metadata import metadata
9288

9389
meta = metadata("attrs")
9490

src/attr/__init__.pyi

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -76,29 +76,20 @@ NOTHING = _Nothing.NOTHING
7676
# NOTE: Factory lies about its return type to make this possible:
7777
# `x: List[int] # = Factory(list)`
7878
# Work around mypy issue #4554 in the common case by using an overload.
79-
if sys.version_info >= (3, 8):
80-
from typing import Literal
81-
@overload
82-
def Factory(factory: Callable[[], _T]) -> _T: ...
83-
@overload
84-
def Factory(
85-
factory: Callable[[Any], _T],
86-
takes_self: Literal[True],
87-
) -> _T: ...
88-
@overload
89-
def Factory(
90-
factory: Callable[[], _T],
91-
takes_self: Literal[False],
92-
) -> _T: ...
79+
from typing import Literal
9380

94-
else:
95-
@overload
96-
def Factory(factory: Callable[[], _T]) -> _T: ...
97-
@overload
98-
def Factory(
99-
factory: Union[Callable[[Any], _T], Callable[[], _T]],
100-
takes_self: bool = ...,
101-
) -> _T: ...
81+
@overload
82+
def Factory(factory: Callable[[], _T]) -> _T: ...
83+
@overload
84+
def Factory(
85+
factory: Callable[[Any], _T],
86+
takes_self: Literal[True],
87+
) -> _T: ...
88+
@overload
89+
def Factory(
90+
factory: Callable[[], _T],
91+
takes_self: Literal[False],
92+
) -> _T: ...
10293

10394
In = TypeVar("In")
10495
Out = TypeVar("Out")

src/attr/_compat.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111

1212
PYPY = platform.python_implementation() == "PyPy"
13-
PY_3_8_PLUS = sys.version_info[:2] >= (3, 8)
1413
PY_3_9_PLUS = sys.version_info[:2] >= (3, 9)
1514
PY_3_10_PLUS = sys.version_info[:2] >= (3, 10)
1615
PY_3_11_PLUS = sys.version_info[:2] >= (3, 11)
@@ -19,14 +18,6 @@
1918
PY_3_14_PLUS = sys.version_info[:2] >= (3, 14)
2019

2120

22-
if sys.version_info < (3, 8):
23-
try:
24-
from typing_extensions import Protocol
25-
except ImportError: # pragma: no cover
26-
Protocol = object
27-
else:
28-
from typing import Protocol # noqa: F401
29-
3021
if PY_3_14_PLUS: # pragma: no cover
3122
import annotationlib
3223

src/attr/_make.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
# having the thread-local in the globals here.
2121
from . import _compat, _config, setters
2222
from ._compat import (
23-
PY_3_8_PLUS,
2423
PY_3_10_PLUS,
2524
PY_3_11_PLUS,
2625
_AnnotationExtractor,
@@ -790,16 +789,11 @@ def _create_slots_class(self):
790789
):
791790
names += ("__weakref__",)
792791

793-
if PY_3_8_PLUS:
794-
cached_properties = {
795-
name: cached_property.func
796-
for name, cached_property in cd.items()
797-
if isinstance(cached_property, functools.cached_property)
798-
}
799-
else:
800-
# `functools.cached_property` was introduced in 3.8.
801-
# So can't be used before this.
802-
cached_properties = {}
792+
cached_properties = {
793+
name: cached_property.func
794+
for name, cached_property in cd.items()
795+
if isinstance(cached_property, functools.cached_property)
796+
}
803797

804798
# Collect methods with a `__class__` reference that are shadowed in the new class.
805799
# To know to update them.
@@ -2213,7 +2207,7 @@ def _attrs_to_init_script(
22132207
# If pre init method has arguments, pass same arguments as `__init__`.
22142208
lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
22152209

2216-
# Python 3.7 doesn't allow backslashes in f strings.
2210+
# Python <3.12 doesn't allow backslashes in f-strings.
22172211
NL = "\n "
22182212
return (
22192213
f"""def {method_name}(self, {args}):

tests/strategies.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313

1414
import attr
1515

16-
from attr._compat import PY_3_8_PLUS
17-
1816
from .utils import make_class
1917

2018

@@ -189,9 +187,7 @@ def init(self, *args, **kwargs):
189187
cls_dict["__init__"] = init
190188

191189
bases = (object,)
192-
if cached_property or (
193-
PY_3_8_PLUS and cached_property is None and cached_property_flag
194-
):
190+
if cached_property or (cached_property is None and cached_property_flag):
195191

196192
class BaseWithCachedProperty:
197193
@functools.cached_property

tests/test_compat.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import types
44

5+
from typing import Protocol
6+
57
import pytest
68

79
import attr
@@ -59,5 +61,5 @@ def test_attrsinstance_subclass_protocol():
5961
It's possible to subclass AttrsInstance and Protocol at once.
6062
"""
6163

62-
class Foo(attr.AttrsInstance, attr._compat.Protocol):
64+
class Foo(attr.AttrsInstance, Protocol):
6365
def attribute(self) -> int: ...

tests/test_make.py

Lines changed: 1 addition & 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, PY_3_14_PLUS
24+
from attr._compat import PY_3_10_PLUS, PY_3_14_PLUS
2525
from attr._make import (
2626
Attribute,
2727
Factory,
@@ -1837,7 +1837,6 @@ class C2(C):
18371837

18381838
assert [C2] == C.__subclasses__()
18391839

1840-
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
18411840
@pytest.mark.xfail(PY_3_14_PLUS, reason="Currently broken on nightly.")
18421841
def test_no_references_to_original_when_using_cached_property(self):
18431842
"""

tests/test_packaging.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
# SPDX-License-Identifier: MIT
22

3-
import sys
3+
4+
from importlib import metadata
45

56
import pytest
67

78
import attr
89
import attrs
910

1011

11-
if sys.version_info < (3, 8):
12-
import importlib_metadata as metadata
13-
else:
14-
from importlib import metadata
15-
16-
1712
@pytest.fixture(name="mod", params=(attr, attrs))
1813
def _mod(request):
1914
return request.param

0 commit comments

Comments
 (0)