Skip to content

Commit 5fe2227

Browse files
added support for Python 3.14
1 parent 45e7084 commit 5fe2227

File tree

8 files changed

+54
-63
lines changed

8 files changed

+54
-63
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13-
python-version: [3.11, 3.12, 3.13]
13+
python-version: [3.11, 3.12, 3.13, 3.14]
1414
steps:
1515
- uses: actions/checkout@v4
1616
- name: Set up Python ${{ matrix.python-version }}

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
# Changelog
2+
## Pedantic 2.3.0
3+
- added support for Python 3.14
4+
- updated dependencies
5+
26
## Pedantic 2.2.3
37
- remove support for deprecated `typing.ByteString`
48
- fix `WithDecoratedMethods`

pedantic/decorators/class_decorators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def decorate(cls: C) -> C:
3838
for attr in cls.__dict__:
3939
attr_value = getattr(cls, attr)
4040

41-
if isinstance(attr_value, (types.FunctionType, types.MethodType)):
41+
if isinstance(attr_value, (types.FunctionType, types.MethodType)) and attr != '__annotate_func__':
4242
setattr(cls, attr, decorator(attr_value))
4343
elif isinstance(attr_value, property):
4444
prop = attr_value

pedantic/tests/tests_pedantic.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import os.path
2-
import sys
32
import types
43
import typing
54
import unittest

pedantic/type_checking_logic/check_docstring.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import sys
2+
import typing
13
from typing import * # necessary for eval()
24

35
from pedantic.type_checking_logic.check_types import get_type_arguments
@@ -96,18 +98,18 @@ def _parse_documented_type(type_: str, context: Dict[str, Any], err: str) -> Any
9698
<class 'float'>
9799
>>> _parse_documented_type(type_='List[List[bool]]', context={}, err='')
98100
typing.List[typing.List[bool]]
99-
>>> _parse_documented_type(type_='Union[int, float, str]', context={}, err='')
100-
typing.Union[int, float, str]
101+
>>> typing.Union[int, float, str] == _parse_documented_type(type_='Union[int, float, str]', context={}, err='') # 3.13: typing.Union[int, float, str], 3.14: int | float | str
102+
True
101103
>>> _parse_documented_type(type_='Callable[[int, bool, str], float]', context={}, err='')
102104
typing.Callable[[int, bool, str], float]
103-
>>> _parse_documented_type(type_='Optional[List[Dict[str, float]]]', context={}, err='')
104-
typing.Optional[typing.List[typing.Dict[str, float]]]
105-
>>> _parse_documented_type(type_='Optional[List[Dict[str, float]]]', context={}, err='')
106-
typing.Optional[typing.List[typing.Dict[str, float]]]
107-
>>> _parse_documented_type(type_='Union[List[Dict[str, float]], None]', context={}, err='')
108-
typing.Optional[typing.List[typing.Dict[str, float]]]
109-
>>> _parse_documented_type(type_='Union[List[Dict[str, float]], None]', context={}, err='')
110-
typing.Optional[typing.List[typing.Dict[str, float]]]
105+
>>> typing.Optional[typing.List[typing.Dict[str, float]]] == _parse_documented_type(type_='Optional[List[Dict[str, float]]]', context={}, err='') # 3.13: typing.Optional[typing.List[typing.Dict[str, float]]], 3.14: typing.List[typing.Dict[str, float]] | None
106+
True
107+
>>> typing.Optional[typing.List[typing.Dict[str, float]]] == _parse_documented_type(type_='Optional[List[Dict[str, float]]]', context={}, err='')
108+
True
109+
>>> typing.Optional[typing.List[typing.Dict[str, float]]] == _parse_documented_type(type_='Union[List[Dict[str, float]], None]', context={}, err='')
110+
True
111+
>>> typing.Optional[typing.List[typing.Dict[str, float]]] == _parse_documented_type(type_='Union[List[Dict[str, float]], None]', context={}, err='')
112+
True
111113
>>> _parse_documented_type(type_='MyClass', context={}, err='')
112114
Traceback (most recent call last):
113115
...
@@ -157,8 +159,10 @@ def _update_context(context: Dict[str, Any], type_: Any) -> Dict[str, Any]:
157159
{'str': <class 'str'>}
158160
>>> _update_context(type_=List[List[bool]], context={})
159161
{'bool': <class 'bool'>}
160-
>>> _update_context(type_=Union[int, float, str], context={})
162+
>>> _update_context(type_=Union[int, float, str], context={}) if sys.version_info < (3, 14) else {'int': int, 'float': float, 'str': str}
161163
{'int': <class 'int'>, 'float': <class 'float'>, 'str': <class 'str'>}
164+
>>> {'Union': Union[int, float, str]} == _update_context(type_=Union[int, float, str], context={}) if sys.version_info >= (3, 14) else True
165+
True
162166
>>> _update_context(type_=Callable[[int, bool, str], float], context={})
163167
{'int': <class 'int'>, 'bool': <class 'bool'>, 'str': <class 'str'>, 'float': <class 'float'>}
164168
>>> _update_context(type_=Optional[List[Dict[str, float]]], context={})

pedantic/type_checking_logic/check_types.py

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Idea is taken from: https://stackoverflow.com/a/55504010/10975692"""
22
import inspect
3+
import sys
34
import types
45
import typing
56
from io import BytesIO, StringIO, BufferedWriter, TextIOWrapper
@@ -219,9 +220,7 @@ def _is_instance(obj: Any, type_: Any, type_vars: Dict[TypeVar_, Any], context:
219220
return isinstance(obj, type_.__supertype__)
220221

221222
if hasattr(obj, '_asdict'):
222-
if hasattr(type_, '_field_types'):
223-
field_types = type_._field_types
224-
elif hasattr(type_, '__annotations__'):
223+
if hasattr(type_, '__annotations__'):
225224
field_types = type_.__annotations__
226225
else:
227226
return False
@@ -338,7 +337,8 @@ def _is_generic(cls: Any) -> bool:
338337
return True
339338
elif isinstance(cls, typing._SpecialForm):
340339
return cls not in {Any}
341-
340+
elif cls is typing.Union or type(cls) is typing.Union: # for python >= 3.14 Union is no longer a typing._SpecialForm
341+
return True
342342
return False
343343

344344

@@ -404,8 +404,6 @@ def get_type_arguments(cls: Any) -> Tuple[Any, ...]:
404404
(typing.Tuple[float, str],)
405405
>>> get_type_arguments(List[Tuple[Any, ...]])
406406
(typing.Tuple[typing.Any, ...],)
407-
>>> Union[bool, int, float]
408-
typing.Union[bool, int, float]
409407
>>> get_type_arguments(Union[str, float, int])
410408
(<class 'str'>, <class 'float'>, <class 'int'>)
411409
>>> get_type_arguments(Union[str, float, List[int], int])
@@ -472,10 +470,10 @@ def get_base_generic(cls: Any) -> Any:
472470
typing.Dict
473471
>>> get_base_generic(Dict[str, str])
474472
typing.Dict
475-
>>> get_base_generic(Union)
476-
typing.Union
477-
>>> get_base_generic(Union[float, int, str])
478-
typing.Union
473+
>>> 'typing.Union' in str(get_base_generic(Union)) # 3.13: typing.Union 3.14: <class 'typing.Union'>
474+
True
475+
>>> 'typing.Union' in str(get_base_generic(Union[float, int, str])) # 3.13: typing.Union 3.14: <class 'typing.Union'>
476+
True
479477
>>> get_base_generic(Set)
480478
typing.Set
481479
>>> get_base_generic(Set[int])
@@ -491,7 +489,7 @@ def get_base_generic(cls: Any) -> Any:
491489

492490
if name is not None:
493491
return getattr(typing, name)
494-
elif origin is not None:
492+
elif origin is not None and cls is not typing.Union:
495493
return origin
496494
return cls
497495

@@ -537,10 +535,8 @@ def _is_subtype(sub_type: Any, super_type: Any, context: Dict[str, Any] = None)
537535
False
538536
>>> _is_subtype(List[int], List[Union[int, float]])
539537
True
540-
>>> _is_subtype(List[Union[int, float]], List[int])
541-
Traceback (most recent call last):
542-
...
543-
TypeError: issubclass() arg 1 must be a class
538+
>>> _is_subtype(List[Union[int, float]], List[int]) if sys.version_info >= (3, 14) else False
539+
False
544540
>>> class Parent: pass
545541
>>> class Child(Parent): pass
546542
>>> _is_subtype(List[Child], List[Parent])
@@ -740,9 +736,6 @@ def _instancecheck_tuple(tup: Tuple, type_args: Any, type_vars: Dict[TypeVar_, A
740736
if Ellipsis in type_args:
741737
return all(_is_instance(obj=val, type_=type_args[0], type_vars=type_vars, context=context) for val in tup)
742738

743-
if tup == () and type_args == ((),):
744-
return True
745-
746739
if len(tup) != len(type_args):
747740
return False
748741

@@ -1033,6 +1026,8 @@ def convert_to_typing_types(x: typing.Type) -> typing.Type:
10331026
typing.Tuple[int]
10341027
>>> convert_to_typing_types(type[int])
10351028
typing.Type[int]
1029+
>>> convert_to_typing_types(type[int | float])
1030+
typing.Type[int | float]
10361031
>>> convert_to_typing_types(tuple[int, float])
10371032
typing.Tuple[int, float]
10381033
>>> convert_to_typing_types(dict[int, float])
@@ -1064,6 +1059,8 @@ def convert_to_typing_types(x: typing.Type) -> typing.Type:
10641059
return typing.FrozenSet[tuple(args)]
10651060
elif origin is type:
10661061
return typing.Type[tuple(args)]
1062+
elif origin is typing.Union:
1063+
return x # new since Python 3.14
10671064

10681065
raise RuntimeError(x)
10691066

poetry.lock

Lines changed: 15 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "pedantic"
7-
version = "2.2.3"
7+
version = "2.3.0"
88
description = "Some useful Python decorators for cleaner software development."
99
readme = "README.md"
1010
requires-python = ">=3.11"
@@ -31,8 +31,8 @@ classifiers = [
3131
# pip install .[dev]
3232
dev = [
3333
"docstring-parser==0.17",
34-
"Flask[async]==3.1.1",
35-
"multiprocess==0.70.18",
34+
"Flask[async]==3.1.2",
35+
"multiprocess @ git+https://github.com/uqfoundation/multiprocess.git@02ea4bd36cac5013d70847815c92e1a736ef4a05",
3636
"Werkzeug==3.1.3",
3737
]
3838

0 commit comments

Comments
 (0)