Coverage for pydantic/_internal/_typing_extra.py: 97.06%

176 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2024-06-21 17:00 +0000

1"""Logic for interacting with type annotations, mostly extensions, shims and hacks to wrap python's typing module.""" 

2 

3from __future__ import annotations as _annotations 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

4 

5import dataclasses 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

6import re 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

7import sys 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

8import types 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

9import typing 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

10import warnings 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

11from collections.abc import Callable 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

12from functools import partial 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

13from types import GetSetDescriptorType 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

14from typing import TYPE_CHECKING, Any, Final 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

15 

16from typing_extensions import Annotated, Literal, TypeAliasType, TypeGuard, deprecated, get_args, get_origin 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

17 

18if TYPE_CHECKING: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

19 from ._dataclasses import StandardDataclass 

20 

21try: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

22 from typing import _TypingBase # type: ignore[attr-defined] 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

23except ImportError: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

24 from typing import _Final as _TypingBase # type: ignore[attr-defined] 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

25 

26typing_base = _TypingBase 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

27 

28 

29if sys.version_info < (3, 9): 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

30 # python < 3.9 does not have GenericAlias (list[int], tuple[str, ...] and so on) 

31 TypingGenericAlias = () 1hijkGlm

32else: 

33 from typing import GenericAlias as TypingGenericAlias # type: ignore 1bcnouvwxatdepqyzABHIJKLMNOfgrsCDEF

34 

35 

36if sys.version_info < (3, 11): 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

37 from typing_extensions import NotRequired, Required 1hibcnoatjkdepqGHIJKLMlmfgrs

38else: 

39 from typing import NotRequired, Required # noqa: F401 1uvwxyzABNOCDEF

40 

41 

42if sys.version_info < (3, 10): 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

43 

44 def origin_is_union(tp: type[Any] | None) -> bool: 1hibcajkdeGHlmfg

45 return tp is typing.Union 1hibcajkdeGHlmfg

46 

47 WithArgsTypes = (TypingGenericAlias,) 1hibcajkdeGHlmfg

48 

49else: 

50 

51 def origin_is_union(tp: type[Any] | None) -> bool: 1nouvwxtpqyzABIJKLMNOrsCDEF

52 return tp is typing.Union or tp is types.UnionType 1nouvwxtpqyzABIJKLMNOrsCDEF

53 

54 WithArgsTypes = typing._GenericAlias, types.GenericAlias, types.UnionType # type: ignore[attr-defined] 1nouvwxtpqyzABIJKLMNOrsCDEF

55 

56 

57if sys.version_info < (3, 10): 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

58 NoneType = type(None) 1hibcajkdeGHlmfg

59 EllipsisType = type(Ellipsis) 1hibcajkdeGHlmfg

60else: 

61 from types import NoneType as NoneType 1nouvwxtpqyzABIJKLMNOrsCDEF

62 

63 

64LITERAL_TYPES: set[Any] = {Literal} 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

65if hasattr(typing, 'Literal'): 65 ↛ 69line 65 didn't jump to line 69, because the condition on line 65 was always true1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

66 LITERAL_TYPES.add(typing.Literal) # type: ignore 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

67 

68# Check if `deprecated` is a type to prevent errors when using typing_extensions < 4.9.0 

69DEPRECATED_TYPES: tuple[Any, ...] = (deprecated,) if isinstance(deprecated, type) else () 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

70if hasattr(warnings, 'deprecated'): 70 ↛ 71line 70 didn't jump to line 71, because the condition on line 70 was never true1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

71 DEPRECATED_TYPES = (*DEPRECATED_TYPES, warnings.deprecated) # type: ignore 

72 

73NONE_TYPES: tuple[Any, ...] = (None, NoneType, *(tp[None] for tp in LITERAL_TYPES)) 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

74 

75 

76TypeVarType = Any # since mypy doesn't allow the use of TypeVar as a type 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

77 

78 

79def is_none_type(type_: Any) -> bool: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

80 return type_ in NONE_TYPES 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

81 

82 

83def is_callable_type(type_: type[Any]) -> bool: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

84 return type_ is Callable or get_origin(type_) is Callable 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

85 

86 

87def is_literal_type(type_: type[Any]) -> bool: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

88 return Literal is not None and get_origin(type_) in LITERAL_TYPES 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

89 

90 

91def is_deprecated_instance(instance: Any) -> TypeGuard[deprecated]: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

92 return isinstance(instance, DEPRECATED_TYPES) 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

93 

94 

95def literal_values(type_: type[Any]) -> tuple[Any, ...]: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

96 return get_args(type_) 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

97 

98 

99def all_literal_values(type_: type[Any]) -> list[Any]: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

100 """This method is used to retrieve all Literal values as 

101 Literal can be used recursively (see https://www.python.org/dev/peps/pep-0586) 

102 e.g. `Literal[Literal[Literal[1, 2, 3], "foo"], 5, None]`. 

103 """ 

104 if not is_literal_type(type_): 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

105 return [type_] 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

106 

107 values = literal_values(type_) 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

108 return list(x for value in values for x in all_literal_values(value)) 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

109 

110 

111def is_annotated(ann_type: Any) -> bool: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

112 from ._utils import lenient_issubclass 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

113 

114 origin = get_origin(ann_type) 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

115 return origin is not None and lenient_issubclass(origin, Annotated) 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

116 

117 

118def annotated_type(type_: Any) -> Any | None: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

119 return get_args(type_)[0] if is_annotated(type_) else None 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

120 

121 

122def is_namedtuple(type_: type[Any]) -> bool: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

123 """Check if a given class is a named tuple. 

124 It can be either a `typing.NamedTuple` or `collections.namedtuple`. 

125 """ 

126 from ._utils import lenient_issubclass 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

127 

128 return lenient_issubclass(type_, tuple) and hasattr(type_, '_fields') 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

129 

130 

131test_new_type = typing.NewType('test_new_type', str) 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

132 

133 

134def is_new_type(type_: type[Any]) -> bool: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

135 """Check whether type_ was created using typing.NewType. 

136 

137 Can't use isinstance because it fails <3.10. 

138 """ 

139 return isinstance(type_, test_new_type.__class__) and hasattr(type_, '__supertype__') # type: ignore[arg-type] 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

140 

141 

142def _check_classvar(v: type[Any] | None) -> bool: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

143 if v is None: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

144 return False 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

145 

146 return v.__class__ == typing.ClassVar.__class__ and getattr(v, '_name', None) == 'ClassVar' 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

147 

148 

149def is_classvar(ann_type: type[Any]) -> bool: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

150 if _check_classvar(ann_type) or _check_classvar(get_origin(ann_type)): 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

151 return True 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

152 

153 # this is an ugly workaround for class vars that contain forward references and are therefore themselves 

154 # forward references, see #3679 

155 if ann_type.__class__ == typing.ForwardRef and re.match( 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

156 r'(\w+\.)?ClassVar\[', 

157 ann_type.__forward_arg__, # type: ignore 

158 ): 

159 return True 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

160 

161 return False 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

162 

163 

164def _check_finalvar(v: type[Any] | None) -> bool: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

165 """Check if a given type is a `typing.Final` type.""" 

166 if v is None: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

167 return False 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

168 

169 return v.__class__ == Final.__class__ and (sys.version_info < (3, 8) or getattr(v, '_name', None) == 'Final') 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

170 

171 

172def is_finalvar(ann_type: Any) -> bool: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

173 return _check_finalvar(ann_type) or _check_finalvar(get_origin(ann_type)) 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

174 

175 

176def parent_frame_namespace(*, parent_depth: int = 2) -> dict[str, Any] | None: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

177 """We allow use of items in parent namespace to get around the issue with `get_type_hints` only looking in the 

178 global module namespace. See https://github.com/pydantic/pydantic/issues/2678#issuecomment-1008139014 -> Scope 

179 and suggestion at the end of the next comment by @gvanrossum. 

180 

181 WARNING 1: it matters exactly where this is called. By default, this function will build a namespace from the 

182 parent of where it is called. 

183 

184 WARNING 2: this only looks in the parent namespace, not other parents since (AFAIK) there's no way to collect a 

185 dict of exactly what's in scope. Using `f_back` would work sometimes but would be very wrong and confusing in many 

186 other cases. See https://discuss.python.org/t/is-there-a-way-to-access-parent-nested-namespaces/20659. 

187 """ 

188 frame = sys._getframe(parent_depth) 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

189 # if f_back is None, it's the global module namespace and we don't need to include it here 

190 if frame.f_back is None: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

191 return None 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

192 else: 

193 return frame.f_locals 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

194 

195 

196def add_module_globals(obj: Any, globalns: dict[str, Any] | None = None) -> dict[str, Any]: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

197 module_name = getattr(obj, '__module__', None) 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

198 if module_name: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

199 try: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

200 module_globalns = sys.modules[module_name].__dict__ 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

201 except KeyError: 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

202 # happens occasionally, see https://github.com/pydantic/pydantic/issues/2363 

203 pass 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

204 else: 

205 if globalns: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

206 return {**module_globalns, **globalns} 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

207 else: 

208 # copy module globals to make sure it can't be updated later 

209 return module_globalns.copy() 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

210 

211 return globalns or {} 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

212 

213 

214def get_cls_types_namespace(cls: type[Any], parent_namespace: dict[str, Any] | None = None) -> dict[str, Any]: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

215 ns = add_module_globals(cls, parent_namespace) 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

216 ns[cls.__name__] = cls 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

217 return ns 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

218 

219 

220def get_cls_type_hints_lenient(obj: Any, globalns: dict[str, Any] | None = None) -> dict[str, Any]: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

221 """Collect annotations from a class, including those from parent classes. 

222 

223 Unlike `typing.get_type_hints`, this function will not error if a forward reference is not resolvable. 

224 """ 

225 hints = {} 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

226 for base in reversed(obj.__mro__): 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

227 ann = base.__dict__.get('__annotations__') 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

228 localns = dict(vars(base)) 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

229 if ann is not None and ann is not GetSetDescriptorType: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

230 for name, value in ann.items(): 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

231 hints[name] = eval_type_lenient(value, globalns, localns) 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

232 return hints 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

233 

234 

235def eval_type_lenient(value: Any, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None) -> Any: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

236 """Behaves like typing._eval_type, except it won't raise an error if a forward reference can't be resolved.""" 

237 if value is None: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

238 value = NoneType 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

239 elif isinstance(value, str): 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

240 value = _make_forward_ref(value, is_argument=False, is_class=True) 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

241 

242 try: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

243 return eval_type_backport(value, globalns, localns) 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

244 except NameError: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

245 # the point of this function is to be tolerant to this case 

246 return value 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

247 

248 

249def eval_type_backport( 1hibcnouvwxjkdepqyzABGHIJKLMNOlmfgrsCDEF

250 value: Any, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None 

251) -> Any: 

252 """Like `typing._eval_type`, but falls back to the `eval_type_backport` package if it's 

253 installed to let older Python versions use newer typing features. 

254 Specifically, this transforms `X | Y` into `typing.Union[X, Y]` 

255 and `list[X]` into `typing.List[X]` etc. (for all the types made generic in PEP 585) 

256 if the original syntax is not supported in the current Python version. 

257 """ 

258 try: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

259 return typing._eval_type( # type: ignore 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

260 value, globalns, localns 

261 ) 

262 except TypeError as e: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

263 if not (isinstance(value, typing.ForwardRef) and is_backport_fixable_error(e)): 1hibcnoatjkdepqlmfgrs

264 raise 1hibcnoatjkdepqlmfgrs

265 try: 1hibcajkdelmfg

266 from eval_type_backport import eval_type_backport 1hibcajkdelmfg

267 except ImportError: 1hibcajkdelmfg

268 raise TypeError( 1hibcajkdelmfg

269 f'You have a type annotation {value.__forward_arg__!r} ' 

270 f'which makes use of newer typing features than are supported in your version of Python. ' 

271 f'To handle this error, you should either remove the use of new syntax ' 

272 f'or install the `eval_type_backport` package.' 

273 ) from e 

274 

275 return eval_type_backport(value, globalns, localns, try_default=False) 1hibcajkdelmfg

276 

277 

278def is_backport_fixable_error(e: TypeError) -> bool: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

279 msg = str(e) 1hibcnoatjkdepqlmfgrs

280 return msg.startswith('unsupported operand type(s) for |: ') or "' object is not subscriptable" in msg 1hibcnoatjkdepqlmfgrs

281 

282 

283def get_function_type_hints( 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

284 function: Callable[..., Any], *, include_keys: set[str] | None = None, types_namespace: dict[str, Any] | None = None 

285) -> dict[str, Any]: 

286 """Like `typing.get_type_hints`, but doesn't convert `X` to `Optional[X]` if the default value is `None`, also 

287 copes with `partial`. 

288 """ 

289 try: 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

290 if isinstance(function, partial): 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

291 annotations = function.func.__annotations__ 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

292 else: 

293 annotations = function.__annotations__ 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

294 except AttributeError: 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

295 type_hints = get_type_hints(function) 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

296 if isinstance(function, type): 296 ↛ 301line 296 didn't jump to line 301, because the condition on line 296 was always true1hibcnouvwxatjkdepqyzABlmfgrsCDEF

297 # `type[...]` is a callable, which returns an instance of itself. 

298 # At some point, we might even look into the return type of `__new__` 

299 # if it returns something else. 

300 type_hints.setdefault('return', function) 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

301 return type_hints 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

302 

303 globalns = add_module_globals(function) 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

304 type_hints = {} 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

305 for name, value in annotations.items(): 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

306 if include_keys is not None and name not in include_keys: 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

307 continue 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

308 if value is None: 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

309 value = NoneType 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

310 elif isinstance(value, str): 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

311 value = _make_forward_ref(value) 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

312 

313 type_hints[name] = eval_type_backport(value, globalns, types_namespace) 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

314 

315 return type_hints 1hibcnouvwxatjkdepqyzABlmfgrsCDEF

316 

317 

318if sys.version_info < (3, 9, 8) or (3, 10) <= sys.version_info < (3, 10, 1): 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

319 

320 def _make_forward_ref( 1hijkGlm

321 arg: Any, 

322 is_argument: bool = True, 

323 *, 

324 is_class: bool = False, 

325 ) -> typing.ForwardRef: 

326 """Wrapper for ForwardRef that accounts for the `is_class` argument missing in older versions. 

327 The `module` argument is omitted as it breaks <3.9.8, =3.10.0 and isn't used in the calls below. 

328 

329 See https://github.com/python/cpython/pull/28560 for some background. 

330 The backport happened on 3.9.8, see: 

331 https://github.com/pydantic/pydantic/discussions/6244#discussioncomment-6275458, 

332 and on 3.10.1 for the 3.10 branch, see: 

333 https://github.com/pydantic/pydantic/issues/6912 

334 

335 Implemented as EAFP with memory. 

336 """ 

337 return typing.ForwardRef(arg, is_argument) 1hijkGlm

338 

339else: 

340 _make_forward_ref = typing.ForwardRef 1bcnouvwxatdepqyzABHIJKLMNOfgrsCDEF

341 

342 

343if sys.version_info >= (3, 10): 343 ↛ 347line 343 didn't jump to line 347, because the condition on line 343 was always true1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

344 get_type_hints = typing.get_type_hints 1nouvwxtpqyzABIJKLMNOrsCDEF

345 

346else: 

347 """ 

348 For older versions of python, we have a custom implementation of `get_type_hints` which is a close as possible to 

349 the implementation in CPython 3.10.8. 

350 """ 

351 

352 @typing.no_type_check 1hibcajkdeGHlmfg

353 def get_type_hints( # noqa: C901 1hibcjkdeGHlmfg

354 obj: Any, 1a

355 globalns: dict[str, Any] | None = None, 1a

356 localns: dict[str, Any] | None = None, 1a

357 include_extras: bool = False, 1a

358 ) -> dict[str, Any]: # pragma: no cover 1a

359 """Taken verbatim from python 3.10.8 unchanged, except: 

360 * type annotations of the function definition above. 

361 * prefixing `typing.` where appropriate 

362 * Use `_make_forward_ref` instead of `typing.ForwardRef` to handle the `is_class` argument. 

363 

364 https://github.com/python/cpython/blob/aaaf5174241496afca7ce4d4584570190ff972fe/Lib/typing.py#L1773-L1875 

365 

366 DO NOT CHANGE THIS METHOD UNLESS ABSOLUTELY NECESSARY. 

367 ====================================================== 

368 

369 Return type hints for an object. 

370 

371 This is often the same as obj.__annotations__, but it handles 

372 forward references encoded as string literals, adds Optional[t] if a 

373 default value equal to None is set and recursively replaces all 

374 'Annotated[T, ...]' with 'T' (unless 'include_extras=True'). 

375 

376 The argument may be a module, class, method, or function. The annotations 

377 are returned as a dictionary. For classes, annotations include also 

378 inherited members. 

379 

380 TypeError is raised if the argument is not of a type that can contain 

381 annotations, and an empty dictionary is returned if no annotations are 

382 present. 

383 

384 BEWARE -- the behavior of globalns and localns is counterintuitive 

385 (unless you are familiar with how eval() and exec() work). The 

386 search order is locals first, then globals. 

387 

388 - If no dict arguments are passed, an attempt is made to use the 

389 globals from obj (or the respective module's globals for classes), 

390 and these are also used as the locals. If the object does not appear 

391 to have globals, an empty dictionary is used. For classes, the search 

392 order is globals first then locals. 

393 

394 - If one dict argument is passed, it is used for both globals and 

395 locals. 

396 

397 - If two dict arguments are passed, they specify globals and 

398 locals, respectively. 

399 """ 

400 if getattr(obj, '__no_type_check__', None): 1hibcajkdelmfg

401 return {} 

402 # Classes require a special treatment. 

403 if isinstance(obj, type): 1hibcajkdelmfg

404 hints = {} 1hibcajkdelmfg

405 for base in reversed(obj.__mro__): 1hibcajkdelmfg

406 if globalns is None: 1hibcajkdelmfg

407 base_globals = getattr(sys.modules.get(base.__module__, None), '__dict__', {}) 1hibcajkdelmfg

408 else: 

409 base_globals = globalns 1hibcajkdelmfg

410 ann = base.__dict__.get('__annotations__', {}) 1hibcajkdelmfg

411 if isinstance(ann, types.GetSetDescriptorType): 1hibcajkdelmfg

412 ann = {} 

413 base_locals = dict(vars(base)) if localns is None else localns 1hibcajkdelmfg

414 if localns is None and globalns is None: 1hibcajkdelmfg

415 # This is surprising, but required. Before Python 3.10, 

416 # get_type_hints only evaluated the globalns of 

417 # a class. To maintain backwards compatibility, we reverse 

418 # the globalns and localns order so that eval() looks into 

419 # *base_globals* first rather than *base_locals*. 

420 # This only affects ForwardRefs. 

421 base_globals, base_locals = base_locals, base_globals 1hibcajkdelmfg

422 for name, value in ann.items(): 1hibcajkdelmfg

423 if value is None: 1hibcajkdelmfg

424 value = type(None) 

425 if isinstance(value, str): 1hibcajkdelmfg

426 value = _make_forward_ref(value, is_argument=False, is_class=True) 1hibcajkdelmfg

427 

428 value = eval_type_backport(value, base_globals, base_locals) 1hibcajkdelmfg

429 hints[name] = value 1hibcajkdelmfg

430 if not include_extras and hasattr(typing, '_strip_annotations'): 1hibcajkdelmfg

431 return { 1bcadefg

432 k: typing._strip_annotations(t) # type: ignore 1bcadefg

433 for k, t in hints.items() 1bcadefg

434 } 

435 else: 

436 return hints 1hibcajkdelmfg

437 

438 if globalns is None: 1hibcajkdelmfg

439 if isinstance(obj, types.ModuleType): 1hibcajkdelmfg

440 globalns = obj.__dict__ 

441 else: 

442 nsobj = obj 1hibcajkdelmfg

443 # Find globalns for the unwrapped object. 

444 while hasattr(nsobj, '__wrapped__'): 1hibcajkdelmfg

445 nsobj = nsobj.__wrapped__ 

446 globalns = getattr(nsobj, '__globals__', {}) 1hibcajkdelmfg

447 if localns is None: 1hibcajkdelmfg

448 localns = globalns 1hibcajkdelmfg

449 elif localns is None: 

450 localns = globalns 

451 hints = getattr(obj, '__annotations__', None) 1hibcajkdelmfg

452 if hints is None: 1hibcajkdelmfg

453 # Return empty annotations for something that _could_ have them. 

454 if isinstance(obj, typing._allowed_types): # type: ignore 

455 return {} 

456 else: 

457 raise TypeError(f'{obj!r} is not a module, class, method, ' 'or function.') 

458 defaults = typing._get_defaults(obj) # type: ignore 1hibcajkdelmfg

459 hints = dict(hints) 1hibcajkdelmfg

460 for name, value in hints.items(): 1hibcajkdelmfg

461 if value is None: 1hibcajkdelmfg

462 value = type(None) 

463 if isinstance(value, str): 1hibcajkdelmfg

464 # class-level forward refs were handled above, this must be either 

465 # a module-level annotation or a function argument annotation 

466 

467 value = _make_forward_ref( 1hibcajkdelmfg

468 value, 1hibcajkdelmfg

469 is_argument=not isinstance(obj, types.ModuleType), 1hibcajkdelmfg

470 is_class=False, 1hibcajkdelmfg

471 ) 

472 value = eval_type_backport(value, globalns, localns) 1hibcajkdelmfg

473 if name in defaults and defaults[name] is None: 1hibcajkdelmfg

474 value = typing.Optional[value] 

475 hints[name] = value 1hibcajkdelmfg

476 return hints if include_extras else {k: typing._strip_annotations(t) for k, t in hints.items()} # type: ignore 1hibcajkdelmfg

477 

478 

479def is_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

480 # The dataclasses.is_dataclass function doesn't seem to provide TypeGuard functionality, 

481 # so I created this convenience function 

482 return dataclasses.is_dataclass(_cls) 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

483 

484 

485def origin_is_type_alias_type(origin: Any) -> TypeGuard[TypeAliasType]: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

486 return isinstance(origin, TypeAliasType) 

487 

488 

489if sys.version_info >= (3, 10): 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

490 

491 def is_generic_alias(type_: type[Any]) -> bool: 1nouvwxtpqyzABIJKLMNOrsCDEF

492 return isinstance(type_, (types.GenericAlias, typing._GenericAlias)) # type: ignore[attr-defined] 1nouvwxtpqyzABIJKLMNOrsCDEF

493 

494else: 

495 

496 def is_generic_alias(type_: type[Any]) -> bool: 1hibcajkdeGHlmfg

497 return isinstance(type_, typing._GenericAlias) # type: ignore 1hibcajkdeGHlmfg

498 

499 

500def is_self_type(tp: Any) -> bool: 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF

501 """Check if a given class is a Self type (from `typing` or `typing_extensions`)""" 

502 return isinstance(tp, typing_base) and getattr(tp, '_name', None) == 'Self' 1hibcnouvwxatjkdepqyzABGHIJKLMNOlmfgrsCDEF