Coverage for /pythoncovmergedfiles/medio/medio/src/pydantic/pydantic/_internal/_typing_extra.py: 63%

158 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-04-27 07:38 +0000

1""" 

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

3""" 

4from __future__ import annotations as _annotations 

5 

6import dataclasses 

7import sys 

8import types 

9import typing 

10from collections.abc import Callable 

11from functools import partial 

12from types import GetSetDescriptorType 

13from typing import TYPE_CHECKING, Any, ForwardRef 

14 

15from typing_extensions import Annotated, Final, Literal, TypeGuard, get_args, get_origin 

16 

17if TYPE_CHECKING: 

18 from ._dataclasses import StandardDataclass 

19 

20try: 

21 from typing import _TypingBase # type: ignore[attr-defined] 

22except ImportError: 

23 from typing import _Final as _TypingBase # type: ignore[attr-defined] 

24 

25typing_base = _TypingBase 

26 

27 

28if sys.version_info < (3, 9): 

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

30 TypingGenericAlias = () 

31else: 

32 from typing import GenericAlias as TypingGenericAlias # type: ignore 

33 

34 

35if sys.version_info < (3, 11): 

36 from typing_extensions import NotRequired, Required 

37else: 

38 from typing import NotRequired, Required # noqa: F401 

39 

40 

41if sys.version_info < (3, 10): 

42 

43 def origin_is_union(tp: type[Any] | None) -> bool: 

44 return tp is typing.Union 

45 

46 WithArgsTypes = (TypingGenericAlias,) 

47 

48else: 

49 

50 def origin_is_union(tp: type[Any] | None) -> bool: 

51 return tp is typing.Union or tp is types.UnionType # noqa: E721 

52 

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

54 

55 

56if sys.version_info < (3, 10): 

57 NoneType = type(None) 

58 EllipsisType = type(Ellipsis) 

59else: 

60 from types import EllipsisType as EllipsisType # noqa: F401 

61 from types import NoneType as NoneType 

62 

63 

64NONE_TYPES: tuple[Any, Any, Any] = (None, NoneType, Literal[None]) 

65 

66 

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

68 

69 

70if sys.version_info < (3, 8): 

71 # Even though this implementation is slower, we need it for python 3.7: 

72 # In python 3.7 "Literal" is not a builtin type and uses a different 

73 # mechanism. 

74 # for this reason `Literal[None] is Literal[None]` evaluates to `False`, 

75 # breaking the faster implementation used for the other python versions. 

76 

77 def is_none_type(type_: Any) -> bool: 

78 return type_ in NONE_TYPES 

79 

80elif sys.version_info[:2] == (3, 8): 

81 

82 def is_none_type(type_: Any) -> bool: 

83 for none_type in NONE_TYPES: 

84 if type_ is none_type: 

85 return True 

86 # With python 3.8, specifically 3.8.10, Literal "is" checks are very flakey 

87 # can change on very subtle changes like use of types in other modules, 

88 # hopefully this check avoids that issue. 

89 if is_literal_type(type_): # pragma: no cover 

90 return all_literal_values(type_) == [None] 

91 return False 

92 

93else: 

94 

95 def is_none_type(type_: Any) -> bool: 

96 for none_type in NONE_TYPES: 

97 if type_ is none_type: 

98 return True 

99 return False 

100 

101 

102def is_callable_type(type_: type[Any]) -> bool: 

103 return type_ is Callable or get_origin(type_) is Callable 

104 

105 

106def is_literal_type(type_: type[Any]) -> bool: 

107 return Literal is not None and get_origin(type_) is Literal 

108 

109 

110def literal_values(type_: type[Any]) -> tuple[Any, ...]: 

111 return get_args(type_) 

112 

113 

114def all_literal_values(type_: type[Any]) -> list[Any]: 

115 """ 

116 This method is used to retrieve all Literal values as 

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

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

119 """ 

120 if not is_literal_type(type_): 

121 return [type_] 

122 

123 values = literal_values(type_) 

124 return list(x for value in values for x in all_literal_values(value)) 

125 

126 

127def is_annotated(ann_type: Any) -> bool: 

128 from ._utils import lenient_issubclass 

129 

130 origin = get_origin(ann_type) 

131 return origin is not None and lenient_issubclass(origin, Annotated) 

132 

133 

134def is_namedtuple(type_: type[Any]) -> bool: 

135 """ 

136 Check if a given class is a named tuple. 

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

138 """ 

139 from ._utils import lenient_issubclass 

140 

141 return lenient_issubclass(type_, tuple) and hasattr(type_, '_fields') 

142 

143 

144test_new_type = typing.NewType('test_new_type', str) 

145 

146 

147def is_new_type(type_: type[Any]) -> bool: 

148 """ 

149 Check whether type_ was created using typing.NewType. 

150 

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

152 """ 

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

154 

155 

156def _check_classvar(v: type[Any] | None) -> bool: 

157 if v is None: 

158 return False 

159 

160 return v.__class__ == typing.ClassVar.__class__ and getattr(v, '_name', None) == 'ClassVar' 

161 

162 

163def is_classvar(ann_type: type[Any]) -> bool: 

164 if _check_classvar(ann_type) or _check_classvar(get_origin(ann_type)): 

165 return True 

166 

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

168 # forward references, see #3679 

169 if ann_type.__class__ == typing.ForwardRef and ann_type.__forward_arg__.startswith('ClassVar['): # type: ignore 

170 return True 

171 

172 return False 

173 

174 

175def _check_finalvar(v: type[Any] | None) -> bool: 

176 """ 

177 Check if a given type is a `typing.Final` type. 

178 """ 

179 if v is None: 

180 return False 

181 

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

183 

184 

185def is_finalvar(ann_type: Any) -> bool: 

186 return _check_finalvar(ann_type) or _check_finalvar(get_origin(ann_type)) 

187 

188 

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

190 """ 

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

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

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

194 

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

196 parent of where it is called. 

197 

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

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

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

201 """ 

202 frame = sys._getframe(parent_depth) 

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

204 if frame.f_back is None: 

205 return None 

206 else: 

207 return frame.f_locals 

208 

209 

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

211 module_name = getattr(obj, '__module__', None) 

212 if module_name: 

213 try: 

214 module_globalns = sys.modules[module_name].__dict__ 

215 except KeyError: 

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

217 pass 

218 else: 

219 if globalns: 

220 return {**module_globalns, **globalns} 

221 else: 

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

223 return module_globalns.copy() 

224 

225 return globalns or {} 

226 

227 

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

229 ns = add_module_globals(cls, parent_namespace) 

230 ns[cls.__name__] = cls 

231 return ns 

232 

233 

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

235 """ 

236 Collect annotations from a class, including those from parent classes. 

237 

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

239 """ 

240 # TODO: Try handling typevars_map here 

241 hints = {} 

242 for base in reversed(obj.__mro__): 

243 ann = base.__dict__.get('__annotations__') 

244 localns = dict(vars(base)) 

245 if ann is not None and ann is not GetSetDescriptorType: 

246 for name, value in ann.items(): 

247 hints[name] = eval_type_lenient(value, globalns, localns) 

248 return hints 

249 

250 

251def eval_type_lenient(value: Any, globalns: dict[str, Any] | None, localns: dict[str, Any] | None) -> Any: 

252 """ 

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

254 """ 

255 if value is None: 

256 value = NoneType 

257 elif isinstance(value, str): 

258 value = _make_forward_ref(value, is_argument=False, is_class=True) 

259 

260 try: 

261 return typing._eval_type(value, globalns, localns) # type: ignore 

262 except NameError: 

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

264 return value 

265 

266 

267def get_function_type_hints(function: Callable[..., Any]) -> dict[str, Any]: 

268 """ 

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

270 copes with `partial`. 

271 """ 

272 

273 if isinstance(function, partial): 

274 annotations = function.func.__annotations__ 

275 else: 

276 annotations = function.__annotations__ 

277 

278 globalns = add_module_globals(function) 

279 type_hints = {} 

280 for name, value in annotations.items(): 

281 if value is None: 

282 value = NoneType 

283 elif isinstance(value, str): 

284 value = _make_forward_ref(value) 

285 

286 type_hints[name] = typing._eval_type(value, globalns, None) # type: ignore 

287 

288 return type_hints 

289 

290 

291if sys.version_info < (3, 9): 

292 

293 def _make_forward_ref( 

294 arg: Any, 

295 is_argument: bool = True, 

296 *, 

297 is_class: bool = False, 

298 ) -> typing.ForwardRef: 

299 """ 

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

301 The `module` argument is omitted as it breaks <3.9 and isn't used in the calls below. 

302 

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

304 

305 Implemented as EAFP with memory. 

306 """ 

307 global _make_forward_ref 

308 try: 

309 res = typing.ForwardRef(arg, is_argument, is_class=is_class) # type: ignore 

310 _make_forward_ref = typing.ForwardRef # type: ignore 

311 return res 

312 except TypeError: 

313 return typing.ForwardRef(arg, is_argument) 

314 

315else: 

316 _make_forward_ref = typing.ForwardRef 

317 

318 

319if sys.version_info >= (3, 10): 

320 get_type_hints = typing.get_type_hints 

321 

322else: 

323 """ 

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

325 the implementation in CPython 3.10.8. 

326 """ 

327 

328 @typing.no_type_check 

329 def get_type_hints( # noqa: C901 

330 obj: Any, 

331 globalns: dict[str, Any] | None = None, 

332 localns: dict[str, Any] | None = None, 

333 include_extras: bool = False, 

334 ) -> dict[str, Any]: # pragma: no cover 

335 """ 

336 Taken verbatim from python 3.10.8 unchanged, except: 

337 * type annotations of the function definition above. 

338 * prefixing `typing.` where appropriate 

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

340 

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

342 

343 DO NOT CHANGE THIS METHOD UNLESS ABSOLUTELY NECESSARY. 

344 ====================================================== 

345 

346 Return type hints for an object. 

347 

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

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

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

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

352 

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

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

355 inherited members. 

356 

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

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

359 present. 

360 

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

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

363 search order is locals first, then globals. 

364 

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

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

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

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

369 order is globals first then locals. 

370 

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

372 locals. 

373 

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

375 locals, respectively. 

376 """ 

377 

378 if getattr(obj, '__no_type_check__', None): 

379 return {} 

380 # Classes require a special treatment. 

381 if isinstance(obj, type): 

382 hints = {} 

383 for base in reversed(obj.__mro__): 

384 if globalns is None: 

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

386 else: 

387 base_globals = globalns 

388 ann = base.__dict__.get('__annotations__', {}) 

389 if isinstance(ann, types.GetSetDescriptorType): 

390 ann = {} 

391 base_locals = dict(vars(base)) if localns is None else localns 

392 if localns is None and globalns is None: 

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

394 # get_type_hints only evaluated the globalns of 

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

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

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

398 # This only affects ForwardRefs. 

399 base_globals, base_locals = base_locals, base_globals 

400 for name, value in ann.items(): 

401 if value is None: 

402 value = type(None) 

403 if isinstance(value, str): 

404 value = _make_forward_ref(value, is_argument=False, is_class=True) 

405 

406 value = typing._eval_type(value, base_globals, base_locals) # type: ignore 

407 hints[name] = value 

408 return ( 

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

410 ) 

411 

412 if globalns is None: 

413 if isinstance(obj, types.ModuleType): 

414 globalns = obj.__dict__ 

415 else: 

416 nsobj = obj 

417 # Find globalns for the unwrapped object. 

418 while hasattr(nsobj, '__wrapped__'): 

419 nsobj = nsobj.__wrapped__ 

420 globalns = getattr(nsobj, '__globals__', {}) 

421 if localns is None: 

422 localns = globalns 

423 elif localns is None: 

424 localns = globalns 

425 hints = getattr(obj, '__annotations__', None) 

426 if hints is None: 

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

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

429 return {} 

430 else: 

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

432 defaults = typing._get_defaults(obj) # type: ignore 

433 hints = dict(hints) 

434 for name, value in hints.items(): 

435 if value is None: 

436 value = type(None) 

437 if isinstance(value, str): 

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

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

440 

441 value = _make_forward_ref( 

442 value, 

443 is_argument=not isinstance(obj, types.ModuleType), 

444 is_class=False, 

445 ) 

446 value = typing._eval_type(value, globalns, localns) # type: ignore 

447 if name in defaults and defaults[name] is None: 

448 value = typing.Optional[value] 

449 hints[name] = value 

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

451 

452 

453if sys.version_info < (3, 9): 

454 

455 def evaluate_fwd_ref( 

456 ref: ForwardRef, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None 

457 ) -> Any: 

458 return ref._evaluate(globalns=globalns, localns=localns) 

459 

460else: 

461 

462 def evaluate_fwd_ref( 

463 ref: ForwardRef, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None 

464 ) -> Any: 

465 return ref._evaluate(globalns=globalns, localns=localns, recursive_guard=frozenset()) 

466 

467 

468def is_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]: 

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

470 # so I created this convenience function 

471 return dataclasses.is_dataclass(_cls)