Skip to content

Commit d41775a

Browse files
zmievsaclaude
andauthored
Add Python 3.14 support (#345)
In Python 3.14, typing.Union is no longer a subtype of typing._BaseGenericAlias, which broke changelog model detection for annotations like list[Union[Model, None]]. Fix by extending GenericAliasUnionArgs to include the new union type on 3.14 and using it uniformly instead of the version-gated get_origin() check. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5989737 commit d41775a

File tree

6 files changed

+17
-15
lines changed

6 files changed

+17
-15
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ jobs:
3131
python-version: "3.12"
3232
- os: ubuntu-latest
3333
python-version: "3.13"
34+
- os: ubuntu-latest
35+
python-version: "3.14"
3436
- os: windows-latest
3537
python-version: "3.10"
3638
- os: macos-latest

.github/workflows/daily_tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
strategy:
1414
fail-fast: true
1515
matrix:
16-
python-version: ["3.10", "3.11", "3.12", "3.13"]
16+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
1717

1818
steps:
1919
- uses: actions/checkout@v6

cadwyn/_asts.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ast
22
import inspect
3+
import sys
34
from collections.abc import Callable
45
from enum import Enum, auto
56
from types import GenericAlias, LambdaType
@@ -29,6 +30,9 @@
2930
# instead of typing._GenericAlias.
3031
GenericAliasUnion = Union[GenericAlias, _BaseGenericAlias]
3132
GenericAliasUnionArgs = get_args(GenericAliasUnion)
33+
if sys.version_info >= (3, 14): # pragma: no cover
34+
# In Python 3.14, Union types are no longer subtypes of _BaseGenericAlias
35+
GenericAliasUnionArgs = (*GenericAliasUnionArgs, type(Union[int, str]))
3236

3337

3438
def get_fancy_repr(value: Any) -> Any:
@@ -38,6 +42,8 @@ def get_fancy_repr(value: Any) -> Any:
3842
return transform_collection(value)
3943
if isinstance(value, dict):
4044
return transform_dict(value)
45+
if isinstance(value, UnionType):
46+
return transform_union(value)
4147
if isinstance(value, GenericAliasUnionArgs):
4248
return transform_generic_alias(value)
4349
if value is None or value is NoneType:
@@ -48,8 +54,6 @@ def get_fancy_repr(value: Any) -> Any:
4854
return transform_enum(value)
4955
if isinstance(value, auto): # pragma: no cover # it works but we no longer use auto
5056
return transform_auto(value)
51-
if isinstance(value, UnionType):
52-
return transform_union(value) # pragma: no cover
5357
if isinstance(value, LambdaType) and _LambdaFunctionName == value.__name__:
5458
return transform_lambda(value)
5559
if inspect.isfunction(value):
@@ -105,7 +109,7 @@ def transform_auto(_: auto) -> Any: # pragma: no cover # it works but we no lon
105109
return PlainRepr("auto()")
106110

107111

108-
def transform_union(value: UnionType) -> Any: # pragma: no cover
112+
def transform_union(value: UnionType) -> Any:
109113
return "typing.Union[" + (", ".join(get_fancy_repr(a) for a in get_args(value))) + "]"
110114

111115

cadwyn/changelogs.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import sys
33
from enum import auto
44
from logging import getLogger
5-
from typing import Any, Literal, TypeVar, Union, cast, get_args, get_origin
5+
from typing import Any, Literal, TypeVar, Union, cast, get_args
66

77
from fastapi._compat import (
88
get_definitions,
@@ -138,13 +138,7 @@ def _get_affected_model_names(
138138

139139

140140
def _get_all_pydantic_models_from_generic(annotation: Any) -> list[type[BaseModel]]:
141-
# https://docs.python.org/3/whatsnew/3.14.html#typing
142-
if sys.version_info >= (3, 14): # pragma: no cover
143-
is_union = get_origin(annotation) is Union and not isinstance(annotation, type)
144-
else:
145-
is_union = isinstance(annotation, GenericAliasUnionArgs)
146-
147-
if not is_union:
141+
if not isinstance(annotation, GenericAliasUnionArgs):
148142
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
149143
return [annotation]
150144
else:

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ keywords = [
2222
"python311",
2323
"python312",
2424
"python313",
25+
"python314",
2526
]
2627
classifiers = [
2728
"Intended Audience :: Information Technology",
@@ -33,6 +34,7 @@ classifiers = [
3334
"Programming Language :: Python :: 3.11",
3435
"Programming Language :: Python :: 3.12",
3536
"Programming Language :: Python :: 3.13",
37+
"Programming Language :: Python :: 3.14",
3638
"Topic :: Internet",
3739
"Topic :: Software Development :: Libraries :: Application Frameworks",
3840
"Topic :: Software Development :: Libraries :: Python Modules",

tox.ini

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ envlist =
44
# When updating Python versions, use search-and-replace
55
# against the entire list of of versions
66
# to ensure consistency throughout this file.
7-
py{3.13, 3.12, 3.11, 3.10}
7+
py{3.14, 3.13, 3.12, 3.11, 3.10}
88
coverage_report
99
docs
1010
pyright
@@ -18,7 +18,7 @@ extras =
1818
package = wheel
1919
wheel_build_env = build_wheel
2020
depends =
21-
py{3.13, 3.12, 3.11, 3.10}: coverage_erase
21+
py{3.14, 3.13, 3.12, 3.11, 3.10}: coverage_erase
2222
commands = coverage run -m pytest {posargs}
2323

2424

@@ -30,7 +30,7 @@ commands = coverage erase
3030
[testenv:coverage_report]
3131
skip_install = true
3232
depends =
33-
py{3.13, 3.12, 3.11, 3.10}
33+
py{3.14, 3.13, 3.12, 3.11, 3.10}
3434
commands_pre =
3535
# Ignore the exit code of `coverage combine`
3636
# (in case the reports are already combined).

0 commit comments

Comments
 (0)