1from __future__ import annotations
2
3import inspect
4import sys
5from importlib import import_module
6from inspect import currentframe
7from types import CodeType, FrameType, FunctionType
8from typing import TYPE_CHECKING, Any, Callable, ForwardRef, Union, cast, final
9from weakref import WeakValueDictionary
10
11if TYPE_CHECKING:
12 from ._memo import TypeCheckMemo
13
14if sys.version_info >= (3, 13):
15 from typing import get_args, get_origin
16
17 def evaluate_forwardref(forwardref: ForwardRef, memo: TypeCheckMemo) -> Any:
18 return forwardref._evaluate(
19 memo.globals, memo.locals, type_params=(), recursive_guard=frozenset()
20 )
21
22elif sys.version_info >= (3, 10):
23 from typing import get_args, get_origin
24
25 def evaluate_forwardref(forwardref: ForwardRef, memo: TypeCheckMemo) -> Any:
26 return forwardref._evaluate(
27 memo.globals, memo.locals, recursive_guard=frozenset()
28 )
29
30else:
31 from typing_extensions import get_args, get_origin
32
33 evaluate_extra_args: tuple[frozenset[Any], ...] = (
34 (frozenset(),) if sys.version_info >= (3, 9) else ()
35 )
36
37 def evaluate_forwardref(forwardref: ForwardRef, memo: TypeCheckMemo) -> Any:
38 from ._union_transformer import compile_type_hint, type_substitutions
39
40 if not forwardref.__forward_evaluated__:
41 forwardref.__forward_code__ = compile_type_hint(forwardref.__forward_arg__)
42
43 try:
44 return forwardref._evaluate(memo.globals, memo.locals, *evaluate_extra_args)
45 except NameError:
46 if sys.version_info < (3, 10):
47 # Try again, with the type substitutions (list -> List etc.) in place
48 new_globals = memo.globals.copy()
49 new_globals.setdefault("Union", Union)
50 if sys.version_info < (3, 9):
51 new_globals.update(type_substitutions)
52
53 return forwardref._evaluate(
54 new_globals, memo.locals or new_globals, *evaluate_extra_args
55 )
56
57 raise
58
59
60_functions_map: WeakValueDictionary[CodeType, FunctionType] = WeakValueDictionary()
61
62
63def get_type_name(type_: Any) -> str:
64 name: str
65 for attrname in "__name__", "_name", "__forward_arg__":
66 candidate = getattr(type_, attrname, None)
67 if isinstance(candidate, str):
68 name = candidate
69 break
70 else:
71 origin = get_origin(type_)
72 candidate = getattr(origin, "_name", None)
73 if candidate is None:
74 candidate = type_.__class__.__name__.strip("_")
75
76 if isinstance(candidate, str):
77 name = candidate
78 else:
79 return "(unknown)"
80
81 args = get_args(type_)
82 if args:
83 if name == "Literal":
84 formatted_args = ", ".join(repr(arg) for arg in args)
85 else:
86 formatted_args = ", ".join(get_type_name(arg) for arg in args)
87
88 name += f"[{formatted_args}]"
89
90 module = getattr(type_, "__module__", None)
91 if module and module not in (None, "typing", "typing_extensions", "builtins"):
92 name = module + "." + name
93
94 return name
95
96
97def qualified_name(obj: Any, *, add_class_prefix: bool = False) -> str:
98 """
99 Return the qualified name (e.g. package.module.Type) for the given object.
100
101 Builtins and types from the :mod:`typing` package get special treatment by having
102 the module name stripped from the generated name.
103
104 """
105 if obj is None:
106 return "None"
107 elif inspect.isclass(obj):
108 prefix = "class " if add_class_prefix else ""
109 type_ = obj
110 else:
111 prefix = ""
112 type_ = type(obj)
113
114 module = type_.__module__
115 qualname = type_.__qualname__
116 name = qualname if module in ("typing", "builtins") else f"{module}.{qualname}"
117 return prefix + name
118
119
120def function_name(func: Callable[..., Any]) -> str:
121 """
122 Return the qualified name of the given function.
123
124 Builtins and types from the :mod:`typing` package get special treatment by having
125 the module name stripped from the generated name.
126
127 """
128 # For partial functions and objects with __call__ defined, __qualname__ does not
129 # exist
130 module = getattr(func, "__module__", "")
131 qualname = (module + ".") if module not in ("builtins", "") else ""
132 return qualname + getattr(func, "__qualname__", repr(func))
133
134
135def resolve_reference(reference: str) -> Any:
136 modulename, varname = reference.partition(":")[::2]
137 if not modulename or not varname:
138 raise ValueError(f"{reference!r} is not a module:varname reference")
139
140 obj = import_module(modulename)
141 for attr in varname.split("."):
142 obj = getattr(obj, attr)
143
144 return obj
145
146
147def is_method_of(obj: object, cls: type) -> bool:
148 return (
149 inspect.isfunction(obj)
150 and obj.__module__ == cls.__module__
151 and obj.__qualname__.startswith(cls.__qualname__ + ".")
152 )
153
154
155def get_stacklevel() -> int:
156 level = 1
157 frame = cast(FrameType, currentframe()).f_back
158 while frame and frame.f_globals.get("__name__", "").startswith("typeguard."):
159 level += 1
160 frame = frame.f_back
161
162 return level
163
164
165@final
166class Unset:
167 __slots__ = ()
168
169 def __repr__(self) -> str:
170 return "<unset>"
171
172
173unset = Unset()