Skip to content

Commit b298c98

Browse files
authored
Merge pull request #85 from noamkush/python-3.14-partial-fix
Fixes for Python 3.14.
2 parents 6c1af33 + 0433e61 commit b298c98

File tree

8 files changed

+69
-96
lines changed

8 files changed

+69
-96
lines changed

.github/workflows/python-test.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,11 @@ jobs:
5252
runs-on: ubuntu-latest
5353
strategy:
5454
matrix:
55-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
55+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
5656
pydantic-version: ["1.10.*", "2.*"]
57+
exclude:
58+
- python-version: "3.14"
59+
pydantic-version: "1.10.*"
5760

5861
services:
5962
postgres:

django_pydantic_field/compat/django.py

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def unwrap(cls, value):
112112
# This is a fallback for Python < 3.8, please be careful with that
113113
return origin[unwrapped_args]
114114
except TypeError:
115-
return GenericAlias(origin, unwrapped_args)
115+
return types.GenericAlias(origin, unwrapped_args)
116116

117117
def __eq__(self, other):
118118
if isinstance(other, GenericTypes):
@@ -297,18 +297,14 @@ def serialize(self):
297297

298298
AnnotatedAlias = te._AnnotatedAlias
299299

300-
if sys.version_info >= (3, 9):
301-
GenericAlias = types.GenericAlias
302-
GenericTypes: ty.Tuple[ty.Any, ...] = (
303-
GenericAlias,
300+
if sys.version_info >= (3, 14):
301+
GenericTypes: ty.Tuple[ty.Any, ...] = (types.GenericAlias, type(ty.List[int]), type(ty.List), ty.Union)
302+
else:
303+
GenericTypes = (
304+
types.GenericAlias,
304305
type(ty.List[int]),
305306
type(ty.List),
306307
)
307-
else:
308-
# types.GenericAlias is missing, meaning python version < 3.9,
309-
# which has a different inheritance models for typed generics
310-
GenericAlias = type(ty.List[int]) # noqa
311-
GenericTypes = GenericAlias, type(ty.List) # noqa
312308

313309

314310
# BaseContainerSerializer *must be* registered after all specialized container serializers
@@ -325,23 +321,25 @@ def serialize(self):
325321

326322
MigrationWriter.register_serializer(ty.ForwardRef, TypingSerializer)
327323
MigrationWriter.register_serializer(type(ty.Union), TypingSerializer) # type: ignore
324+
MigrationWriter.register_serializer(ty._SpecialForm, TypingSerializer) # type: ignore
325+
328326

327+
UnionType = types.UnionType
329328

330-
if sys.version_info >= (3, 10):
331-
UnionType = types.UnionType
332329

333-
class UnionTypeSerializer(BaseSerializer):
334-
value: UnionType
330+
class UnionTypeSerializer(BaseSerializer):
331+
value: UnionType
332+
333+
def serialize(self):
334+
imports = set()
335+
if isinstance(self.value, (type(ty.Union), types.UnionType)): # type: ignore
336+
imports.add("import typing")
335337

336-
def serialize(self):
337-
imports = set()
338-
if isinstance(self.value, (type(ty.Union), types.UnionType)): # type: ignore
339-
imports.add("import typing")
338+
for arg in get_args(self.value):
339+
_, arg_imports = serializer_factory(arg).serialize()
340+
imports.update(arg_imports)
340341

341-
for arg in get_args(self.value):
342-
_, arg_imports = serializer_factory(arg).serialize()
343-
imports.update(arg_imports)
342+
return repr(self.value), imports
344343

345-
return repr(self.value), imports
346344

347-
MigrationWriter.register_serializer(UnionType, UnionTypeSerializer)
345+
MigrationWriter.register_serializer(UnionType, UnionTypeSerializer)

django_pydantic_field/v2/utils.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,22 @@
99
if ty.TYPE_CHECKING:
1010
from collections.abc import Mapping
1111

12+
get_annotations: ty.Callable[[ty.Any], dict[str, ty.Any]]
1213

13-
def get_annotated_type(obj, field, default=None) -> ty.Any:
14-
try:
14+
try:
15+
from annotationlib import get_annotations # Python >= 3.14
16+
except ImportError:
17+
18+
def get_annotations(obj: ty.Any) -> dict[str, ty.Any]:
1519
if isinstance(obj, type):
16-
annotations = obj.__dict__["__annotations__"]
20+
return obj.__dict__["__annotations__"]
1721
else:
18-
annotations = obj.__annotations__
22+
return obj.__annotations__
23+
24+
25+
def get_annotated_type(obj, field, default=None) -> ty.Any:
26+
try:
27+
annotations = get_annotations(obj)
1928

2029
return annotations[field]
2130
except (AttributeError, KeyError):
@@ -48,12 +57,5 @@ def get_origin_type(cls: type):
4857
return cls
4958

5059

51-
if sys.version_info >= (3, 9):
52-
53-
def evaluate_forward_ref(ref: ty.ForwardRef, ns: Mapping[str, ty.Any]) -> ty.Any:
54-
return ref._evaluate(dict(ns), {}, recursive_guard=frozenset())
55-
56-
else:
57-
58-
def evaluate_forward_ref(ref: ty.ForwardRef, ns: Mapping[str, ty.Any]) -> ty.Any:
59-
return ref._evaluate(dict(ns), {})
60+
def evaluate_forward_ref(ref: ty.ForwardRef, ns: Mapping[str, ty.Any]) -> ty.Any:
61+
return ref._evaluate(dict(ns), {}, recursive_guard=frozenset())

pyproject.toml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,14 @@ classifiers = [
3535
"Programming Language :: Python",
3636
"Programming Language :: Python :: 3",
3737
"Programming Language :: Python :: 3 :: Only",
38-
"Programming Language :: Python :: 3.8",
39-
"Programming Language :: Python :: 3.9",
4038
"Programming Language :: Python :: 3.10",
4139
"Programming Language :: Python :: 3.11",
4240
"Programming Language :: Python :: 3.12",
4341
"Programming Language :: Python :: 3.13",
42+
"Programming Language :: Python :: 3.14",
4443
]
4544

46-
requires-python = ">=3.8"
45+
requires-python = ">=3.10"
4746
dependencies = [
4847
"pydantic>=1.10,<3",
4948
"django>=3.1,<6",
@@ -61,8 +60,8 @@ dev = [
6160
"pre-commit",
6261
"pytest~=7.4",
6362
"djangorestframework>=3.11,<4",
64-
"django-stubs[compatible-mypy]~=4.2",
65-
"djangorestframework-stubs[compatible-mypy]~=3.14",
63+
"django-stubs[compatible-mypy]~=5.2.3",
64+
"djangorestframework-stubs[compatible-mypy]~=3.16.3",
6665
"pytest-django>=4.5,<6",
6766
]
6867
test = [
@@ -73,8 +72,7 @@ test = [
7372
"syrupy>=3,<5",
7473
]
7574
ci = [
76-
'psycopg[binary]>=3.1,<4; python_version>="3.9"',
77-
'psycopg2-binary>=2.7,<3; python_version<"3.9"',
75+
'psycopg[binary]>=3.1,<4',
7876
"mysqlclient>=2.1",
7977
]
8078

tests/test_fields.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ def test_field_serialization(field):
147147
_test_field_serialization(field)
148148

149149

150-
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Built-in type subscription supports only in 3.9+")
151150
@pytest.mark.parametrize(
152151
"field_factory",
153152
[
@@ -161,30 +160,11 @@ def test_field_builtin_annotations_serialization(field_factory):
161160
_test_field_serialization(field_factory())
162161

163162

164-
@pytest.mark.skipif(sys.version_info < (3, 10), reason="Union type syntax supported only in 3.10+")
165163
def test_field_union_type_serialization():
166164
field = fields.PydanticSchemaField(schema=(InnerSchema | None), null=True, default=None)
167165
_test_field_serialization(field)
168166

169167

170-
@pytest.mark.skipif(sys.version_info >= (3, 9), reason="Should test against builtin generic types")
171-
@pytest.mark.parametrize(
172-
"field",
173-
[
174-
fields.PydanticSchemaField(schema=ty.List[InnerSchema], default=list),
175-
fields.PydanticSchemaField(schema=ty.Dict[str, InnerSchema], default=dict),
176-
fields.PydanticSchemaField(schema=ty.Sequence[InnerSchema], default=list),
177-
fields.PydanticSchemaField(schema=ty.Mapping[str, InnerSchema], default=dict),
178-
],
179-
)
180-
def test_field_typing_annotations_serialization(field):
181-
_test_field_serialization(field)
182-
183-
184-
@pytest.mark.skipif(
185-
sys.version_info < (3, 9),
186-
reason="Typing-to-builtin migrations is reasonable only on py >= 3.9",
187-
)
188168
@pytest.mark.parametrize(
189169
"old_field, new_field",
190170
[

tests/test_migration_serializers.py

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,27 @@
66
import pytest
77

88
import django_pydantic_field
9+
910
try:
1011
from django_pydantic_field.compat.django import GenericContainer
1112
except ImportError:
1213
from django_pydantic_field._migration_serializers import GenericContainer # noqa
1314

14-
if sys.version_info < (3, 9):
15-
test_types = [
16-
str,
17-
list,
18-
t.List[str],
19-
t.Union[te.Literal["foo"], t.List[str]],
20-
t.List[t.Union[int, bool]],
21-
t.Tuple[t.List[te.Literal[1]], t.Union[str, te.Literal["foo"]]],
22-
t.ForwardRef("str"),
23-
]
24-
else:
25-
test_types = [
26-
str,
27-
list,
28-
list[str],
29-
t.Union[t.Literal["foo"], list[str]],
30-
list[t.Union[int, bool]],
31-
tuple[list[t.Literal[1]], t.Union[str, t.Literal["foo"]]],
32-
t.ForwardRef("str"),
33-
]
15+
try:
16+
import annotationlib
17+
except ImportError:
18+
annotationlib = None
19+
20+
test_types = [
21+
str,
22+
list,
23+
list[str],
24+
t.Literal["foo"],
25+
t.Union[t.Literal["foo"], list[str]],
26+
list[t.Union[int, bool]],
27+
tuple[list[t.Literal[1]], t.Union[str, t.Literal["foo"]]],
28+
t.ForwardRef("str"),
29+
]
3430

3531

3632
@pytest.mark.parametrize("raw_type", test_types)
@@ -42,6 +38,8 @@ def test_wrap_unwrap_idempotent(raw_type):
4238
@pytest.mark.parametrize("raw_type", test_types)
4339
def test_serialize_eval_idempotent(raw_type):
4440
raw_type = GenericContainer.wrap(raw_type)
45-
expression, _ = MigrationWriter.serialize(GenericContainer.wrap(raw_type))
46-
imports = dict(typing=t, typing_extensions=te, django_pydantic_field=django_pydantic_field)
41+
expression, _ = MigrationWriter.serialize(raw_type)
42+
imports = dict(
43+
typing=t, typing_extensions=te, django_pydantic_field=django_pydantic_field, annotationlib=annotationlib
44+
)
4745
assert eval(expression, imports) == raw_type

tests/v1/test_base.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ def test_concrete_types(type_, encoded, decoded):
107107
assert decoder.decode(existing_encoded) == decoded
108108

109109

110-
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Should test against builtin generic types")
111110
@pytest.mark.parametrize(
112111
"type_factory, encoded, decoded",
113112
[

tests/v2/test_types.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,24 @@
1-
import sys
21
import pydantic
32
import pytest
43
import typing as ty
54

65
from ..conftest import InnerSchema, SampleDataclass
76

87
types = pytest.importorskip("django_pydantic_field.v2.types")
9-
skip_unsupported_builtin_subscription = pytest.mark.skipif(
10-
sys.version_info < (3, 9),
11-
reason="Built-in type subscription supports only in 3.9+",
12-
)
138

149

1510
# fmt: off
1611
@pytest.mark.parametrize(
1712
"ctor, args, kwargs",
1813
[
19-
pytest.param(types.SchemaAdapter, ["list[int]", None, None, None], {}, marks=skip_unsupported_builtin_subscription),
20-
pytest.param(types.SchemaAdapter, ["list[int]", {"strict": True}, None, None], {}, marks=skip_unsupported_builtin_subscription),
14+
pytest.param(types.SchemaAdapter, ["list[int]", None, None, None], {}),
15+
pytest.param(types.SchemaAdapter, ["list[int]", {"strict": True}, None, None], {}),
2116
(types.SchemaAdapter, [ty.List[int], None, None, None], {}),
2217
(types.SchemaAdapter, [ty.List[int], {"strict": True}, None, None], {}),
2318
(types.SchemaAdapter, [None, None, InnerSchema, "stub_int"], {}),
2419
(types.SchemaAdapter, [None, None, SampleDataclass, "stub_int"], {}),
25-
pytest.param(types.SchemaAdapter.from_type, ["list[int]"], {}, marks=skip_unsupported_builtin_subscription),
26-
pytest.param(types.SchemaAdapter.from_type, ["list[int]", {"strict": True}], {}, marks=skip_unsupported_builtin_subscription),
20+
pytest.param(types.SchemaAdapter.from_type, ["list[int]"], {}),
21+
pytest.param(types.SchemaAdapter.from_type, ["list[int]", {"strict": True}], {}),
2722
(types.SchemaAdapter.from_type, [ty.List[int]], {}),
2823
(types.SchemaAdapter.from_type, [ty.List[int], {"strict": True}], {}),
2924
(types.SchemaAdapter.from_annotation, [InnerSchema, "stub_int"], {}),

0 commit comments

Comments
 (0)