Skip to content

Commit 17a7866

Browse files
AlexWaygoodTinche
authored andcommitted
Make detection of TypeVar defaults robust to the CPython PEP-696 implementation
1 parent a13fa2e commit 17a7866

1 file changed

Lines changed: 26 additions & 7 deletions

File tree

src/cattrs/gen/_generics.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,30 @@
55
from .._compat import get_args, get_origin, is_generic
66

77

8+
def _tvar_has_default(tvar) -> bool:
9+
"""Does `tvar` have a default?
10+
11+
In CPython 3.13+ and typing_extensions>=4.12.0:
12+
- TypeVars have a `no_default()` method for detecting
13+
if a TypeVar has a default
14+
- TypeVars with `default=None` have `__default__` set to `None`
15+
- TypeVars with no `default` parameter passed
16+
have `__default__` set to `typing(_extensions).NoDefault
17+
18+
On typing_exensions<4.12.0:
19+
- TypeVars do not have a `no_default()` method for detecting
20+
if a TypeVar has a default
21+
- TypeVars with `default=None` have `__default__` set to `NoneType`
22+
- TypeVars with no `default` parameter passed
23+
have `__default__` set to `typing(_extensions).NoDefault
24+
"""
25+
try:
26+
return tvar.has_default()
27+
except AttributeError:
28+
# compatibility for typing_extensions<4.12.0
29+
return getattr(tvar, "__default__", None) is not None
30+
31+
832
def generate_mapping(cl: type, old_mapping: dict[str, type] = {}) -> dict[str, type]:
933
"""Generate a mapping of typevars to actual types for a generic class."""
1034
mapping = dict(old_mapping)
@@ -35,20 +59,15 @@ def generate_mapping(cl: type, old_mapping: dict[str, type] = {}) -> dict[str, t
3559
base_args = base.__args__
3660
if hasattr(base.__origin__, "__parameters__"):
3761
base_params = base.__origin__.__parameters__
38-
elif any(
39-
getattr(base_arg, "__default__", None) is not None
40-
for base_arg in base_args
41-
):
62+
elif any(_tvar_has_default(base_arg) for base_arg in base_args):
4263
# TypeVar with a default e.g. PEP 696
4364
# https://www.python.org/dev/peps/pep-0696/
4465
# Extract the defaults for the TypeVars and insert
4566
# them into the mapping
4667
mapping_params = [
4768
(base_arg, base_arg.__default__)
4869
for base_arg in base_args
49-
# Note: None means no default was provided, since
50-
# TypeVar("T", default=None) sets NoneType as the default
51-
if getattr(base_arg, "__default__", None) is not None
70+
if _tvar_has_default(base_arg)
5271
]
5372
base_params, base_args = zip(*mapping_params)
5473
else:

0 commit comments

Comments
 (0)