Skip to content

Commit 9e0e6ee

Browse files
authored
Remove 3.9 (#698)
1 parent 5449232 commit 9e0e6ee

File tree

12 files changed

+59
-388
lines changed

12 files changed

+59
-388
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616

1717
strategy:
1818
matrix:
19-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "pypy3.10"]
19+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "pypy3.10"]
2020
fail-fast: false
2121

2222
steps:

.github/workflows/zizmor.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@ jobs:
2121
uses: actions/checkout@v4
2222
with:
2323
persist-credentials: false
24-
- name: Install the latest version of uv
25-
uses: astral-sh/setup-uv@v4
26-
with:
27-
version: "latest"
24+
- uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0
2825
- name: Run zizmor 🌈
2926
run: uvx zizmor --format sarif . > results.sarif
3027
env:

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ Now you can make your changes locally.
7575
```shell
7676
$ just lint
7777
$ just test
78-
$ just --set python python3.9 test # Test on other versions
78+
$ just --set python python3.10 test # Test on other versions
7979
```
8080

8181
6. Write any necessary documentation, including updating the changelog (HISTORY.md). The docs can be built like so:

HISTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Our backwards-compatibility policy can be found [here](https://github.com/python
1515

1616
- Fix structuring of nested generic classes with stringified annotations.
1717
([#688](https://github.com/python-attrs/cattrs/pull/688))
18+
- Python 3.9 is no longer supported, as it is end-of-life. Use previous versions on this Python version.
19+
([#698](https://github.com/python-attrs/cattrs/pull/698))
1820

1921
## 25.3.0 (2025-10-07)
2022

Justfile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ sync version="":
66
uv sync {{ if python != '' { '-p ' + python } else if version != '' { '-p ' + version } else { '' } }} --all-groups --all-extras
77

88
lint:
9-
uv run -p python3.13 --group lint ruff check src/ tests bench
10-
uv run -p python3.13 --group lint black --check src tests docs/conf.py
9+
uv run -p python3.14 --group lint ruff check src/ tests bench
10+
uv run -p python3.14 --group lint black --check src tests docs/conf.py
1111

1212
test *args="-x --ff -n auto tests":
1313
uv run {{ if python != '' { '-p ' + python } else { '' } }} --all-extras --group test --group lint pytest {{args}}
1414

1515
testall:
16-
just python=python3.9 test
1716
just python=python3.10 test
1817
just python=pypy3.10 test
1918
just python=python3.11 test
2019
just python=python3.12 test
2120
just python=python3.13 test
21+
just python=python3.14 test
2222

2323
cov *args="-x --ff -n auto tests":
2424
uv run {{ if python != '' { '-p ' + python } else { '' } }} --all-extras --group test --group lint coverage run -m pytest {{args}}
@@ -27,12 +27,12 @@ cov *args="-x --ff -n auto tests":
2727
{{ if covcleanup == "true" { "@rm .coverage*" } else { "" } }}
2828

2929
covall:
30-
just python=python3.9 covcleanup=false cov
3130
just python=python3.10 covcleanup=false cov
3231
just python=pypy3.10 covcleanup=false cov
3332
just python=python3.11 covcleanup=false cov
3433
just python=python3.12 covcleanup=false cov
3534
just python=python3.13 covcleanup=false cov
35+
just python=python3.14 covcleanup=false cov
3636
uv run coverage combine
3737
uv run coverage report
3838
@rm .coverage*

docs/indepth.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ The new copy may be changed through the `copy` arguments, but will retain all ma
1919

2020
## Customizing Collection Unstructuring
2121

22-
```{important}
23-
This feature is supported for Python 3.9 and later.
24-
```
25-
2622
```{tip}
2723
See [](customizing.md#customizing-collections) for a more modern and more powerful way of customizing collection handling.
2824
```

pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,14 @@ dependencies = [
4444
"typing-extensions>=4.14.0",
4545
"exceptiongroup>=1.1.1; python_version < '3.11'",
4646
]
47-
requires-python = ">=3.9"
47+
requires-python = ">=3.10"
4848
readme = "README.md"
4949
license = {text = "MIT"}
5050
keywords = ["attrs", "serialization", "dataclasses"]
5151
classifiers = [
5252
"Development Status :: 5 - Production/Stable",
5353
"Intended Audience :: Developers",
5454
"License :: OSI Approved :: MIT License",
55-
"Programming Language :: Python :: 3.9",
5655
"Programming Language :: Python :: 3.10",
5756
"Programming Language :: Python :: 3.11",
5857
"Programming Language :: Python :: 3.12",
@@ -152,10 +151,14 @@ ignore = [
152151
"SIM300", # Yoda rocks in asserts
153152
"PGH003", # leave my type: ignores alone
154153
"B006", # mutable argument defaults
154+
"B905", # zip strictness, too noisy
155155
"DTZ001", # datetimes in tests
156156
"DTZ006", # datetimes in tests
157157
"UP006", # We support old typing constructs at runtime
158+
"UP007", # We support old typing constructs at runtime
158159
"UP035", # We support old typing constructs at runtime
160+
"UP038", # Dubious rule
161+
"UP045", # We support old typing constructs at runtime
159162
]
160163

161164
[tool.ruff.lint.pyupgrade]

src/cattrs/_compat.py

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,7 @@ def get_final_base(type) -> Optional[type]:
208208
OriginAbstractSet = AbcSet
209209
OriginMutableSet = AbcMutableSet
210210

211-
signature = _signature
212-
213-
if sys.version_info >= (3, 10):
214-
signature = partial(_signature, eval_str=True)
211+
signature = partial(_signature, eval_str=True)
215212

216213

217214
try:
@@ -269,7 +266,7 @@ def get_newtype_base(typ: Any) -> Optional[type]:
269266

270267
from typing import NotRequired, Required
271268

272-
elif sys.version_info >= (3, 10):
269+
else:
273270
from typing import _UnionGenericAlias
274271

275272
def is_union_type(obj):
@@ -291,27 +288,6 @@ def get_newtype_base(typ: Any) -> Optional[type]:
291288
else:
292289
from typing_extensions import NotRequired, Required
293290

294-
else:
295-
# 3.9
296-
from typing import _UnionGenericAlias
297-
298-
from typing_extensions import NotRequired, Required
299-
300-
def is_union_type(obj):
301-
return obj is Union or (
302-
isinstance(obj, _UnionGenericAlias) and obj.__origin__ is Union
303-
)
304-
305-
def get_newtype_base(typ: Any) -> Optional[type]:
306-
supertype = getattr(typ, "__supertype__", None)
307-
if (
308-
supertype is not None
309-
and getattr(typ, "__qualname__", "") == "NewType.<locals>.new_type"
310-
and typ.__module__ in ("typing", "typing_extensions")
311-
):
312-
return supertype
313-
return None
314-
315291

316292
def get_notrequired_base(type) -> Union[Any, NothingType]:
317293
if is_annotated(type):

src/cattrs/gen/typeddicts.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,12 @@
33
import re
44
import sys
55
from collections.abc import Mapping
6+
from inspect import get_annotations
67
from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar
78

89
from attrs import NOTHING, Attribute
910
from typing_extensions import _TypedDictMeta
1011

11-
try:
12-
from inspect import get_annotations
13-
14-
def get_annots(cl) -> dict[str, Any]:
15-
return get_annotations(cl, eval_str=True)
16-
17-
except ImportError:
18-
# https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
19-
def get_annots(cl) -> dict[str, Any]:
20-
return cl.__dict__.get("__annotations__", {})
21-
22-
2312
from .._compat import (
2413
get_full_type_hints,
2514
get_notrequired_base,
@@ -50,6 +39,10 @@ def get_annots(cl) -> dict[str, Any]:
5039
T = TypeVar("T")
5140

5241

42+
def get_annots(cl) -> dict[str, Any]:
43+
return get_annotations(cl, eval_str=True)
44+
45+
5346
def make_dict_unstructure_fn(
5447
cl: type[T],
5548
converter: BaseConverter,

src/cattrs/preconf/__init__.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
1-
import sys
21
from datetime import datetime
32
from enum import Enum
4-
from typing import Any, Callable, TypeVar, get_args
3+
from typing import Any, Callable, ParamSpec, TypeVar, get_args
54

65
from .._compat import is_subclass
76
from ..converters import Converter, UnstructureHook
87
from ..fns import identity
98

10-
if sys.version_info[:2] < (3, 10):
11-
from typing_extensions import ParamSpec
12-
else:
13-
from typing import ParamSpec
14-
159

1610
def validate_datetime(v, _):
1711
if not isinstance(v, datetime):

0 commit comments

Comments
 (0)