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

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

551 statements  

1from __future__ import annotations 

2 

3from collections import Counter, deque 

4from collections.abc import Callable, Iterable 

5from collections.abc import Mapping as AbcMapping 

6from collections.abc import MutableMapping as AbcMutableMapping 

7from dataclasses import Field 

8from enum import Enum 

9from inspect import Signature 

10from inspect import signature as inspect_signature 

11from pathlib import Path 

12from typing import Any, Optional, Tuple, TypeVar, overload 

13 

14from attrs import Attribute, resolve_types 

15from attrs import has as attrs_has 

16from typing_extensions import Self 

17 

18try: 

19 from annotationlib import ForwardRef as AnnotationForwardRef 

20except ImportError: 

21 AnnotationForwardRef = None 

22 

23from ._compat import ( 

24 ANIES, 

25 FrozenSetSubscriptable, 

26 Mapping, 

27 MutableMapping, 

28 MutableSequence, 

29 NoneType, 

30 OriginAbstractSet, 

31 OriginMutableSet, 

32 Sequence, 

33 Set, 

34 TypeAlias, 

35 fields, 

36 get_final_base, 

37 get_newtype_base, 

38 get_origin, 

39 has, 

40 has_with_generic, 

41 is_annotated, 

42 is_bare, 

43 is_counter, 

44 is_deque, 

45 is_frozenset, 

46 is_generic, 

47 is_generic_attrs, 

48 is_hetero_tuple, 

49 is_literal, 

50 is_mapping, 

51 is_mutable_sequence, 

52 is_mutable_set, 

53 is_optional, 

54 is_protocol, 

55 is_subclass, 

56 is_tuple, 

57 is_typeddict, 

58 is_union_type, 

59 signature, 

60) 

61from .cols import ( 

62 defaultdict_structure_factory, 

63 homogenous_tuple_structure_factory, 

64 is_abstract_set, 

65 is_defaultdict, 

66 is_namedtuple, 

67 is_sequence, 

68 iterable_unstructure_factory, 

69 list_structure_factory, 

70 mapping_structure_factory, 

71 mapping_unstructure_factory, 

72 namedtuple_structure_factory, 

73 namedtuple_unstructure_factory, 

74) 

75from .disambiguators import create_default_dis_func, is_supported_union 

76from .dispatch import ( 

77 HookFactory, 

78 MultiStrategyDispatch, 

79 StructuredValue, 

80 StructureHook, 

81 TargetType, 

82 UnstructuredValue, 

83 UnstructureHook, 

84) 

85from .enums import enum_structure_factory, enum_unstructure_factory 

86from .errors import ( 

87 CattrsError, 

88 IterableValidationError, 

89 IterableValidationNote, 

90 StructureHandlerNotFoundError, 

91) 

92from .fns import Predicate, identity, raise_error 

93from .gen import ( 

94 AttributeOverride, 

95 HeteroTupleStructureFn, 

96 HeteroTupleUnstructureFn, 

97 IterableUnstructureFn, 

98 MappingUnstructureFn, 

99 make_dict_structure_fn, 

100 make_dict_unstructure_fn, 

101 make_hetero_tuple_structure_fn, 

102 make_hetero_tuple_unstructure_fn, 

103) 

104from .gen.typeddicts import make_dict_structure_fn as make_typeddict_dict_struct_fn 

105from .gen.typeddicts import make_dict_unstructure_fn as make_typeddict_dict_unstruct_fn 

106from .literals import is_literal_containing_enums 

107from .typealiases import ( 

108 get_type_alias_base, 

109 is_type_alias, 

110 type_alias_structure_factory, 

111) 

112from .types import SimpleStructureHook 

113 

114__all__ = ["BaseConverter", "Converter", "GenConverter", "UnstructureStrategy"] 

115 

116T = TypeVar("T") 

117V = TypeVar("V") 

118 

119UnstructureHookFactory = TypeVar( 

120 "UnstructureHookFactory", bound=HookFactory[UnstructureHook] 

121) 

122 

123# The Extended factory also takes a converter. 

124ExtendedUnstructureHookFactory: TypeAlias = Callable[[TargetType, T], UnstructureHook] 

125 

126# This typevar for the BaseConverter. 

127AnyUnstructureHookFactoryBase = TypeVar( 

128 "AnyUnstructureHookFactoryBase", 

129 bound="HookFactory[UnstructureHook] | ExtendedUnstructureHookFactory[BaseConverter]", 

130) 

131 

132# This typevar for the Converter. 

133AnyUnstructureHookFactory = TypeVar( 

134 "AnyUnstructureHookFactory", 

135 bound="HookFactory[UnstructureHook] | ExtendedUnstructureHookFactory[Converter]", 

136) 

137 

138StructureHookFactory = TypeVar("StructureHookFactory", bound=HookFactory[StructureHook]) 

139 

140# The Extended factory also takes a converter. 

141ExtendedStructureHookFactory: TypeAlias = Callable[[TargetType, T], StructureHook] 

142 

143# This typevar for the BaseConverter. 

144AnyStructureHookFactoryBase = TypeVar( 

145 "AnyStructureHookFactoryBase", 

146 bound="HookFactory[StructureHook] | ExtendedStructureHookFactory[BaseConverter]", 

147) 

148 

149# This typevar for the Converter. 

150AnyStructureHookFactory = TypeVar( 

151 "AnyStructureHookFactory", 

152 bound="HookFactory[StructureHook] | ExtendedStructureHookFactory[Converter]", 

153) 

154 

155UnstructureHookT = TypeVar("UnstructureHookT", bound=UnstructureHook) 

156StructureHookT = TypeVar("StructureHookT", bound=StructureHook) 

157CounterT = TypeVar("CounterT", bound=Counter) 

158 

159 

160class UnstructureStrategy(Enum): 

161 """`attrs` classes unstructuring strategies.""" 

162 

163 AS_DICT = "asdict" 

164 AS_TUPLE = "astuple" 

165 

166 

167def _is_extended_factory(factory: Callable) -> bool: 

168 """Does this factory also accept a converter arg?""" 

169 # We use the original `inspect.signature` to not evaluate string 

170 # annotations. 

171 sig = inspect_signature(factory) 

172 return ( 

173 len(sig.parameters) >= 2 

174 and (list(sig.parameters.values())[1]).default is Signature.empty 

175 ) 

176 

177 

178class BaseConverter: 

179 """Converts between structured and unstructured data.""" 

180 

181 __slots__ = ( 

182 "_dict_factory", 

183 "_prefer_attrib_converters", 

184 "_struct_copy_skip", 

185 "_structure_attrs", 

186 "_structure_func", 

187 "_union_struct_registry", 

188 "_unstruct_copy_skip", 

189 "_unstructure_attrs", 

190 "_unstructure_func", 

191 "detailed_validation", 

192 ) 

193 

194 def __init__( 

195 self, 

196 dict_factory: Callable[[], Any] = dict, 

197 unstruct_strat: UnstructureStrategy = UnstructureStrategy.AS_DICT, 

198 prefer_attrib_converters: bool = False, 

199 detailed_validation: bool = True, 

200 unstructure_fallback_factory: HookFactory[UnstructureHook] = lambda _: identity, 

201 structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error( 

202 None, t 

203 ), 

204 ) -> None: 

205 """ 

206 :param detailed_validation: Whether to use a slightly slower mode for detailed 

207 validation errors. 

208 :param unstructure_fallback_factory: A hook factory to be called when no 

209 registered unstructuring hooks match. 

210 :param structure_fallback_factory: A hook factory to be called when no 

211 registered structuring hooks match. 

212 

213 .. versionadded:: 23.2.0 *unstructure_fallback_factory* 

214 .. versionadded:: 23.2.0 *structure_fallback_factory* 

215 .. versionchanged:: 24.2.0 

216 The default `structure_fallback_factory` now raises errors for missing handlers 

217 more eagerly, surfacing problems earlier. 

218 """ 

219 unstruct_strat = UnstructureStrategy(unstruct_strat) 

220 self._prefer_attrib_converters = prefer_attrib_converters 

221 

222 self.detailed_validation = detailed_validation 

223 self._union_struct_registry: dict[Any, Callable[[Any, type[T]], T]] = {} 

224 

225 # Create a per-instance cache. 

226 if unstruct_strat is UnstructureStrategy.AS_DICT: 

227 self._unstructure_attrs = self.unstructure_attrs_asdict 

228 self._structure_attrs = self.structure_attrs_fromdict 

229 else: 

230 self._unstructure_attrs = self.unstructure_attrs_astuple 

231 self._structure_attrs = self.structure_attrs_fromtuple 

232 

233 self._unstructure_func = MultiStrategyDispatch( 

234 unstructure_fallback_factory, self 

235 ) 

236 self._unstructure_func.register_cls_list( 

237 [(bytes, identity), (str, identity), (Path, str)] 

238 ) 

239 self._unstructure_func.register_func_list( 

240 [ 

241 ( 

242 lambda t: get_newtype_base(t) is not None, 

243 lambda o: self.unstructure(o, unstructure_as=o.__class__), 

244 ), 

245 ( 

246 is_protocol, 

247 lambda o: self.unstructure(o, unstructure_as=o.__class__), 

248 ), 

249 ( 

250 lambda t: get_final_base(t) is not None, 

251 lambda t: self.get_unstructure_hook(get_final_base(t)), 

252 True, 

253 ), 

254 ( 

255 is_type_alias, 

256 lambda t: self.get_unstructure_hook(get_type_alias_base(t)), 

257 True, 

258 ), 

259 (is_mapping, self._unstructure_mapping), 

260 (is_sequence, self._unstructure_seq), 

261 (is_mutable_set, self._unstructure_seq), 

262 (is_frozenset, self._unstructure_seq), 

263 (is_literal_containing_enums, self.unstructure), 

264 (lambda t: is_subclass(t, Enum), enum_unstructure_factory, "extended"), 

265 (has, self._unstructure_attrs), 

266 (is_union_type, self._unstructure_union), 

267 (lambda t: t in ANIES, self.unstructure), 

268 ] 

269 ) 

270 

271 # Per-instance register of to-attrs converters. 

272 # Singledispatch dispatches based on the first argument, so we 

273 # store the function and switch the arguments in self.loads. 

274 self._structure_func = MultiStrategyDispatch(structure_fallback_factory, self) 

275 self._structure_func.register_func_list( 

276 [ 

277 ( 

278 lambda cl: cl in ANIES or cl is Optional or cl is None, 

279 lambda v, _: v, 

280 ), 

281 (is_generic_attrs, self._gen_structure_generic, True), 

282 (lambda t: get_newtype_base(t) is not None, self._structure_newtype), 

283 (is_type_alias, type_alias_structure_factory, "extended"), 

284 ( 

285 lambda t: get_final_base(t) is not None, 

286 self._structure_final_factory, 

287 True, 

288 ), 

289 (is_literal, self._structure_simple_literal), 

290 (is_literal_containing_enums, self._structure_enum_literal), 

291 (is_sequence, homogenous_tuple_structure_factory, "extended"), 

292 (is_mutable_sequence, list_structure_factory, "extended"), 

293 (is_deque, self._structure_deque), 

294 (is_mutable_set, self._structure_set), 

295 (is_abstract_set, self._structure_frozenset), 

296 (is_frozenset, self._structure_frozenset), 

297 (is_tuple, self._structure_tuple), 

298 (is_namedtuple, namedtuple_structure_factory, "extended"), 

299 (is_mapping, self._structure_dict), 

300 *( 

301 [(is_supported_union, self._gen_attrs_union_structure, True)] 

302 if unstruct_strat is UnstructureStrategy.AS_DICT 

303 else [] 

304 ), 

305 (is_optional, self._structure_optional), 

306 ( 

307 lambda t: is_union_type(t) and t in self._union_struct_registry, 

308 self._union_struct_registry.__getitem__, 

309 True, 

310 ), 

311 (lambda t: is_subclass(t, Enum), enum_structure_factory, "extended"), 

312 (has, self._structure_attrs), 

313 ] 

314 ) 

315 # Strings are sequences. 

316 self._structure_func.register_cls_list( 

317 [ 

318 (str, self._structure_call), 

319 (bytes, self._structure_call), 

320 (int, self._structure_call), 

321 (float, self._structure_call), 

322 (Path, self._structure_call), 

323 ] 

324 ) 

325 

326 self._dict_factory = dict_factory 

327 

328 self._unstruct_copy_skip = self._unstructure_func.get_num_fns() 

329 self._struct_copy_skip = self._structure_func.get_num_fns() 

330 

331 def unstructure(self, obj: Any, unstructure_as: Any = None) -> Any: 

332 return self._unstructure_func.dispatch( 

333 obj.__class__ if unstructure_as is None else unstructure_as 

334 )(obj) 

335 

336 @property 

337 def unstruct_strat(self) -> UnstructureStrategy: 

338 """The default way of unstructuring ``attrs`` classes.""" 

339 return ( 

340 UnstructureStrategy.AS_DICT 

341 if self._unstructure_attrs == self.unstructure_attrs_asdict 

342 else UnstructureStrategy.AS_TUPLE 

343 ) 

344 

345 @overload 

346 def register_unstructure_hook(self, cls: UnstructureHookT) -> UnstructureHookT: ... 

347 

348 @overload 

349 def register_unstructure_hook(self, cls: Any, func: UnstructureHook) -> None: ... 

350 

351 def register_unstructure_hook( 

352 self, cls: Any = None, func: UnstructureHook | None = None 

353 ) -> Callable[[UnstructureHook]] | None: 

354 """Register a class-to-primitive converter function for a class. 

355 

356 The converter function should take an instance of the class and return 

357 its Python equivalent. 

358 

359 May also be used as a decorator. When used as a decorator, the first 

360 argument annotation from the decorated function will be used as the 

361 type to register the hook for. 

362 

363 .. versionchanged:: 24.1.0 

364 This method may now be used as a decorator. 

365 .. versionchanged:: 25.1.0 

366 Modern type aliases are now supported. 

367 """ 

368 if func is None: 

369 # Autodetecting decorator. 

370 func = cls 

371 sig = signature(func) 

372 cls = next(iter(sig.parameters.values())).annotation 

373 self.register_unstructure_hook(cls, func) 

374 

375 return func 

376 

377 if attrs_has(cls): 

378 resolve_types(cls) 

379 if is_union_type(cls): 

380 self._unstructure_func.register_func_list([(lambda t: t == cls, func)]) 

381 elif is_type_alias(cls): 

382 self._unstructure_func.register_func_list([(lambda t: t is cls, func)]) 

383 elif get_newtype_base(cls) is not None: 

384 # This is a newtype, so we handle it specially. 

385 self._unstructure_func.register_func_list([(lambda t: t is cls, func)]) 

386 else: 

387 self._unstructure_func.register_cls_list([(cls, func)]) 

388 return None 

389 

390 def register_unstructure_hook_func( 

391 self, check_func: Predicate, func: UnstructureHook 

392 ) -> None: 

393 """Register a class-to-primitive converter function for a class, using 

394 a function to check if it's a match. 

395 """ 

396 self._unstructure_func.register_func_list([(check_func, func)]) 

397 

398 @overload 

399 def register_unstructure_hook_factory( 

400 self, predicate: Predicate 

401 ) -> Callable[[AnyUnstructureHookFactoryBase], AnyUnstructureHookFactoryBase]: ... 

402 

403 @overload 

404 def register_unstructure_hook_factory( 

405 self, predicate: Predicate, factory: UnstructureHookFactory 

406 ) -> UnstructureHookFactory: ... 

407 

408 @overload 

409 def register_unstructure_hook_factory( 

410 self, 

411 predicate: Predicate, 

412 factory: ExtendedUnstructureHookFactory[BaseConverter], 

413 ) -> ExtendedUnstructureHookFactory[BaseConverter]: ... 

414 

415 def register_unstructure_hook_factory(self, predicate, factory=None): 

416 """ 

417 Register a hook factory for a given predicate. 

418 

419 The hook factory may expose an additional required parameter. In this case, 

420 the current converter will be provided to the hook factory as that 

421 parameter. 

422 

423 May also be used as a decorator. 

424 

425 :param predicate: A function that, given a type, returns whether the factory 

426 can produce a hook for that type. 

427 :param factory: A callable that, given a type, produces an unstructuring 

428 hook for that type. This unstructuring hook will be cached. 

429 

430 .. versionchanged:: 24.1.0 

431 This method may now be used as a decorator. 

432 The factory may also receive the converter as a second, required argument. 

433 """ 

434 if factory is None: 

435 

436 def decorator(factory): 

437 # Is this an extended factory (takes a converter too)? 

438 if _is_extended_factory(factory): 

439 self._unstructure_func.register_func_list( 

440 [(predicate, factory, "extended")] 

441 ) 

442 else: 

443 self._unstructure_func.register_func_list( 

444 [(predicate, factory, True)] 

445 ) 

446 return factory 

447 

448 return decorator 

449 

450 self._unstructure_func.register_func_list( 

451 [ 

452 ( 

453 predicate, 

454 factory, 

455 "extended" if _is_extended_factory(factory) else True, 

456 ) 

457 ] 

458 ) 

459 return factory 

460 

461 def get_unstructure_hook( 

462 self, type: Any, cache_result: bool = True 

463 ) -> UnstructureHook: 

464 """Get the unstructure hook for the given type. 

465 

466 This hook can be manually called, or composed with other functions 

467 and re-registered. 

468 

469 If no hook is registered, the converter unstructure fallback factory 

470 will be used to produce one. 

471 

472 :param cache: Whether to cache the returned hook. 

473 

474 .. versionadded:: 24.1.0 

475 """ 

476 return ( 

477 self._unstructure_func.dispatch(type) 

478 if cache_result 

479 else self._unstructure_func.dispatch_without_caching(type) 

480 ) 

481 

482 @overload 

483 def register_structure_hook(self, cl: StructureHookT) -> StructureHookT: ... 

484 

485 @overload 

486 def register_structure_hook(self, cl: Any, func: StructureHook) -> None: ... 

487 

488 def register_structure_hook( 

489 self, cl: Any, func: StructureHook | None = None 

490 ) -> None: 

491 """Register a primitive-to-class converter function for a type. 

492 

493 The converter function should take two arguments: 

494 * a Python object to be converted, 

495 * the type to convert to 

496 

497 and return the instance of the class. The type may seem redundant, but 

498 is sometimes needed (for example, when dealing with generic classes). 

499 

500 This method may be used as a decorator. In this case, the decorated 

501 hook must have a return type annotation, and this annotation will be used 

502 as the type for the hook. 

503 

504 .. versionchanged:: 24.1.0 

505 This method may now be used as a decorator. 

506 .. versionchanged:: 25.1.0 

507 Modern type aliases are now supported. 

508 """ 

509 if func is None: 

510 # The autodetecting decorator. 

511 func = cl 

512 sig = signature(func) 

513 self.register_structure_hook(sig.return_annotation, func) 

514 return func 

515 

516 if attrs_has(cl): 

517 resolve_types(cl) 

518 if is_union_type(cl): 

519 self._union_struct_registry[cl] = func 

520 self._structure_func.clear_cache() 

521 elif is_type_alias(cl): 

522 # Type aliases are special-cased. 

523 self._structure_func.register_func_list([(lambda t: t is cl, func)]) 

524 elif get_newtype_base(cl) is not None: 

525 # This is a newtype, so we handle it specially. 

526 self._structure_func.register_func_list([(lambda t: t is cl, func)]) 

527 else: 

528 self._structure_func.register_cls_list([(cl, func)]) 

529 return None 

530 

531 def register_structure_hook_func( 

532 self, check_func: Predicate, func: StructureHook 

533 ) -> None: 

534 """Register a class-to-primitive converter function for a class, using 

535 a function to check if it's a match. 

536 """ 

537 self._structure_func.register_func_list([(check_func, func)]) 

538 

539 @overload 

540 def register_structure_hook_factory( 

541 self, predicate: Predicate 

542 ) -> Callable[[AnyStructureHookFactoryBase], AnyStructureHookFactoryBase]: ... 

543 

544 @overload 

545 def register_structure_hook_factory( 

546 self, predicate: Predicate, factory: StructureHookFactory 

547 ) -> StructureHookFactory: ... 

548 

549 @overload 

550 def register_structure_hook_factory( 

551 self, predicate: Predicate, factory: ExtendedStructureHookFactory[BaseConverter] 

552 ) -> ExtendedStructureHookFactory[BaseConverter]: ... 

553 

554 def register_structure_hook_factory(self, predicate, factory=None): 

555 """ 

556 Register a hook factory for a given predicate. 

557 

558 The hook factory may expose an additional required parameter. In this case, 

559 the current converter will be provided to the hook factory as that 

560 parameter. 

561 

562 May also be used as a decorator. 

563 

564 :param predicate: A function that, given a type, returns whether the factory 

565 can produce a hook for that type. 

566 :param factory: A callable that, given a type, produces a structuring 

567 hook for that type. This structuring hook will be cached. 

568 

569 .. versionchanged:: 24.1.0 

570 This method may now be used as a decorator. 

571 The factory may also receive the converter as a second, required argument. 

572 """ 

573 if factory is None: 

574 # Decorator use. 

575 def decorator(factory): 

576 # Is this an extended factory (takes a converter too)? 

577 if _is_extended_factory(factory): 

578 self._structure_func.register_func_list( 

579 [(predicate, factory, "extended")] 

580 ) 

581 else: 

582 self._structure_func.register_func_list( 

583 [(predicate, factory, True)] 

584 ) 

585 return factory 

586 

587 return decorator 

588 self._structure_func.register_func_list( 

589 [ 

590 ( 

591 predicate, 

592 factory, 

593 "extended" if _is_extended_factory(factory) else True, 

594 ) 

595 ] 

596 ) 

597 return factory 

598 

599 def structure(self, obj: UnstructuredValue, cl: type[T]) -> T: 

600 """Convert unstructured Python data structures to structured data.""" 

601 return self._structure_func.dispatch(cl)(obj, cl) 

602 

603 def get_structure_hook(self, type: Any, cache_result: bool = True) -> StructureHook: 

604 """Get the structure hook for the given type. 

605 

606 This hook can be manually called, or composed with other functions 

607 and re-registered. 

608 

609 If no hook is registered, the converter structure fallback factory 

610 will be used to produce one. 

611 

612 :param cache: Whether to cache the returned hook. 

613 

614 .. versionadded:: 24.1.0 

615 """ 

616 return ( 

617 self._structure_func.dispatch(type) 

618 if cache_result 

619 else self._structure_func.dispatch_without_caching(type) 

620 ) 

621 

622 # Classes to Python primitives. 

623 def unstructure_attrs_asdict(self, obj: Any) -> dict[str, Any]: 

624 """Our version of `attrs.asdict`, so we can call back to us.""" 

625 attrs = fields(obj.__class__) 

626 dispatch = self._unstructure_func.dispatch 

627 rv = self._dict_factory() 

628 for a in attrs: 

629 name = a.name 

630 v = getattr(obj, name) 

631 rv[name] = dispatch(a.type or v.__class__)(v) 

632 return rv 

633 

634 def unstructure_attrs_astuple(self, obj: Any) -> tuple[Any, ...]: 

635 """Our version of `attrs.astuple`, so we can call back to us.""" 

636 attrs = fields(obj.__class__) 

637 dispatch = self._unstructure_func.dispatch 

638 res = [] 

639 for a in attrs: 

640 name = a.name 

641 v = getattr(obj, name) 

642 res.append(dispatch(a.type or v.__class__)(v)) 

643 return tuple(res) 

644 

645 def _unstructure_seq(self, seq: Sequence[T]) -> Sequence[T]: 

646 """Convert a sequence to primitive equivalents.""" 

647 # We can reuse the sequence class, so tuples stay tuples. 

648 dispatch = self._unstructure_func.dispatch 

649 return seq.__class__(dispatch(e.__class__)(e) for e in seq) 

650 

651 def _unstructure_mapping(self, mapping: Mapping[T, V]) -> Mapping[T, V]: 

652 """Convert a mapping of attr classes to primitive equivalents.""" 

653 

654 # We can reuse the mapping class, so dicts stay dicts and OrderedDicts 

655 # stay OrderedDicts. 

656 dispatch = self._unstructure_func.dispatch 

657 return mapping.__class__( 

658 (dispatch(k.__class__)(k), dispatch(v.__class__)(v)) 

659 for k, v in mapping.items() 

660 ) 

661 

662 # note: Use UnionType when 3.11 is released as 

663 # the behaviour of @final is changed. This would 

664 # affect how we can support UnionType in ._compat.py 

665 def _unstructure_union(self, obj: Any) -> Any: 

666 """ 

667 Unstructure an object as a union. 

668 

669 By default, just unstructures the instance. 

670 """ 

671 return self._unstructure_func.dispatch(obj.__class__)(obj) 

672 

673 # Python primitives to classes. 

674 

675 def _gen_structure_generic( 

676 self, cl: type[T] 

677 ) -> SimpleStructureHook[Mapping[str, Any], T]: 

678 """Create and return a hook for structuring generics.""" 

679 return make_dict_structure_fn( 

680 cl, self, _cattrs_prefer_attrib_converters=self._prefer_attrib_converters 

681 ) 

682 

683 def _gen_attrs_union_structure( 

684 self, cl: Any, use_literals: bool = True 

685 ) -> Callable[[Any, type[T]], type[T] | None]: 

686 """ 

687 Generate a structuring function for a union of attrs classes (and maybe None). 

688 

689 :param use_literals: Whether to consider literal fields. 

690 """ 

691 dis_fn = self._get_dis_func(cl, use_literals=use_literals) 

692 has_none = NoneType in cl.__args__ 

693 

694 if has_none: 

695 

696 def structure_attrs_union(obj, _) -> cl: 

697 if obj is None: 

698 return None 

699 return self.structure(obj, dis_fn(obj)) 

700 

701 else: 

702 

703 def structure_attrs_union(obj, _): 

704 return self.structure(obj, dis_fn(obj)) 

705 

706 return structure_attrs_union 

707 

708 @staticmethod 

709 def _structure_call(obj: Any, cl: type[T]) -> Any: 

710 """Just call ``cl`` with the given ``obj``. 

711 

712 This is just an optimization on the ``_structure_default`` case, when 

713 we know we can skip the ``if`` s. Use for ``str``, ``bytes``, ``enum``, 

714 etc. 

715 """ 

716 return cl(obj) 

717 

718 @staticmethod 

719 def _structure_simple_literal(val, type): 

720 if val not in type.__args__: 

721 raise CattrsError(f"{val} not in literal {type}") 

722 return val 

723 

724 @staticmethod 

725 def _structure_enum_literal(val, type): 

726 vals = {(x.value if isinstance(x, Enum) else x): x for x in type.__args__} 

727 try: 

728 return vals[val] 

729 except KeyError: 

730 raise CattrsError(f"{val} not in literal {type}") from None 

731 

732 def _structure_newtype(self, val: UnstructuredValue, type) -> StructuredValue: 

733 base = get_newtype_base(type) 

734 return self.get_structure_hook(base)(val, base) 

735 

736 def _structure_final_factory(self, type): 

737 base = get_final_base(type) 

738 res = self.get_structure_hook(base) 

739 return lambda v, _, __base=base: res(v, __base) 

740 

741 # Attrs classes. 

742 

743 def structure_attrs_fromtuple(self, obj: tuple[Any, ...], cl: type[T]) -> T: 

744 """Load an attrs class from a sequence (tuple).""" 

745 conv_obj = [] # A list of converter parameters. 

746 for a, value in zip(fields(cl), obj): 

747 # We detect the type by the metadata. 

748 converted = self._structure_attribute(a, value) 

749 conv_obj.append(converted) 

750 

751 return cl(*conv_obj) 

752 

753 def _structure_attribute(self, a: Attribute | Field, value: Any) -> Any: 

754 """Handle an individual attrs attribute.""" 

755 type_ = a.type 

756 attrib_converter = getattr(a, "converter", None) 

757 if self._prefer_attrib_converters and attrib_converter: 

758 # A attrib converter is defined on this attribute, and 

759 # prefer_attrib_converters is set to give these priority over registered 

760 # structure hooks. So, pass through the raw value, which attrs will flow 

761 # into the converter 

762 return value 

763 if type_ is None: 

764 # No type metadata. 

765 return value 

766 

767 try: 

768 return self._structure_func.dispatch(type_)(value, type_) 

769 except StructureHandlerNotFoundError: 

770 if attrib_converter: 

771 # Return the original value and fallback to using an attrib converter. 

772 return value 

773 raise 

774 

775 def structure_attrs_fromdict(self, obj: Mapping[str, Any], cl: type[T]) -> T: 

776 """Instantiate an attrs class from a mapping (dict).""" 

777 # For public use. 

778 

779 conv_obj = {} # Start with a fresh dict, to ignore extra keys. 

780 for a in fields(cl): 

781 try: 

782 val = obj[a.name] 

783 except KeyError: 

784 continue 

785 

786 # try .alias and .name because this code also supports dataclasses! 

787 conv_obj[getattr(a, "alias", a.name)] = self._structure_attribute(a, val) 

788 

789 return cl(**conv_obj) 

790 

791 def _structure_deque(self, obj: Iterable[T], cl: Any) -> deque[T]: 

792 """Convert an iterable to a potentially generic deque.""" 

793 if is_bare(cl) or cl.__args__[0] in ANIES: 

794 res = deque(obj) 

795 else: 

796 elem_type = cl.__args__[0] 

797 handler = self._structure_func.dispatch(elem_type) 

798 if self.detailed_validation: 

799 errors = [] 

800 res = deque() 

801 ix = 0 # Avoid `enumerate` for performance. 

802 for e in obj: 

803 try: 

804 res.append(handler(e, elem_type)) 

805 except Exception as e: 

806 msg = IterableValidationNote( 

807 f"Structuring {cl} @ index {ix}", ix, elem_type 

808 ) 

809 e.__notes__ = [*getattr(e, "__notes__", []), msg] 

810 errors.append(e) 

811 finally: 

812 ix += 1 

813 if errors: 

814 raise IterableValidationError( 

815 f"While structuring {cl!r}", errors, cl 

816 ) 

817 else: 

818 res = deque(handler(e, elem_type) for e in obj) 

819 return res 

820 

821 def _structure_set( 

822 self, obj: Iterable[T], cl: Any, structure_to: type = set 

823 ) -> Set[T]: 

824 """Convert an iterable into a potentially generic set.""" 

825 if is_bare(cl) or cl.__args__[0] in ANIES: 

826 return structure_to(obj) 

827 elem_type = cl.__args__[0] 

828 handler = self._structure_func.dispatch(elem_type) 

829 if self.detailed_validation: 

830 errors = [] 

831 res = set() 

832 ix = 0 

833 for e in obj: 

834 try: 

835 res.add(handler(e, elem_type)) 

836 except Exception as exc: 

837 msg = IterableValidationNote( 

838 f"Structuring {structure_to.__name__} @ element {e!r}", 

839 ix, 

840 elem_type, 

841 ) 

842 exc.__notes__ = [*getattr(exc, "__notes__", []), msg] 

843 errors.append(exc) 

844 finally: 

845 ix += 1 

846 if errors: 

847 raise IterableValidationError(f"While structuring {cl!r}", errors, cl) 

848 return res if structure_to is set else structure_to(res) 

849 if structure_to is set: 

850 return {handler(e, elem_type) for e in obj} 

851 return structure_to([handler(e, elem_type) for e in obj]) 

852 

853 def _structure_frozenset( 

854 self, obj: Iterable[T], cl: Any 

855 ) -> FrozenSetSubscriptable[T]: 

856 """Convert an iterable into a potentially generic frozenset.""" 

857 return self._structure_set(obj, cl, structure_to=frozenset) 

858 

859 def _structure_dict(self, obj: Mapping[T, V], cl: Any) -> dict[T, V]: 

860 """Convert a mapping into a potentially generic dict.""" 

861 if is_bare(cl) or cl.__args__ == (Any, Any): 

862 return dict(obj) 

863 key_type, val_type = cl.__args__ 

864 

865 if self.detailed_validation: 

866 key_handler = self._structure_func.dispatch(key_type) 

867 val_handler = self._structure_func.dispatch(val_type) 

868 errors = [] 

869 res = {} 

870 

871 for k, v in obj.items(): 

872 try: 

873 value = val_handler(v, val_type) 

874 except Exception as exc: 

875 msg = IterableValidationNote( 

876 f"Structuring mapping value @ key {k!r}", k, val_type 

877 ) 

878 exc.__notes__ = [*getattr(exc, "__notes__", []), msg] 

879 errors.append(exc) 

880 continue 

881 

882 try: 

883 key = key_handler(k, key_type) 

884 res[key] = value 

885 except Exception as exc: 

886 msg = IterableValidationNote( 

887 f"Structuring mapping key @ key {k!r}", k, key_type 

888 ) 

889 exc.__notes__ = [*getattr(exc, "__notes__", []), msg] 

890 errors.append(exc) 

891 

892 if errors: 

893 raise IterableValidationError(f"While structuring {cl!r}", errors, cl) 

894 return res 

895 

896 if key_type in ANIES: 

897 val_conv = self._structure_func.dispatch(val_type) 

898 return {k: val_conv(v, val_type) for k, v in obj.items()} 

899 if val_type in ANIES: 

900 key_conv = self._structure_func.dispatch(key_type) 

901 return {key_conv(k, key_type): v for k, v in obj.items()} 

902 key_conv = self._structure_func.dispatch(key_type) 

903 val_conv = self._structure_func.dispatch(val_type) 

904 return {key_conv(k, key_type): val_conv(v, val_type) for k, v in obj.items()} 

905 

906 def _structure_optional(self, obj, union): 

907 if obj is None: 

908 return None 

909 if AnnotationForwardRef is not None and isinstance(union, AnnotationForwardRef): 

910 union = union.evaluate() 

911 union_params = union.__args__ 

912 other = union_params[0] if union_params[1] is NoneType else union_params[1] 

913 # We can't actually have a Union of a Union, so this is safe. 

914 return self._structure_func.dispatch(other)(obj, other) 

915 

916 def gen_structure_hetero_tuple(self, cl: Any) -> HeteroTupleStructureFn: 

917 """Generate a heterogeneous tuple structure function.""" 

918 return make_hetero_tuple_structure_fn( 

919 cl, self, detailed_validation=self.detailed_validation 

920 ) 

921 

922 def _structure_tuple(self, obj: Iterable, tup: type[T]) -> T: 

923 """Deal with structuring into a tuple.""" 

924 tup_params = None if tup in (Tuple, tuple) else tup.__args__ 

925 has_ellipsis = tup_params and tup_params[-1] is Ellipsis 

926 if tup_params is None or (has_ellipsis and tup_params[0] in ANIES): 

927 # Just a Tuple. (No generic information.) 

928 return tuple(obj) 

929 if has_ellipsis: 

930 # We're dealing with a homogeneous tuple, tuple[int, ...] 

931 tup_type = tup_params[0] 

932 conv = self._structure_func.dispatch(tup_type) 

933 if self.detailed_validation: 

934 errors = [] 

935 res = [] 

936 ix = 0 

937 for e in obj: 

938 try: 

939 res.append(conv(e, tup_type)) 

940 except Exception as exc: 

941 msg = IterableValidationNote( 

942 f"Structuring {tup} @ index {ix}", ix, tup_type 

943 ) 

944 exc.__notes__ = [*getattr(exc, "__notes__", []), msg] 

945 errors.append(exc) 

946 finally: 

947 ix += 1 

948 if errors: 

949 raise IterableValidationError( 

950 f"While structuring {tup!r}", errors, tup 

951 ) 

952 return tuple(res) 

953 return tuple(conv(e, tup_type) for e in obj) 

954 

955 # We're dealing with a heterogeneous tuple. 

956 exp_len = len(tup_params) 

957 if self.detailed_validation: 

958 errors = [] 

959 res = [] 

960 for ix, (t, e) in enumerate(zip(tup_params, obj)): 

961 try: 

962 conv = self._structure_func.dispatch(t) 

963 res.append(conv(e, t)) 

964 except Exception as exc: 

965 msg = IterableValidationNote( 

966 f"Structuring {tup} @ index {ix}", ix, t 

967 ) 

968 exc.__notes__ = [*getattr(exc, "__notes__", []), msg] 

969 errors.append(exc) 

970 if len(obj) != exp_len: 

971 problem = "Not enough" if len(res) < exp_len else "Too many" 

972 exc = ValueError(f"{problem} values in {obj!r} to structure as {tup!r}") 

973 msg = f"Structuring {tup}" 

974 exc.__notes__ = [*getattr(exc, "__notes__", []), msg] 

975 errors.append(exc) 

976 if errors: 

977 raise IterableValidationError(f"While structuring {tup!r}", errors, tup) 

978 return tuple(res) 

979 

980 if len(obj) != exp_len: 

981 problem = "Not enough" if len(obj) < len(tup_params) else "Too many" 

982 raise ValueError(f"{problem} values in {obj!r} to structure as {tup!r}") 

983 return tuple( 

984 [self._structure_func.dispatch(t)(e, t) for t, e in zip(tup_params, obj)] 

985 ) 

986 

987 def _get_dis_func( 

988 self, 

989 union: Any, 

990 use_literals: bool = True, 

991 overrides: dict[str, AttributeOverride] | None = None, 

992 ) -> Callable[[Any], type]: 

993 """Fetch or try creating a disambiguation function for a union.""" 

994 union_types = union.__args__ 

995 if NoneType in union_types: 

996 # We support unions of attrs classes and NoneType higher in the 

997 # logic. 

998 union_types = tuple(e for e in union_types if e is not NoneType) 

999 

1000 if not all(has(get_origin(e) or e) for e in union_types): 

1001 raise StructureHandlerNotFoundError( 

1002 "Only unions of attrs classes and dataclasses supported " 

1003 "currently. Register a structure hook manually.", 

1004 type_=union, 

1005 ) 

1006 

1007 return create_default_dis_func( 

1008 self, 

1009 *union_types, 

1010 use_literals=use_literals, 

1011 overrides=overrides if overrides is not None else "from_converter", 

1012 ) 

1013 

1014 def __deepcopy__(self, _) -> BaseConverter: 

1015 return self.copy() 

1016 

1017 def copy( 

1018 self, 

1019 dict_factory: Callable[[], Any] | None = None, 

1020 unstruct_strat: UnstructureStrategy | None = None, 

1021 prefer_attrib_converters: bool | None = None, 

1022 detailed_validation: bool | None = None, 

1023 ) -> Self: 

1024 """Create a copy of the converter, keeping all existing custom hooks. 

1025 

1026 :param detailed_validation: Whether to use a slightly slower mode for detailed 

1027 validation errors. 

1028 """ 

1029 res = self.__class__( 

1030 dict_factory if dict_factory is not None else self._dict_factory, 

1031 ( 

1032 unstruct_strat 

1033 if unstruct_strat is not None 

1034 else ( 

1035 UnstructureStrategy.AS_DICT 

1036 if self._unstructure_attrs == self.unstructure_attrs_asdict 

1037 else UnstructureStrategy.AS_TUPLE 

1038 ) 

1039 ), 

1040 ( 

1041 prefer_attrib_converters 

1042 if prefer_attrib_converters is not None 

1043 else self._prefer_attrib_converters 

1044 ), 

1045 ( 

1046 detailed_validation 

1047 if detailed_validation is not None 

1048 else self.detailed_validation 

1049 ), 

1050 ) 

1051 

1052 self._unstructure_func.copy_to(res._unstructure_func, self._unstruct_copy_skip) 

1053 self._structure_func.copy_to(res._structure_func, self._struct_copy_skip) 

1054 

1055 return res 

1056 

1057 

1058class Converter(BaseConverter): 

1059 """A converter which generates specialized un/structuring functions.""" 

1060 

1061 __slots__ = ( 

1062 "_unstruct_collection_overrides", 

1063 "forbid_extra_keys", 

1064 "omit_if_default", 

1065 "type_overrides", 

1066 "use_alias", 

1067 ) 

1068 

1069 def __init__( 

1070 self, 

1071 dict_factory: Callable[[], Any] = dict, 

1072 unstruct_strat: UnstructureStrategy = UnstructureStrategy.AS_DICT, 

1073 omit_if_default: bool = False, 

1074 forbid_extra_keys: bool = False, 

1075 type_overrides: Mapping[type, AttributeOverride] = {}, 

1076 unstruct_collection_overrides: Mapping[type, UnstructureHook] = {}, 

1077 prefer_attrib_converters: bool = False, 

1078 detailed_validation: bool = True, 

1079 unstructure_fallback_factory: HookFactory[UnstructureHook] = lambda _: identity, 

1080 structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error( 

1081 None, t 

1082 ), 

1083 use_alias: bool = False, 

1084 ): 

1085 """ 

1086 :param detailed_validation: Whether to use a slightly slower mode for detailed 

1087 validation errors. 

1088 :param unstructure_fallback_factory: A hook factory to be called when no 

1089 registered unstructuring hooks match. 

1090 :param structure_fallback_factory: A hook factory to be called when no 

1091 registered structuring hooks match. 

1092 :param use_alias: Whether to use the field alias instead of the field name as 

1093 the un/structured dictionary key by default. 

1094 

1095 .. versionadded:: 23.2.0 *unstructure_fallback_factory* 

1096 .. versionadded:: 23.2.0 *structure_fallback_factory* 

1097 .. versionchanged:: 24.2.0 

1098 The default `structure_fallback_factory` now raises errors for missing handlers 

1099 more eagerly, surfacing problems earlier. 

1100 .. versionadded:: 25.2.0 *use_alias* 

1101 """ 

1102 super().__init__( 

1103 dict_factory=dict_factory, 

1104 unstruct_strat=unstruct_strat, 

1105 prefer_attrib_converters=prefer_attrib_converters, 

1106 detailed_validation=detailed_validation, 

1107 unstructure_fallback_factory=unstructure_fallback_factory, 

1108 structure_fallback_factory=structure_fallback_factory, 

1109 ) 

1110 self.omit_if_default = omit_if_default 

1111 self.forbid_extra_keys = forbid_extra_keys 

1112 self.type_overrides = dict(type_overrides) 

1113 self.use_alias = use_alias 

1114 

1115 unstruct_collection_overrides = { 

1116 get_origin(k) or k: v for k, v in unstruct_collection_overrides.items() 

1117 } 

1118 

1119 self._unstruct_collection_overrides = unstruct_collection_overrides 

1120 

1121 # Do a little post-processing magic to make things easier for users. 

1122 co = unstruct_collection_overrides 

1123 

1124 # abc.Set overrides, if defined, apply to abc.MutableSets and sets 

1125 if OriginAbstractSet in co: 

1126 if OriginMutableSet not in co: 

1127 co[OriginMutableSet] = co[OriginAbstractSet] 

1128 if FrozenSetSubscriptable not in co: 

1129 co[FrozenSetSubscriptable] = co[OriginAbstractSet] 

1130 

1131 # abc.MutableSet overrides, if defined, apply to sets 

1132 if OriginMutableSet in co and set not in co: 

1133 co[set] = co[OriginMutableSet] 

1134 

1135 # abc.Sequence overrides, if defined, can apply to MutableSequences, lists and 

1136 # tuples 

1137 if Sequence in co: 

1138 if MutableSequence not in co: 

1139 co[MutableSequence] = co[Sequence] 

1140 if tuple not in co: 

1141 co[tuple] = co[Sequence] 

1142 

1143 # abc.MutableSequence overrides, if defined, can apply to lists 

1144 if MutableSequence in co: 

1145 if list not in co: 

1146 co[list] = co[MutableSequence] 

1147 if deque not in co: 

1148 co[deque] = co[MutableSequence] 

1149 

1150 # abc.Mapping overrides, if defined, can apply to MutableMappings 

1151 if Mapping in co and MutableMapping not in co: 

1152 co[MutableMapping] = co[Mapping] 

1153 

1154 # abc.MutableMapping overrides, if defined, can apply to dicts 

1155 if MutableMapping in co and dict not in co: 

1156 co[dict] = co[MutableMapping] 

1157 

1158 # builtins.dict overrides, if defined, can apply to counters 

1159 if dict in co and Counter not in co: 

1160 co[Counter] = co[dict] 

1161 

1162 if unstruct_strat is UnstructureStrategy.AS_DICT: 

1163 # Override the attrs handler. 

1164 self.register_unstructure_hook_factory( 

1165 has_with_generic, self.gen_unstructure_attrs_fromdict 

1166 ) 

1167 self.register_structure_hook_factory( 

1168 has_with_generic, self.gen_structure_attrs_fromdict 

1169 ) 

1170 self.register_unstructure_hook_factory( 

1171 is_annotated, self.gen_unstructure_annotated 

1172 ) 

1173 self.register_unstructure_hook_factory( 

1174 is_hetero_tuple, self.gen_unstructure_hetero_tuple 

1175 ) 

1176 self.register_unstructure_hook_factory(is_namedtuple)( 

1177 namedtuple_unstructure_factory 

1178 ) 

1179 self.register_unstructure_hook_factory( 

1180 is_sequence, self.gen_unstructure_iterable 

1181 ) 

1182 self.register_unstructure_hook_factory(is_mapping, self.gen_unstructure_mapping) 

1183 self.register_unstructure_hook_factory( 

1184 is_mutable_set, 

1185 lambda cl: self.gen_unstructure_iterable(cl, unstructure_to=set), 

1186 ) 

1187 self.register_unstructure_hook_factory( 

1188 is_frozenset, 

1189 lambda cl: self.gen_unstructure_iterable(cl, unstructure_to=frozenset), 

1190 ) 

1191 if AnnotationForwardRef is not None: 

1192 self.register_unstructure_hook_factory( 

1193 lambda t: isinstance(t, AnnotationForwardRef), 

1194 lambda t: self.get_unstructure_hook(t.evaluate()), 

1195 ) 

1196 self.register_unstructure_hook_factory( 

1197 is_optional, self.gen_unstructure_optional 

1198 ) 

1199 self.register_unstructure_hook_factory( 

1200 is_typeddict, self.gen_unstructure_typeddict 

1201 ) 

1202 self.register_unstructure_hook_factory( 

1203 lambda t: get_newtype_base(t) is not None, 

1204 lambda t: self.get_unstructure_hook(get_newtype_base(t)), 

1205 ) 

1206 

1207 self.register_structure_hook_factory(is_annotated, self.gen_structure_annotated) 

1208 self.register_structure_hook_factory( 

1209 is_hetero_tuple, self.gen_structure_hetero_tuple 

1210 ) 

1211 self.register_structure_hook_factory(is_mapping, self.gen_structure_mapping) 

1212 self.register_structure_hook_factory(is_counter, self.gen_structure_counter) 

1213 self.register_structure_hook_factory( 

1214 is_defaultdict, defaultdict_structure_factory 

1215 ) 

1216 self.register_structure_hook_factory(is_typeddict, self.gen_structure_typeddict) 

1217 if AnnotationForwardRef is not None: 

1218 self.register_structure_hook_factory( 

1219 lambda t: isinstance(t, AnnotationForwardRef), 

1220 lambda t: self.get_structure_hook(t.evaluate()), 

1221 ) 

1222 self.register_structure_hook_factory( 

1223 lambda t: get_newtype_base(t) is not None, self.get_structure_newtype 

1224 ) 

1225 

1226 # We keep these so we can more correctly copy the hooks. 

1227 self._struct_copy_skip = self._structure_func.get_num_fns() 

1228 self._unstruct_copy_skip = self._unstructure_func.get_num_fns() 

1229 

1230 @overload 

1231 def register_unstructure_hook_factory( 

1232 self, predicate: Predicate 

1233 ) -> Callable[[AnyUnstructureHookFactory], AnyUnstructureHookFactory]: ... 

1234 

1235 @overload 

1236 def register_unstructure_hook_factory( 

1237 self, predicate: Predicate, factory: UnstructureHookFactory 

1238 ) -> UnstructureHookFactory: ... 

1239 

1240 @overload 

1241 def register_unstructure_hook_factory( 

1242 self, predicate: Predicate, factory: ExtendedUnstructureHookFactory[Converter] 

1243 ) -> ExtendedUnstructureHookFactory[Converter]: ... 

1244 

1245 def register_unstructure_hook_factory(self, predicate, factory=None): 

1246 # This dummy wrapper is required due to how `@overload` works. 

1247 return super().register_unstructure_hook_factory(predicate, factory) 

1248 

1249 @overload 

1250 def register_structure_hook_factory( 

1251 self, predicate: Predicate 

1252 ) -> Callable[[AnyStructureHookFactory], AnyStructureHookFactory]: ... 

1253 

1254 @overload 

1255 def register_structure_hook_factory( 

1256 self, predicate: Predicate, factory: StructureHookFactory 

1257 ) -> StructureHookFactory: ... 

1258 

1259 @overload 

1260 def register_structure_hook_factory( 

1261 self, predicate: Predicate, factory: ExtendedStructureHookFactory[Converter] 

1262 ) -> ExtendedStructureHookFactory[Converter]: ... 

1263 

1264 def register_structure_hook_factory(self, predicate, factory=None): 

1265 # This dummy wrapper is required due to how `@overload` works. 

1266 return super().register_structure_hook_factory(predicate, factory) 

1267 

1268 def get_structure_newtype(self, type: type[T]) -> Callable[[Any, Any], T]: 

1269 base = get_newtype_base(type) 

1270 handler = self.get_structure_hook(base) 

1271 return lambda v, _: handler(v, base) 

1272 

1273 def gen_unstructure_annotated(self, type): 

1274 origin = type.__origin__ 

1275 return self.get_unstructure_hook(origin) 

1276 

1277 def gen_structure_annotated(self, type) -> Callable: 

1278 """A hook factory for annotated types.""" 

1279 origin = type.__origin__ 

1280 hook = self.get_structure_hook(origin) 

1281 return lambda v, _: hook(v, origin) 

1282 

1283 def gen_unstructure_typeddict(self, cl: Any) -> Callable[[dict], dict]: 

1284 """Generate a TypedDict unstructure function. 

1285 

1286 Also apply converter-scored modifications. 

1287 """ 

1288 return make_typeddict_dict_unstruct_fn(cl, self) 

1289 

1290 def gen_unstructure_attrs_fromdict( 

1291 self, cl: type[T] 

1292 ) -> Callable[[T], dict[str, Any]]: 

1293 origin = get_origin(cl) 

1294 attribs = fields(origin or cl) 

1295 if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs): 

1296 # PEP 563 annotations - need to be resolved. 

1297 resolve_types(origin or cl) 

1298 attrib_overrides = { 

1299 a.name: self.type_overrides[a.type] 

1300 for a in attribs 

1301 if a.type in self.type_overrides 

1302 } 

1303 

1304 return make_dict_unstructure_fn( 

1305 cl, self, _cattrs_omit_if_default=self.omit_if_default, **attrib_overrides 

1306 ) 

1307 

1308 def gen_unstructure_optional(self, cl: type[T]) -> Callable[[T], Any]: 

1309 """Generate an unstructuring hook for optional types.""" 

1310 union_params = cl.__args__ 

1311 other = union_params[0] if union_params[1] is NoneType else union_params[1] 

1312 

1313 if isinstance(other, TypeVar): 

1314 handler = self.unstructure 

1315 else: 

1316 handler = self.get_unstructure_hook(other) 

1317 

1318 def unstructure_optional(val, _handler=handler): 

1319 return None if val is None else _handler(val) 

1320 

1321 return unstructure_optional 

1322 

1323 def gen_structure_typeddict(self, cl: Any) -> Callable[[dict, Any], dict]: 

1324 """Generate a TypedDict structure function. 

1325 

1326 Also apply converter-scored modifications. 

1327 """ 

1328 return make_typeddict_dict_struct_fn( 

1329 cl, self, _cattrs_detailed_validation=self.detailed_validation 

1330 ) 

1331 

1332 def gen_structure_attrs_fromdict( 

1333 self, cl: type[T] 

1334 ) -> Callable[[Mapping[str, Any], Any], T]: 

1335 origin = get_origin(cl) 

1336 attribs = fields(origin or cl if is_generic(cl) else cl) 

1337 if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs): 

1338 # PEP 563 annotations - need to be resolved. 

1339 resolve_types(origin or cl) 

1340 attrib_overrides = { 

1341 a.name: self.type_overrides[a.type] 

1342 for a in attribs 

1343 if a.type in self.type_overrides 

1344 } 

1345 return make_dict_structure_fn( 

1346 cl, 

1347 self, 

1348 _cattrs_forbid_extra_keys=self.forbid_extra_keys, 

1349 _cattrs_prefer_attrib_converters=self._prefer_attrib_converters, 

1350 _cattrs_detailed_validation=self.detailed_validation, 

1351 _cattrs_use_alias=self.use_alias, 

1352 **attrib_overrides, 

1353 ) 

1354 

1355 def gen_unstructure_iterable( 

1356 self, cl: Any, unstructure_to: Any = None 

1357 ) -> IterableUnstructureFn: 

1358 unstructure_to = self._unstruct_collection_overrides.get( 

1359 get_origin(cl) or cl, unstructure_to or list 

1360 ) 

1361 h = iterable_unstructure_factory(cl, self, unstructure_to=unstructure_to) 

1362 self._unstructure_func.register_cls_list([(cl, h)], direct=True) 

1363 return h 

1364 

1365 def gen_unstructure_hetero_tuple( 

1366 self, cl: Any, unstructure_to: Any = None 

1367 ) -> HeteroTupleUnstructureFn: 

1368 unstructure_to = self._unstruct_collection_overrides.get( 

1369 get_origin(cl) or cl, unstructure_to or tuple 

1370 ) 

1371 h = make_hetero_tuple_unstructure_fn(cl, self, unstructure_to=unstructure_to) 

1372 self._unstructure_func.register_cls_list([(cl, h)], direct=True) 

1373 return h 

1374 

1375 def gen_unstructure_mapping( 

1376 self, 

1377 cl: Any, 

1378 unstructure_to: Any = None, 

1379 key_handler: Callable[[Any, Any | None], Any] | None = None, 

1380 ) -> MappingUnstructureFn: 

1381 unstructure_to = self._unstruct_collection_overrides.get( 

1382 get_origin(cl) or cl, unstructure_to or dict 

1383 ) 

1384 h = mapping_unstructure_factory( 

1385 cl, self, unstructure_to=unstructure_to, key_handler=key_handler 

1386 ) 

1387 self._unstructure_func.register_cls_list([(cl, h)], direct=True) 

1388 return h 

1389 

1390 def gen_structure_counter( 

1391 self, cl: type[CounterT] 

1392 ) -> SimpleStructureHook[Mapping[Any, Any], CounterT]: 

1393 h = mapping_structure_factory( 

1394 cl, 

1395 self, 

1396 structure_to=Counter, 

1397 val_type=int, 

1398 detailed_validation=self.detailed_validation, 

1399 ) 

1400 self._structure_func.register_cls_list([(cl, h)], direct=True) 

1401 return h 

1402 

1403 def gen_structure_mapping( 

1404 self, cl: Any 

1405 ) -> SimpleStructureHook[Mapping[Any, Any], Any]: 

1406 structure_to = get_origin(cl) or cl 

1407 if structure_to in ( 

1408 MutableMapping, 

1409 AbcMutableMapping, 

1410 Mapping, 

1411 AbcMapping, 

1412 ): # These default to dicts 

1413 structure_to = dict 

1414 h = mapping_structure_factory( 

1415 cl, self, structure_to, detailed_validation=self.detailed_validation 

1416 ) 

1417 self._structure_func.register_cls_list([(cl, h)], direct=True) 

1418 return h 

1419 

1420 def copy( 

1421 self, 

1422 dict_factory: Callable[[], Any] | None = None, 

1423 unstruct_strat: UnstructureStrategy | None = None, 

1424 omit_if_default: bool | None = None, 

1425 forbid_extra_keys: bool | None = None, 

1426 type_overrides: Mapping[type, AttributeOverride] | None = None, 

1427 unstruct_collection_overrides: Mapping[type, UnstructureHook] | None = None, 

1428 prefer_attrib_converters: bool | None = None, 

1429 detailed_validation: bool | None = None, 

1430 use_alias: bool | None = None, 

1431 ) -> Self: 

1432 """Create a copy of the converter, keeping all existing custom hooks. 

1433 

1434 :param detailed_validation: Whether to use a slightly slower mode for detailed 

1435 validation errors. 

1436 """ 

1437 res = self.__class__( 

1438 dict_factory if dict_factory is not None else self._dict_factory, 

1439 ( 

1440 unstruct_strat 

1441 if unstruct_strat is not None 

1442 else ( 

1443 UnstructureStrategy.AS_DICT 

1444 if self._unstructure_attrs == self.unstructure_attrs_asdict 

1445 else UnstructureStrategy.AS_TUPLE 

1446 ) 

1447 ), 

1448 omit_if_default if omit_if_default is not None else self.omit_if_default, 

1449 ( 

1450 forbid_extra_keys 

1451 if forbid_extra_keys is not None 

1452 else self.forbid_extra_keys 

1453 ), 

1454 type_overrides if type_overrides is not None else self.type_overrides, 

1455 ( 

1456 unstruct_collection_overrides 

1457 if unstruct_collection_overrides is not None 

1458 else self._unstruct_collection_overrides 

1459 ), 

1460 ( 

1461 prefer_attrib_converters 

1462 if prefer_attrib_converters is not None 

1463 else self._prefer_attrib_converters 

1464 ), 

1465 ( 

1466 detailed_validation 

1467 if detailed_validation is not None 

1468 else self.detailed_validation 

1469 ), 

1470 use_alias=(use_alias if use_alias is not None else self.use_alias), 

1471 ) 

1472 

1473 self._unstructure_func.copy_to( 

1474 res._unstructure_func, skip=self._unstruct_copy_skip 

1475 ) 

1476 self._structure_func.copy_to(res._structure_func, skip=self._struct_copy_skip) 

1477 

1478 return res 

1479 

1480 

1481GenConverter: TypeAlias = Converter