Skip to content

Commit dfc9330

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

File tree

8 files changed

+55
-57
lines changed

8 files changed

+55
-57
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: 16 additions & 12 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
@@ -220,6 +221,7 @@ def _is_instance(obj: Any, type_: Any, type_vars: Dict[TypeVar_, Any], context:
220221

221222
if hasattr(obj, '_asdict'):
222223
if hasattr(type_, '_field_types'):
224+
raise RuntimeError('Foo')
223225
field_types = type_._field_types
224226
elif hasattr(type_, '__annotations__'):
225227
field_types = type_.__annotations__
@@ -338,7 +340,8 @@ def _is_generic(cls: Any) -> bool:
338340
return True
339341
elif isinstance(cls, typing._SpecialForm):
340342
return cls not in {Any}
341-
343+
elif cls is typing.Union or type(cls) is typing.Union: # for python >= 3.14 Union is no longer a typing._SpecialForm
344+
return True
342345
return False
343346

344347

@@ -404,8 +407,6 @@ def get_type_arguments(cls: Any) -> Tuple[Any, ...]:
404407
(typing.Tuple[float, str],)
405408
>>> get_type_arguments(List[Tuple[Any, ...]])
406409
(typing.Tuple[typing.Any, ...],)
407-
>>> Union[bool, int, float]
408-
typing.Union[bool, int, float]
409410
>>> get_type_arguments(Union[str, float, int])
410411
(<class 'str'>, <class 'float'>, <class 'int'>)
411412
>>> get_type_arguments(Union[str, float, List[int], int])
@@ -472,10 +473,10 @@ def get_base_generic(cls: Any) -> Any:
472473
typing.Dict
473474
>>> get_base_generic(Dict[str, str])
474475
typing.Dict
475-
>>> get_base_generic(Union)
476-
typing.Union
477-
>>> get_base_generic(Union[float, int, str])
478-
typing.Union
476+
>>> 'typing.Union' in str(get_base_generic(Union)) # 3.13: typing.Union 3.14: <class 'typing.Union'>
477+
True
478+
>>> 'typing.Union' in str(get_base_generic(Union[float, int, str])) # 3.13: typing.Union 3.14: <class 'typing.Union'>
479+
True
479480
>>> get_base_generic(Set)
480481
typing.Set
481482
>>> get_base_generic(Set[int])
@@ -491,7 +492,7 @@ def get_base_generic(cls: Any) -> Any:
491492

492493
if name is not None:
493494
return getattr(typing, name)
494-
elif origin is not None:
495+
elif origin is not None and cls is not typing.Union:
495496
return origin
496497
return cls
497498

@@ -537,10 +538,8 @@ def _is_subtype(sub_type: Any, super_type: Any, context: Dict[str, Any] = None)
537538
False
538539
>>> _is_subtype(List[int], List[Union[int, float]])
539540
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
541+
>>> _is_subtype(List[Union[int, float]], List[int]) if sys.version_info >= (3, 14) else False
542+
False
544543
>>> class Parent: pass
545544
>>> class Child(Parent): pass
546545
>>> _is_subtype(List[Child], List[Parent])
@@ -741,6 +740,7 @@ def _instancecheck_tuple(tup: Tuple, type_args: Any, type_vars: Dict[TypeVar_, A
741740
return all(_is_instance(obj=val, type_=type_args[0], type_vars=type_vars, context=context) for val in tup)
742741

743742
if tup == () and type_args == ((),):
743+
raise RuntimeError('Foo3')
744744
return True
745745

746746
if len(tup) != len(type_args):
@@ -1033,6 +1033,8 @@ def convert_to_typing_types(x: typing.Type) -> typing.Type:
10331033
typing.Tuple[int]
10341034
>>> convert_to_typing_types(type[int])
10351035
typing.Type[int]
1036+
>>> convert_to_typing_types(type[int | float])
1037+
typing.Type[int | float]
10361038
>>> convert_to_typing_types(tuple[int, float])
10371039
typing.Tuple[int, float]
10381040
>>> convert_to_typing_types(dict[int, float])
@@ -1064,6 +1066,8 @@ def convert_to_typing_types(x: typing.Type) -> typing.Type:
10641066
return typing.FrozenSet[tuple(args)]
10651067
elif origin is type:
10661068
return typing.Type[tuple(args)]
1069+
elif origin is typing.Union:
1070+
return x # new since Python 3.14
10671071

10681072
raise RuntimeError(x)
10691073

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)