Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/cattrs/_compat.py: 57%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

162 statements  

1import sys 

2from collections import Counter, deque 

3from collections.abc import Mapping as AbcMapping 

4from collections.abc import MutableMapping as AbcMutableMapping 

5from collections.abc import MutableSequence as AbcMutableSequence 

6from collections.abc import MutableSet as AbcMutableSet 

7from collections.abc import Sequence as AbcSequence 

8from collections.abc import Set as AbcSet 

9from dataclasses import MISSING, Field, is_dataclass 

10from dataclasses import fields as dataclass_fields 

11from functools import partial 

12from inspect import signature as _signature 

13from types import GenericAlias 

14from typing import ( 

15 Annotated, 

16 Any, 

17 Deque, 

18 Dict, 

19 Final, 

20 FrozenSet, 

21 Generic, 

22 List, 

23 Literal, 

24 NewType, 

25 Optional, 

26 Protocol, 

27 Tuple, 

28 Union, 

29 _AnnotatedAlias, 

30 _GenericAlias, 

31 _SpecialGenericAlias, 

32 _UnionGenericAlias, 

33 get_args, 

34 get_origin, 

35 get_type_hints, 

36) 

37from typing import Counter as TypingCounter 

38from typing import Mapping as TypingMapping 

39from typing import MutableMapping as TypingMutableMapping 

40from typing import MutableSequence as TypingMutableSequence 

41from typing import MutableSet as TypingMutableSet 

42from typing import Sequence as TypingSequence 

43from typing import Set as TypingSet 

44 

45from attrs import NOTHING, Attribute, Factory, NothingType, resolve_types 

46from attrs import fields as attrs_fields 

47from attrs import fields_dict as attrs_fields_dict 

48 

49__all__ = [ 

50 "ANIES", 

51 "ExceptionGroup", 

52 "ExtensionsTypedDict", 

53 "TypeAlias", 

54 "adapted_fields", 

55 "fields_dict", 

56 "has", 

57 "is_typeddict", 

58] 

59 

60try: 

61 from typing_extensions import TypedDict as ExtensionsTypedDict 

62except ImportError: # pragma: no cover 

63 ExtensionsTypedDict = None 

64 

65if sys.version_info >= (3, 11): 

66 from builtins import ExceptionGroup 

67else: 

68 from exceptiongroup import ExceptionGroup 

69 

70try: 

71 from typing_extensions import is_typeddict as _is_typeddict 

72except ImportError: # pragma: no cover 

73 assert sys.version_info >= (3, 10) 

74 from typing import is_typeddict as _is_typeddict 

75 

76try: 

77 from typing_extensions import TypeAlias 

78except ImportError: # pragma: no cover 

79 assert sys.version_info >= (3, 11) 

80 from typing import TypeAlias 

81 

82LITERALS = {Literal} 

83try: 

84 from typing_extensions import Literal as teLiteral 

85 

86 LITERALS.add(teLiteral) 

87except ImportError: # pragma: no cover 

88 pass 

89 

90# On some Python versions, `typing_extensions.Any` is different than 

91# `typing.Any`. 

92try: 

93 from typing_extensions import Any as teAny 

94 

95 ANIES = frozenset([Any, teAny]) 

96except ImportError: # pragma: no cover 

97 ANIES = frozenset([Any]) 

98 

99NoneType = type(None) 

100 

101 

102def is_optional(typ: Any) -> bool: 

103 return is_union_type(typ) and NoneType in typ.__args__ and len(typ.__args__) == 2 

104 

105 

106def is_typeddict(cls: Any): 

107 """Thin wrapper around typing(_extensions).is_typeddict""" 

108 return _is_typeddict(getattr(cls, "__origin__", cls)) 

109 

110 

111def has(cls): 

112 return hasattr(cls, "__attrs_attrs__") or hasattr(cls, "__dataclass_fields__") 

113 

114 

115def has_with_generic(cls): 

116 """Test whether the class if a normal or generic attrs or dataclass.""" 

117 return has(cls) or has(get_origin(cls)) 

118 

119 

120def fields(type): 

121 try: 

122 return type.__attrs_attrs__ 

123 except AttributeError: 

124 return dataclass_fields(type) 

125 

126 

127def fields_dict(type) -> dict[str, Union[Attribute, Field]]: 

128 """Return the fields_dict for attrs and dataclasses.""" 

129 if is_dataclass(type): 

130 return {f.name: f for f in dataclass_fields(type)} 

131 return attrs_fields_dict(type) 

132 

133 

134def adapted_fields(cl: type) -> list[Attribute]: 

135 """Return the attrs format of `fields()` for attrs and dataclasses. 

136 

137 Resolves `attrs` stringified annotations, if present. 

138 """ 

139 if is_dataclass(cl): 

140 attrs = dataclass_fields(cl) 

141 if any(isinstance(a.type, str) for a in attrs): 

142 # Do this conditionally in case `get_type_hints` fails, so 

143 # users can resolve on their own first. 

144 type_hints = get_type_hints(cl) 

145 else: 

146 type_hints = {} 

147 return [ 

148 Attribute( 

149 attr.name, 

150 ( 

151 attr.default 

152 if attr.default is not MISSING 

153 else ( 

154 Factory(attr.default_factory) 

155 if attr.default_factory is not MISSING 

156 else NOTHING 

157 ) 

158 ), 

159 None, 

160 True, 

161 None, 

162 True, 

163 attr.init, 

164 True, 

165 type=type_hints.get(attr.name, attr.type), 

166 alias=attr.name, 

167 kw_only=getattr(attr, "kw_only", False), 

168 ) 

169 for attr in attrs 

170 ] 

171 attribs = attrs_fields(cl) 

172 if any(isinstance(a.type, str) for a in attribs): 

173 # PEP 563 annotations - need to be resolved. 

174 resolve_types(cl) 

175 attribs = attrs_fields(cl) 

176 return attribs 

177 

178 

179def is_subclass(obj: type, bases) -> bool: 

180 """A safe version of issubclass (won't raise).""" 

181 try: 

182 return issubclass(obj, bases) 

183 except TypeError: 

184 return False 

185 

186 

187def is_hetero_tuple(type: Any) -> bool: 

188 origin = getattr(type, "__origin__", None) 

189 return origin is tuple and ... not in type.__args__ 

190 

191 

192def is_protocol(type: Any) -> bool: 

193 return is_subclass(type, Protocol) and getattr(type, "_is_protocol", False) 

194 

195 

196def is_bare_final(type) -> bool: 

197 return type is Final 

198 

199 

200def get_final_base(type) -> Optional[type]: 

201 """Return the base of the Final annotation, if it is Final.""" 

202 if type is Final: 

203 return Any 

204 if type.__class__ is _GenericAlias and type.__origin__ is Final: 

205 return type.__args__[0] 

206 return None 

207 

208 

209OriginAbstractSet = AbcSet 

210OriginMutableSet = AbcMutableSet 

211 

212signature = _signature 

213 

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

215 signature = partial(_signature, eval_str=True) 

216 

217 

218try: 

219 # Not present on 3.9.0, so we try carefully. 

220 from typing import _LiteralGenericAlias 

221 

222 def is_literal(type: Any) -> bool: 

223 """Is this a literal?""" 

224 return type in LITERALS or ( 

225 isinstance( 

226 type, (_GenericAlias, _LiteralGenericAlias, _SpecialGenericAlias) 

227 ) 

228 and type.__origin__ in LITERALS 

229 ) 

230 

231except ImportError: # pragma: no cover 

232 

233 def is_literal(_) -> bool: 

234 return False 

235 

236 

237Set = AbcSet 

238MutableSet = AbcMutableSet 

239Sequence = AbcSequence 

240MutableSequence = AbcMutableSequence 

241MutableMapping = AbcMutableMapping 

242Mapping = AbcMapping 

243FrozenSetSubscriptable = frozenset 

244TupleSubscriptable = tuple 

245 

246 

247def is_annotated(type) -> bool: 

248 return getattr(type, "__class__", None) is _AnnotatedAlias 

249 

250 

251def is_tuple(type): 

252 return ( 

253 type in (Tuple, tuple) 

254 or (type.__class__ is _GenericAlias and is_subclass(type.__origin__, Tuple)) 

255 or (getattr(type, "__origin__", None) is tuple) 

256 ) 

257 

258 

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

260 

261 def is_union_type(obj): 

262 from types import UnionType 

263 

264 return ( 

265 obj is Union 

266 or (isinstance(obj, _UnionGenericAlias) and obj.__origin__ is Union) 

267 or isinstance(obj, UnionType) 

268 ) 

269 

270 def get_newtype_base(typ: Any) -> Optional[type]: 

271 if typ is NewType or isinstance(typ, NewType): 

272 return typ.__supertype__ 

273 return None 

274 

275 if sys.version_info >= (3, 11): 

276 from typing import NotRequired, Required 

277 else: 

278 from typing_extensions import NotRequired, Required 

279 

280else: 

281 # 3.9 

282 from typing_extensions import NotRequired, Required 

283 

284 def is_union_type(obj): 

285 return obj is Union or ( 

286 isinstance(obj, _UnionGenericAlias) and obj.__origin__ is Union 

287 ) 

288 

289 def get_newtype_base(typ: Any) -> Optional[type]: 

290 supertype = getattr(typ, "__supertype__", None) 

291 if ( 

292 supertype is not None 

293 and getattr(typ, "__qualname__", "") == "NewType.<locals>.new_type" 

294 and typ.__module__ in ("typing", "typing_extensions") 

295 ): 

296 return supertype 

297 return None 

298 

299 

300def get_notrequired_base(type) -> Union[Any, NothingType]: 

301 if is_annotated(type): 

302 # Handle `Annotated[NotRequired[int]]` 

303 type = get_args(type)[0] 

304 if get_origin(type) in (NotRequired, Required): 

305 return get_args(type)[0] 

306 return NOTHING 

307 

308 

309def is_sequence(type: Any) -> bool: 

310 """A predicate function for sequences. 

311 

312 Matches lists, sequences, mutable sequences, deques and homogenous 

313 tuples. 

314 """ 

315 origin = getattr(type, "__origin__", None) 

316 return ( 

317 type 

318 in ( 

319 List, 

320 list, 

321 TypingSequence, 

322 TypingMutableSequence, 

323 AbcMutableSequence, 

324 tuple, 

325 Tuple, 

326 deque, 

327 Deque, 

328 ) 

329 or ( 

330 type.__class__ is _GenericAlias 

331 and ( 

332 ((origin is not tuple) and is_subclass(origin, TypingSequence)) 

333 or (origin is tuple and type.__args__[1] is ...) 

334 ) 

335 ) 

336 or (origin in (list, deque, AbcMutableSequence, AbcSequence)) 

337 or (origin is tuple and type.__args__[1] is ...) 

338 ) 

339 

340 

341def is_deque(type): 

342 return ( 

343 type in (deque, Deque) 

344 or (type.__class__ is _GenericAlias and is_subclass(type.__origin__, deque)) 

345 or (getattr(type, "__origin__", None) is deque) 

346 ) 

347 

348 

349def is_mutable_set(type: Any) -> bool: 

350 """A predicate function for (mutable) sets. 

351 

352 Matches built-in sets and sets from the typing module. 

353 """ 

354 return ( 

355 type in (TypingSet, TypingMutableSet, set) 

356 or ( 

357 type.__class__ is _GenericAlias 

358 and is_subclass(type.__origin__, TypingMutableSet) 

359 ) 

360 or (getattr(type, "__origin__", None) in (set, AbcMutableSet, AbcSet)) 

361 ) 

362 

363 

364def is_frozenset(type: Any) -> bool: 

365 """A predicate function for frozensets. 

366 

367 Matches built-in frozensets and frozensets from the typing module. 

368 """ 

369 return ( 

370 type in (FrozenSet, frozenset) 

371 or (type.__class__ is _GenericAlias and is_subclass(type.__origin__, FrozenSet)) 

372 or (getattr(type, "__origin__", None) is frozenset) 

373 ) 

374 

375 

376def is_bare(type): 

377 return isinstance(type, _SpecialGenericAlias) or ( 

378 not hasattr(type, "__origin__") and not hasattr(type, "__args__") 

379 ) 

380 

381 

382def is_mapping(type: Any) -> bool: 

383 """A predicate function for mappings.""" 

384 return ( 

385 type in (dict, Dict, TypingMapping, TypingMutableMapping, AbcMutableMapping) 

386 or ( 

387 type.__class__ is _GenericAlias 

388 and is_subclass(type.__origin__, TypingMapping) 

389 ) 

390 or is_subclass( 

391 getattr(type, "__origin__", type), (dict, AbcMutableMapping, AbcMapping) 

392 ) 

393 ) 

394 

395 

396def is_counter(type): 

397 return ( 

398 type in (Counter, TypingCounter) or getattr(type, "__origin__", None) is Counter 

399 ) 

400 

401 

402def is_generic(type) -> bool: 

403 """Whether `type` is a generic type.""" 

404 # Inheriting from protocol will inject `Generic` into the MRO 

405 # without `__orig_bases__`. 

406 return isinstance(type, (_GenericAlias, GenericAlias)) or ( 

407 is_subclass(type, Generic) and hasattr(type, "__orig_bases__") 

408 ) 

409 

410 

411def copy_with(type, args): 

412 """Replace a generic type's arguments.""" 

413 if is_annotated(type): 

414 # typing.Annotated requires a special case. 

415 return Annotated[args] 

416 if isinstance(args, tuple) and len(args) == 1: 

417 # Some annotations can't handle 1-tuples. 

418 args = args[0] 

419 return type.__origin__[args] 

420 

421 

422def get_full_type_hints(obj, globalns=None, localns=None): 

423 return get_type_hints(obj, globalns, localns, include_extras=True) 

424 

425 

426def is_generic_attrs(type) -> bool: 

427 """Return True for both specialized (A[int]) and unspecialized (A) generics.""" 

428 return is_generic(type) and has(type.__origin__)