Skip to content

Commit 6c97742

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

File tree

8 files changed

+53
-57
lines changed

8 files changed

+53
-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: 14 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
@@ -338,7 +339,8 @@ def _is_generic(cls: Any) -> bool:
338339
return True
339340
elif isinstance(cls, typing._SpecialForm):
340341
return cls not in {Any}
341-
342+
elif cls is typing.Union or type(cls) is typing.Union: # for python >= 3.14 Union is no longer a typing._SpecialForm
343+
return True
342344
return False
343345

344346

@@ -404,8 +406,6 @@ def get_type_arguments(cls: Any) -> Tuple[Any, ...]:
404406
(typing.Tuple[float, str],)
405407
>>> get_type_arguments(List[Tuple[Any, ...]])
406408
(typing.Tuple[typing.Any, ...],)
407-
>>> Union[bool, int, float]
408-
typing.Union[bool, int, float]
409409
>>> get_type_arguments(Union[str, float, int])
410410
(<class 'str'>, <class 'float'>, <class 'int'>)
411411
>>> get_type_arguments(Union[str, float, List[int], int])
@@ -472,10 +472,10 @@ def get_base_generic(cls: Any) -> Any:
472472
typing.Dict
473473
>>> get_base_generic(Dict[str, str])
474474
typing.Dict
475-
>>> get_base_generic(Union)
476-
typing.Union
477-
>>> get_base_generic(Union[float, int, str])
478-
typing.Union
475+
>>> 'typing.Union' in str(get_base_generic(Union)) # 3.13: typing.Union 3.14: <class 'typing.Union'>
476+
True
477+
>>> 'typing.Union' in str(get_base_generic(Union[float, int, str])) # 3.13: typing.Union 3.14: <class 'typing.Union'>
478+
True
479479
>>> get_base_generic(Set)
480480
typing.Set
481481
>>> get_base_generic(Set[int])
@@ -491,7 +491,7 @@ def get_base_generic(cls: Any) -> Any:
491491

492492
if name is not None:
493493
return getattr(typing, name)
494-
elif origin is not None:
494+
elif origin is not None and cls is not typing.Union:
495495
return origin
496496
return cls
497497

@@ -537,10 +537,8 @@ def _is_subtype(sub_type: Any, super_type: Any, context: Dict[str, Any] = None)
537537
False
538538
>>> _is_subtype(List[int], List[Union[int, float]])
539539
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
540+
>>> _is_subtype(List[Union[int, float]], List[int]) if sys.version_info >= (3, 14) else False
541+
False
544542
>>> class Parent: pass
545543
>>> class Child(Parent): pass
546544
>>> _is_subtype(List[Child], List[Parent])
@@ -1033,6 +1031,8 @@ def convert_to_typing_types(x: typing.Type) -> typing.Type:
10331031
typing.Tuple[int]
10341032
>>> convert_to_typing_types(type[int])
10351033
typing.Type[int]
1034+
>>> convert_to_typing_types(type[int | float])
1035+
typing.Type[int | float]
10361036
>>> convert_to_typing_types(tuple[int, float])
10371037
typing.Tuple[int, float]
10381038
>>> convert_to_typing_types(dict[int, float])
@@ -1064,6 +1064,8 @@ def convert_to_typing_types(x: typing.Type) -> typing.Type:
10641064
return typing.FrozenSet[tuple(args)]
10651065
elif origin is type:
10661066
return typing.Type[tuple(args)]
1067+
elif origin is typing.Union:
1068+
return x
10671069

10681070
raise RuntimeError(x)
10691071

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)