Skip to content

Commit

Permalink
Make detection of TypeVar defaults robust to the CPython PEP-696 impl…
Browse files Browse the repository at this point in the history
…ementation
  • Loading branch information
AlexWaygood authored and Tinche committed May 12, 2024
1 parent a13fa2e commit 17a7866
Showing 1 changed file with 26 additions and 7 deletions.
33 changes: 26 additions & 7 deletions src/cattrs/gen/_generics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@
from .._compat import get_args, get_origin, is_generic


def _tvar_has_default(tvar) -> bool:
"""Does `tvar` have a default?
In CPython 3.13+ and typing_extensions>=4.12.0:
- TypeVars have a `no_default()` method for detecting
if a TypeVar has a default
- TypeVars with `default=None` have `__default__` set to `None`
- TypeVars with no `default` parameter passed
have `__default__` set to `typing(_extensions).NoDefault
On typing_exensions<4.12.0:
- TypeVars do not have a `no_default()` method for detecting
if a TypeVar has a default
- TypeVars with `default=None` have `__default__` set to `NoneType`
- TypeVars with no `default` parameter passed
have `__default__` set to `typing(_extensions).NoDefault
"""
try:
return tvar.has_default()
except AttributeError:
# compatibility for typing_extensions<4.12.0
return getattr(tvar, "__default__", None) is not None


def generate_mapping(cl: type, old_mapping: dict[str, type] = {}) -> dict[str, type]:
"""Generate a mapping of typevars to actual types for a generic class."""
mapping = dict(old_mapping)
Expand Down Expand Up @@ -35,20 +59,15 @@ def generate_mapping(cl: type, old_mapping: dict[str, type] = {}) -> dict[str, t
base_args = base.__args__
if hasattr(base.__origin__, "__parameters__"):
base_params = base.__origin__.__parameters__
elif any(
getattr(base_arg, "__default__", None) is not None
for base_arg in base_args
):
elif any(_tvar_has_default(base_arg) for base_arg in base_args):
# TypeVar with a default e.g. PEP 696
# https://www.python.org/dev/peps/pep-0696/
# Extract the defaults for the TypeVars and insert
# them into the mapping
mapping_params = [
(base_arg, base_arg.__default__)
for base_arg in base_args
# Note: None means no default was provided, since
# TypeVar("T", default=None) sets NoneType as the default
if getattr(base_arg, "__default__", None) is not None
if _tvar_has_default(base_arg)
]
base_params, base_args = zip(*mapping_params)
else:
Expand Down

0 comments on commit 17a7866

Please sign in to comment.