from __future__ import annotations
from typing import TypeVar
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)
origin = get_origin(cl)
if origin is not None:
# To handle the cases where classes in the typing module are using
# the GenericAlias structure but aren't a Generic and hence
# end up in this function but do not have an `__parameters__`
# attribute. These classes are interface types, for example
# `typing.Hashable`.
parameters = getattr(get_origin(cl), "__parameters__", None)
if parameters is None:
return dict(old_mapping)
for p, t in zip(parameters, get_args(cl)):
if isinstance(t, TypeVar):
continue
mapping[p.__name__] = t
elif is_generic(cl):
# Origin is None, so this may be a subclass of a generic class.
orig_bases = cl.__orig_bases__
for base in orig_bases:
if not hasattr(base, "__args__"):
continue
base_args = base.__args__
if hasattr(base.__origin__, "__parameters__"):
base_params = base.__origin__.__parameters__
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
if _tvar_has_default(base_arg)
]
base_params, base_args = zip(*mapping_params)
else:
continue
for param, arg in zip(base_params, base_args):
mapping[param.__name__] = arg
return mapping