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

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

164 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 get_args, 

33 get_origin, 

34 get_type_hints, 

35) 

36from typing import Counter as TypingCounter 

37from typing import Mapping as TypingMapping 

38from typing import MutableMapping as TypingMutableMapping 

39from typing import MutableSequence as TypingMutableSequence 

40from typing import MutableSet as TypingMutableSet 

41from typing import Sequence as TypingSequence 

42from typing import Set as TypingSet 

43 

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

45from attrs import fields as attrs_fields 

46from attrs import fields_dict as attrs_fields_dict 

47 

48__all__ = [ 

49 "ANIES", 

50 "ExceptionGroup", 

51 "ExtensionsTypedDict", 

52 "TypeAlias", 

53 "adapted_fields", 

54 "fields_dict", 

55 "has", 

56 "is_typeddict", 

57] 

58 

59try: 

60 from typing_extensions import TypedDict as ExtensionsTypedDict 

61except ImportError: # pragma: no cover 

62 ExtensionsTypedDict = None 

63 

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

65 from builtins import ExceptionGroup 

66else: 

67 from exceptiongroup import ExceptionGroup 

68 

69try: 

70 from typing_extensions import is_typeddict as _is_typeddict 

71except ImportError: # pragma: no cover 

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

73 from typing import is_typeddict as _is_typeddict 

74 

75try: 

76 from typing_extensions import TypeAlias 

77except ImportError: # pragma: no cover 

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

79 from typing import TypeAlias 

80 

81LITERALS = {Literal} 

82try: 

83 from typing_extensions import Literal as teLiteral 

84 

85 LITERALS.add(teLiteral) 

86except ImportError: # pragma: no cover 

87 pass 

88 

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

90# `typing.Any`. 

91try: 

92 from typing_extensions import Any as teAny 

93 

94 ANIES = frozenset([Any, teAny]) 

95except ImportError: # pragma: no cover 

96 ANIES = frozenset([Any]) 

97 

98NoneType = type(None) 

99 

100 

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

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

103 

104 

105def is_typeddict(cls: Any): 

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

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

108 

109 

110def has(cls): 

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

112 

113 

114def has_with_generic(cls): 

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

116 return has(cls) or has(get_origin(cls)) 

117 

118 

119def fields(type): 

120 try: 

121 return type.__attrs_attrs__ 

122 except AttributeError: 

123 return dataclass_fields(type) 

124 

125 

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

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

128 if is_dataclass(type): 

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

130 return attrs_fields_dict(type) 

131 

132 

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

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

135 

136 Resolves `attrs` stringified annotations, if present. 

137 """ 

138 if is_dataclass(cl): 

139 attrs = dataclass_fields(cl) 

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

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

142 # users can resolve on their own first. 

143 type_hints = get_type_hints(cl) 

144 else: 

145 type_hints = {} 

146 return [ 

147 Attribute( 

148 attr.name, 

149 ( 

150 attr.default 

151 if attr.default is not MISSING 

152 else ( 

153 Factory(attr.default_factory) 

154 if attr.default_factory is not MISSING 

155 else NOTHING 

156 ) 

157 ), 

158 None, 

159 True, 

160 None, 

161 True, 

162 attr.init, 

163 True, 

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

165 alias=attr.name, 

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

167 ) 

168 for attr in attrs 

169 ] 

170 attribs = attrs_fields(cl) 

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

172 # PEP 563 annotations - need to be resolved. 

173 resolve_types(cl) 

174 attribs = attrs_fields(cl) 

175 return attribs 

176 

177 

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

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

180 try: 

181 return issubclass(obj, bases) 

182 except TypeError: 

183 return False 

184 

185 

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

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

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

189 

190 

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

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

193 

194 

195def is_bare_final(type) -> bool: 

196 return type is Final 

197 

198 

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

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

201 if type is Final: 

202 return Any 

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

204 return type.__args__[0] 

205 return None 

206 

207 

208OriginAbstractSet = AbcSet 

209OriginMutableSet = AbcMutableSet 

210 

211signature = partial(_signature, eval_str=True) 

212 

213 

214try: 

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

216 from typing import _LiteralGenericAlias 

217 

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

219 """Is this a literal?""" 

220 return type in LITERALS or ( 

221 isinstance( 

222 type, (_GenericAlias, _LiteralGenericAlias, _SpecialGenericAlias) 

223 ) 

224 and type.__origin__ in LITERALS 

225 ) 

226 

227except ImportError: # pragma: no cover 

228 

229 def is_literal(_) -> bool: 

230 return False 

231 

232 

233Set = AbcSet 

234MutableSet = AbcMutableSet 

235Sequence = AbcSequence 

236MutableSequence = AbcMutableSequence 

237MutableMapping = AbcMutableMapping 

238Mapping = AbcMapping 

239FrozenSetSubscriptable = frozenset 

240TupleSubscriptable = tuple 

241 

242 

243def is_annotated(type) -> bool: 

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

245 

246 

247def is_tuple(type): 

248 return ( 

249 type in (Tuple, tuple) 

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

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

252 ) 

253 

254 

255if sys.version_info >= (3, 14): 

256 

257 def is_union_type(obj): 

258 from types import UnionType # noqa: PLC0415 

259 

260 return obj is Union or isinstance(obj, UnionType) 

261 

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

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

264 return typ.__supertype__ 

265 return None 

266 

267 from typing import NotRequired, Required 

268 

269else: 

270 from typing import _UnionGenericAlias 

271 

272 def is_union_type(obj): 

273 from types import UnionType # noqa: PLC0415 

274 

275 return ( 

276 obj is Union 

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

278 or isinstance(obj, UnionType) 

279 ) 

280 

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

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

283 return typ.__supertype__ 

284 return None 

285 

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

287 from typing import NotRequired, Required 

288 else: 

289 from typing_extensions import NotRequired, Required 

290 

291 

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

293 if is_annotated(type): 

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

295 type = get_args(type)[0] 

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

297 return get_args(type)[0] 

298 return NOTHING 

299 

300 

301def is_mutable_sequence(type: Any) -> bool: 

302 """A predicate function for mutable sequences. 

303 

304 Matches lists, mutable sequences, and deques. 

305 """ 

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

307 return ( 

308 type in (List, list, TypingMutableSequence, AbcMutableSequence, deque, Deque) 

309 or ( 

310 type.__class__ is _GenericAlias 

311 and ( 

312 ((origin is not tuple) and is_subclass(origin, TypingMutableSequence)) 

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

314 ) 

315 ) 

316 or (origin in (list, deque, AbcMutableSequence)) 

317 ) 

318 

319 

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

321 """A predicate function for sequences. 

322 

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

324 tuples. 

325 """ 

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

327 return is_mutable_sequence(type) or ( 

328 type in (TypingSequence, tuple, Tuple) 

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 is 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 ( 

407 isinstance(type, (_GenericAlias, GenericAlias)) 

408 or (is_subclass(type, Generic) and hasattr(type, "__orig_bases__")) 

409 or type.__class__ is Union # On 3.14, unions are no longer typing._GenericAlias 

410 ) 

411 

412 

413def copy_with(type, args): 

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

415 if is_annotated(type): 

416 # typing.Annotated requires a special case. 

417 return Annotated[args] 

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

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

420 args = args[0] 

421 return type.__origin__[args] 

422 

423 

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

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

426 

427 

428def is_generic_attrs(type) -> bool: 

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

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