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

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

536 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 

18from ._compat import ( 

19 ANIES, 

20 FrozenSetSubscriptable, 

21 Mapping, 

22 MutableMapping, 

23 MutableSequence, 

24 NoneType, 

25 OriginAbstractSet, 

26 OriginMutableSet, 

27 Sequence, 

28 Set, 

29 TypeAlias, 

30 fields, 

31 get_final_base, 

32 get_newtype_base, 

33 get_origin, 

34 has, 

35 has_with_generic, 

36 is_annotated, 

37 is_bare, 

38 is_counter, 

39 is_deque, 

40 is_frozenset, 

41 is_generic, 

42 is_generic_attrs, 

43 is_hetero_tuple, 

44 is_literal, 

45 is_mapping, 

46 is_mutable_sequence, 

47 is_mutable_set, 

48 is_optional, 

49 is_protocol, 

50 is_tuple, 

51 is_typeddict, 

52 is_union_type, 

53 signature, 

54) 

55from .cols import ( 

56 defaultdict_structure_factory, 

57 homogenous_tuple_structure_factory, 

58 is_abstract_set, 

59 is_defaultdict, 

60 is_namedtuple, 

61 is_sequence, 

62 iterable_unstructure_factory, 

63 list_structure_factory, 

64 mapping_structure_factory, 

65 mapping_unstructure_factory, 

66 namedtuple_structure_factory, 

67 namedtuple_unstructure_factory, 

68) 

69from .disambiguators import create_default_dis_func, is_supported_union 

70from .dispatch import ( 

71 HookFactory, 

72 MultiStrategyDispatch, 

73 StructuredValue, 

74 StructureHook, 

75 TargetType, 

76 UnstructuredValue, 

77 UnstructureHook, 

78) 

79from .errors import ( 

80 IterableValidationError, 

81 IterableValidationNote, 

82 StructureHandlerNotFoundError, 

83) 

84from .fns import Predicate, identity, raise_error 

85from .gen import ( 

86 AttributeOverride, 

87 HeteroTupleUnstructureFn, 

88 IterableUnstructureFn, 

89 MappingUnstructureFn, 

90 make_dict_structure_fn, 

91 make_dict_unstructure_fn, 

92 make_hetero_tuple_unstructure_fn, 

93) 

94from .gen.typeddicts import make_dict_structure_fn as make_typeddict_dict_struct_fn 

95from .gen.typeddicts import make_dict_unstructure_fn as make_typeddict_dict_unstruct_fn 

96from .literals import is_literal_containing_enums 

97from .typealiases import ( 

98 get_type_alias_base, 

99 is_type_alias, 

100 type_alias_structure_factory, 

101) 

102from .types import SimpleStructureHook 

103 

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

105 

106T = TypeVar("T") 

107V = TypeVar("V") 

108 

109UnstructureHookFactory = TypeVar( 

110 "UnstructureHookFactory", bound=HookFactory[UnstructureHook] 

111) 

112 

113# The Extended factory also takes a converter. 

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

115 

116# This typevar for the BaseConverter. 

117AnyUnstructureHookFactoryBase = TypeVar( 

118 "AnyUnstructureHookFactoryBase", 

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

120) 

121 

122# This typevar for the Converter. 

123AnyUnstructureHookFactory = TypeVar( 

124 "AnyUnstructureHookFactory", 

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

126) 

127 

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

129 

130# The Extended factory also takes a converter. 

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

132 

133# This typevar for the BaseConverter. 

134AnyStructureHookFactoryBase = TypeVar( 

135 "AnyStructureHookFactoryBase", 

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

137) 

138 

139# This typevar for the Converter. 

140AnyStructureHookFactory = TypeVar( 

141 "AnyStructureHookFactory", 

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

143) 

144 

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

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

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

148 

149 

150class UnstructureStrategy(Enum): 

151 """`attrs` classes unstructuring strategies.""" 

152 

153 AS_DICT = "asdict" 

154 AS_TUPLE = "astuple" 

155 

156 

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

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

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

160 # annotations. 

161 sig = inspect_signature(factory) 

162 return ( 

163 len(sig.parameters) >= 2 

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

165 ) 

166 

167 

168class BaseConverter: 

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

170 

171 __slots__ = ( 

172 "_dict_factory", 

173 "_prefer_attrib_converters", 

174 "_struct_copy_skip", 

175 "_structure_attrs", 

176 "_structure_func", 

177 "_union_struct_registry", 

178 "_unstruct_copy_skip", 

179 "_unstructure_attrs", 

180 "_unstructure_func", 

181 "detailed_validation", 

182 ) 

183 

184 def __init__( 

185 self, 

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

187 unstruct_strat: UnstructureStrategy = UnstructureStrategy.AS_DICT, 

188 prefer_attrib_converters: bool = False, 

189 detailed_validation: bool = True, 

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

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

192 None, t 

193 ), 

194 ) -> None: 

195 """ 

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

197 validation errors. 

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

199 registered unstructuring hooks match. 

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

201 registered structuring hooks match. 

202 

203 .. versionadded:: 23.2.0 *unstructure_fallback_factory* 

204 .. versionadded:: 23.2.0 *structure_fallback_factory* 

205 .. versionchanged:: 24.2.0 

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

207 more eagerly, surfacing problems earlier. 

208 """ 

209 unstruct_strat = UnstructureStrategy(unstruct_strat) 

210 self._prefer_attrib_converters = prefer_attrib_converters 

211 

212 self.detailed_validation = detailed_validation 

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

214 

215 # Create a per-instance cache. 

216 if unstruct_strat is UnstructureStrategy.AS_DICT: 

217 self._unstructure_attrs = self.unstructure_attrs_asdict 

218 self._structure_attrs = self.structure_attrs_fromdict 

219 else: 

220 self._unstructure_attrs = self.unstructure_attrs_astuple 

221 self._structure_attrs = self.structure_attrs_fromtuple 

222 

223 self._unstructure_func = MultiStrategyDispatch( 

224 unstructure_fallback_factory, self 

225 ) 

226 self._unstructure_func.register_cls_list( 

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

228 ) 

229 self._unstructure_func.register_func_list( 

230 [ 

231 ( 

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

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

234 ), 

235 ( 

236 is_protocol, 

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

238 ), 

239 ( 

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

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

242 True, 

243 ), 

244 ( 

245 is_type_alias, 

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

247 True, 

248 ), 

249 (is_literal_containing_enums, self.unstructure), 

250 (is_mapping, self._unstructure_mapping), 

251 (is_sequence, self._unstructure_seq), 

252 (is_mutable_set, self._unstructure_seq), 

253 (is_frozenset, self._unstructure_seq), 

254 (lambda t: issubclass(t, Enum), self._unstructure_enum), 

255 (has, self._unstructure_attrs), 

256 (is_union_type, self._unstructure_union), 

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

258 ] 

259 ) 

260 

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

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

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

264 self._structure_func = MultiStrategyDispatch(structure_fallback_factory, self) 

265 self._structure_func.register_func_list( 

266 [ 

267 ( 

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

269 lambda v, _: v, 

270 ), 

271 (is_generic_attrs, self._gen_structure_generic, True), 

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

273 (is_type_alias, type_alias_structure_factory, "extended"), 

274 ( 

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

276 self._structure_final_factory, 

277 True, 

278 ), 

279 (is_literal, self._structure_simple_literal), 

280 (is_literal_containing_enums, self._structure_enum_literal), 

281 (is_sequence, homogenous_tuple_structure_factory, "extended"), 

282 (is_mutable_sequence, list_structure_factory, "extended"), 

283 (is_deque, self._structure_deque), 

284 (is_mutable_set, self._structure_set), 

285 (is_abstract_set, self._structure_frozenset), 

286 (is_frozenset, self._structure_frozenset), 

287 (is_tuple, self._structure_tuple), 

288 (is_namedtuple, namedtuple_structure_factory, "extended"), 

289 (is_mapping, self._structure_dict), 

290 *( 

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

292 if unstruct_strat is UnstructureStrategy.AS_DICT 

293 else [] 

294 ), 

295 (is_optional, self._structure_optional), 

296 ( 

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

298 self._union_struct_registry.__getitem__, 

299 True, 

300 ), 

301 (has, self._structure_attrs), 

302 ] 

303 ) 

304 # Strings are sequences. 

305 self._structure_func.register_cls_list( 

306 [ 

307 (str, self._structure_call), 

308 (bytes, self._structure_call), 

309 (int, self._structure_call), 

310 (float, self._structure_call), 

311 (Enum, self._structure_call), 

312 (Path, self._structure_call), 

313 ] 

314 ) 

315 

316 self._dict_factory = dict_factory 

317 

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

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

320 

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

322 return self._unstructure_func.dispatch( 

323 obj.__class__ if unstructure_as is None else unstructure_as 

324 )(obj) 

325 

326 @property 

327 def unstruct_strat(self) -> UnstructureStrategy: 

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

329 return ( 

330 UnstructureStrategy.AS_DICT 

331 if self._unstructure_attrs == self.unstructure_attrs_asdict 

332 else UnstructureStrategy.AS_TUPLE 

333 ) 

334 

335 @overload 

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

337 

338 @overload 

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

340 

341 def register_unstructure_hook( 

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

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

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

345 

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

347 its Python equivalent. 

348 

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

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

351 type to register the hook for. 

352 

353 .. versionchanged:: 24.1.0 

354 This method may now be used as a decorator. 

355 .. versionchanged:: 25.1.0 

356 Modern type aliases are now supported. 

357 """ 

358 if func is None: 

359 # Autodetecting decorator. 

360 func = cls 

361 sig = signature(func) 

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

363 self.register_unstructure_hook(cls, func) 

364 

365 return func 

366 

367 if attrs_has(cls): 

368 resolve_types(cls) 

369 if is_union_type(cls): 

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

371 elif is_type_alias(cls): 

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

373 elif get_newtype_base(cls) is not None: 

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

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

376 else: 

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

378 return None 

379 

380 def register_unstructure_hook_func( 

381 self, check_func: Predicate, func: UnstructureHook 

382 ) -> None: 

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

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

385 """ 

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

387 

388 @overload 

389 def register_unstructure_hook_factory( 

390 self, predicate: Predicate 

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

392 

393 @overload 

394 def register_unstructure_hook_factory( 

395 self, predicate: Predicate, factory: UnstructureHookFactory 

396 ) -> UnstructureHookFactory: ... 

397 

398 @overload 

399 def register_unstructure_hook_factory( 

400 self, 

401 predicate: Predicate, 

402 factory: ExtendedUnstructureHookFactory[BaseConverter], 

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

404 

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

406 """ 

407 Register a hook factory for a given predicate. 

408 

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

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

411 parameter. 

412 

413 May also be used as a decorator. 

414 

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

416 can produce a hook for that type. 

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

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

419 

420 .. versionchanged:: 24.1.0 

421 This method may now be used as a decorator. 

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

423 """ 

424 if factory is None: 

425 

426 def decorator(factory): 

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

428 if _is_extended_factory(factory): 

429 self._unstructure_func.register_func_list( 

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

431 ) 

432 else: 

433 self._unstructure_func.register_func_list( 

434 [(predicate, factory, True)] 

435 ) 

436 

437 return decorator 

438 

439 self._unstructure_func.register_func_list( 

440 [ 

441 ( 

442 predicate, 

443 factory, 

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

445 ) 

446 ] 

447 ) 

448 return factory 

449 

450 def get_unstructure_hook( 

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

452 ) -> UnstructureHook: 

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

454 

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

456 and re-registered. 

457 

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

459 will be used to produce one. 

460 

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

462 

463 .. versionadded:: 24.1.0 

464 """ 

465 return ( 

466 self._unstructure_func.dispatch(type) 

467 if cache_result 

468 else self._unstructure_func.dispatch_without_caching(type) 

469 ) 

470 

471 @overload 

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

473 

474 @overload 

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

476 

477 def register_structure_hook( 

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

479 ) -> None: 

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

481 

482 The converter function should take two arguments: 

483 * a Python object to be converted, 

484 * the type to convert to 

485 

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

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

488 

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

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

491 as the type for the hook. 

492 

493 .. versionchanged:: 24.1.0 

494 This method may now be used as a decorator. 

495 .. versionchanged:: 25.1.0 

496 Modern type aliases are now supported. 

497 """ 

498 if func is None: 

499 # The autodetecting decorator. 

500 func = cl 

501 sig = signature(func) 

502 self.register_structure_hook(sig.return_annotation, func) 

503 return func 

504 

505 if attrs_has(cl): 

506 resolve_types(cl) 

507 if is_union_type(cl): 

508 self._union_struct_registry[cl] = func 

509 self._structure_func.clear_cache() 

510 elif is_type_alias(cl): 

511 # Type aliases are special-cased. 

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

513 elif get_newtype_base(cl) is not None: 

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

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

516 else: 

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

518 return None 

519 

520 def register_structure_hook_func( 

521 self, check_func: Predicate, func: StructureHook 

522 ) -> None: 

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

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

525 """ 

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

527 

528 @overload 

529 def register_structure_hook_factory( 

530 self, predicate: Predicate 

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

532 

533 @overload 

534 def register_structure_hook_factory( 

535 self, predicate: Predicate, factory: StructureHookFactory 

536 ) -> StructureHookFactory: ... 

537 

538 @overload 

539 def register_structure_hook_factory( 

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

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

542 

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

544 """ 

545 Register a hook factory for a given predicate. 

546 

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

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

549 parameter. 

550 

551 May also be used as a decorator. 

552 

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

554 can produce a hook for that type. 

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

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

557 

558 .. versionchanged:: 24.1.0 

559 This method may now be used as a decorator. 

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

561 """ 

562 if factory is None: 

563 # Decorator use. 

564 def decorator(factory): 

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

566 if _is_extended_factory(factory): 

567 self._structure_func.register_func_list( 

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

569 ) 

570 else: 

571 self._structure_func.register_func_list( 

572 [(predicate, factory, True)] 

573 ) 

574 

575 return decorator 

576 self._structure_func.register_func_list( 

577 [ 

578 ( 

579 predicate, 

580 factory, 

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

582 ) 

583 ] 

584 ) 

585 return factory 

586 

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

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

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

590 

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

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

593 

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

595 and re-registered. 

596 

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

598 will be used to produce one. 

599 

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

601 

602 .. versionadded:: 24.1.0 

603 """ 

604 return ( 

605 self._structure_func.dispatch(type) 

606 if cache_result 

607 else self._structure_func.dispatch_without_caching(type) 

608 ) 

609 

610 # Classes to Python primitives. 

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

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

613 attrs = fields(obj.__class__) 

614 dispatch = self._unstructure_func.dispatch 

615 rv = self._dict_factory() 

616 for a in attrs: 

617 name = a.name 

618 v = getattr(obj, name) 

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

620 return rv 

621 

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

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

624 attrs = fields(obj.__class__) 

625 dispatch = self._unstructure_func.dispatch 

626 res = [] 

627 for a in attrs: 

628 name = a.name 

629 v = getattr(obj, name) 

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

631 return tuple(res) 

632 

633 def _unstructure_enum(self, obj: Enum) -> Any: 

634 """Convert an enum to its value.""" 

635 return obj.value 

636 

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

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

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

640 dispatch = self._unstructure_func.dispatch 

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

642 

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

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

645 

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

647 # stay OrderedDicts. 

648 dispatch = self._unstructure_func.dispatch 

649 return mapping.__class__( 

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

651 for k, v in mapping.items() 

652 ) 

653 

654 # note: Use UnionType when 3.11 is released as 

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

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

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

658 """ 

659 Unstructure an object as a union. 

660 

661 By default, just unstructures the instance. 

662 """ 

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

664 

665 # Python primitives to classes. 

666 

667 def _gen_structure_generic( 

668 self, cl: type[T] 

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

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

671 return make_dict_structure_fn( 

672 cl, self, _cattrs_prefer_attrib_converters=self._prefer_attrib_converters 

673 ) 

674 

675 def _gen_attrs_union_structure( 

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

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

678 """ 

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

680 

681 :param use_literals: Whether to consider literal fields. 

682 """ 

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

684 has_none = NoneType in cl.__args__ 

685 

686 if has_none: 

687 

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

689 if obj is None: 

690 return None 

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

692 

693 else: 

694 

695 def structure_attrs_union(obj, _): 

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

697 

698 return structure_attrs_union 

699 

700 @staticmethod 

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

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

703 

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

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

706 etc. 

707 """ 

708 return cl(obj) 

709 

710 @staticmethod 

711 def _structure_simple_literal(val, type): 

712 if val not in type.__args__: 

713 raise Exception(f"{val} not in literal {type}") 

714 return val 

715 

716 @staticmethod 

717 def _structure_enum_literal(val, type): 

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

719 try: 

720 return vals[val] 

721 except KeyError: 

722 raise Exception(f"{val} not in literal {type}") from None 

723 

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

725 base = get_newtype_base(type) 

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

727 

728 def _structure_final_factory(self, type): 

729 base = get_final_base(type) 

730 res = self.get_structure_hook(base) 

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

732 

733 # Attrs classes. 

734 

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

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

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

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

739 # We detect the type by the metadata. 

740 converted = self._structure_attribute(a, value) 

741 conv_obj.append(converted) 

742 

743 return cl(*conv_obj) 

744 

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

746 """Handle an individual attrs attribute.""" 

747 type_ = a.type 

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

749 if self._prefer_attrib_converters and attrib_converter: 

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

751 # prefer_attrib_converters is set to give these priority over registered 

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

753 # into the converter 

754 return value 

755 if type_ is None: 

756 # No type metadata. 

757 return value 

758 

759 try: 

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

761 except StructureHandlerNotFoundError: 

762 if attrib_converter: 

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

764 return value 

765 raise 

766 

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

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

769 # For public use. 

770 

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

772 for a in fields(cl): 

773 try: 

774 val = obj[a.name] 

775 except KeyError: 

776 continue 

777 

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

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

780 

781 return cl(**conv_obj) 

782 

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

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

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

786 res = deque(obj) 

787 else: 

788 elem_type = cl.__args__[0] 

789 handler = self._structure_func.dispatch(elem_type) 

790 if self.detailed_validation: 

791 errors = [] 

792 res = deque() 

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

794 for e in obj: 

795 try: 

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

797 except Exception as e: 

798 msg = IterableValidationNote( 

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

800 ) 

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

802 errors.append(e) 

803 finally: 

804 ix += 1 

805 if errors: 

806 raise IterableValidationError( 

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

808 ) 

809 else: 

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

811 return res 

812 

813 def _structure_set( 

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

815 ) -> Set[T]: 

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

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

818 return structure_to(obj) 

819 elem_type = cl.__args__[0] 

820 handler = self._structure_func.dispatch(elem_type) 

821 if self.detailed_validation: 

822 errors = [] 

823 res = set() 

824 ix = 0 

825 for e in obj: 

826 try: 

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

828 except Exception as exc: 

829 msg = IterableValidationNote( 

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

831 ix, 

832 elem_type, 

833 ) 

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

835 errors.append(exc) 

836 finally: 

837 ix += 1 

838 if errors: 

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

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

841 if structure_to is set: 

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

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

844 

845 def _structure_frozenset( 

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

847 ) -> FrozenSetSubscriptable[T]: 

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

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

850 

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

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

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

854 return dict(obj) 

855 key_type, val_type = cl.__args__ 

856 

857 if self.detailed_validation: 

858 key_handler = self._structure_func.dispatch(key_type) 

859 val_handler = self._structure_func.dispatch(val_type) 

860 errors = [] 

861 res = {} 

862 

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

864 try: 

865 value = val_handler(v, val_type) 

866 except Exception as exc: 

867 msg = IterableValidationNote( 

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

869 ) 

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

871 errors.append(exc) 

872 continue 

873 

874 try: 

875 key = key_handler(k, key_type) 

876 res[key] = value 

877 except Exception as exc: 

878 msg = IterableValidationNote( 

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

880 ) 

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

882 errors.append(exc) 

883 

884 if errors: 

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

886 return res 

887 

888 if key_type in ANIES: 

889 val_conv = self._structure_func.dispatch(val_type) 

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

891 if val_type in ANIES: 

892 key_conv = self._structure_func.dispatch(key_type) 

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

894 key_conv = self._structure_func.dispatch(key_type) 

895 val_conv = self._structure_func.dispatch(val_type) 

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

897 

898 def _structure_optional(self, obj, union): 

899 if obj is None: 

900 return None 

901 union_params = union.__args__ 

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

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

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

905 

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

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

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

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

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

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

912 return tuple(obj) 

913 if has_ellipsis: 

914 # We're dealing with a homogenous tuple, tuple[int, ...] 

915 tup_type = tup_params[0] 

916 conv = self._structure_func.dispatch(tup_type) 

917 if self.detailed_validation: 

918 errors = [] 

919 res = [] 

920 ix = 0 

921 for e in obj: 

922 try: 

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

924 except Exception as exc: 

925 msg = IterableValidationNote( 

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

927 ) 

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

929 errors.append(exc) 

930 finally: 

931 ix += 1 

932 if errors: 

933 raise IterableValidationError( 

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

935 ) 

936 return tuple(res) 

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

938 

939 # We're dealing with a heterogenous tuple. 

940 exp_len = len(tup_params) 

941 if self.detailed_validation: 

942 errors = [] 

943 res = [] 

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

945 try: 

946 conv = self._structure_func.dispatch(t) 

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

948 except Exception as exc: 

949 msg = IterableValidationNote( 

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

951 ) 

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

953 errors.append(exc) 

954 if len(obj) != exp_len: 

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

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

957 msg = f"Structuring {tup}" 

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

959 errors.append(exc) 

960 if errors: 

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

962 return tuple(res) 

963 

964 if len(obj) != exp_len: 

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

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

967 return tuple( 

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

969 ) 

970 

971 def _get_dis_func( 

972 self, 

973 union: Any, 

974 use_literals: bool = True, 

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

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

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

978 union_types = union.__args__ 

979 if NoneType in union_types: 

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

981 # logic. 

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

983 

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

985 raise StructureHandlerNotFoundError( 

986 "Only unions of attrs classes and dataclasses supported " 

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

988 type_=union, 

989 ) 

990 

991 return create_default_dis_func( 

992 self, 

993 *union_types, 

994 use_literals=use_literals, 

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

996 ) 

997 

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

999 return self.copy() 

1000 

1001 def copy( 

1002 self, 

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

1004 unstruct_strat: UnstructureStrategy | None = None, 

1005 prefer_attrib_converters: bool | None = None, 

1006 detailed_validation: bool | None = None, 

1007 ) -> Self: 

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

1009 

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

1011 validation errors. 

1012 """ 

1013 res = self.__class__( 

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

1015 ( 

1016 unstruct_strat 

1017 if unstruct_strat is not None 

1018 else ( 

1019 UnstructureStrategy.AS_DICT 

1020 if self._unstructure_attrs == self.unstructure_attrs_asdict 

1021 else UnstructureStrategy.AS_TUPLE 

1022 ) 

1023 ), 

1024 ( 

1025 prefer_attrib_converters 

1026 if prefer_attrib_converters is not None 

1027 else self._prefer_attrib_converters 

1028 ), 

1029 ( 

1030 detailed_validation 

1031 if detailed_validation is not None 

1032 else self.detailed_validation 

1033 ), 

1034 ) 

1035 

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

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

1038 

1039 return res 

1040 

1041 

1042class Converter(BaseConverter): 

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

1044 

1045 __slots__ = ( 

1046 "_unstruct_collection_overrides", 

1047 "forbid_extra_keys", 

1048 "omit_if_default", 

1049 "type_overrides", 

1050 "use_alias", 

1051 ) 

1052 

1053 def __init__( 

1054 self, 

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

1056 unstruct_strat: UnstructureStrategy = UnstructureStrategy.AS_DICT, 

1057 omit_if_default: bool = False, 

1058 forbid_extra_keys: bool = False, 

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

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

1061 prefer_attrib_converters: bool = False, 

1062 detailed_validation: bool = True, 

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

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

1065 None, t 

1066 ), 

1067 use_alias: bool = False, 

1068 ): 

1069 """ 

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

1071 validation errors. 

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

1073 registered unstructuring hooks match. 

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

1075 registered structuring hooks match. 

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

1077 the un/structured dictionary key by default. 

1078 

1079 .. versionadded:: 23.2.0 *unstructure_fallback_factory* 

1080 .. versionadded:: 23.2.0 *structure_fallback_factory* 

1081 .. versionchanged:: 24.2.0 

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

1083 more eagerly, surfacing problems earlier. 

1084 .. versionadded:: 25.2.0 *use_alias* 

1085 """ 

1086 super().__init__( 

1087 dict_factory=dict_factory, 

1088 unstruct_strat=unstruct_strat, 

1089 prefer_attrib_converters=prefer_attrib_converters, 

1090 detailed_validation=detailed_validation, 

1091 unstructure_fallback_factory=unstructure_fallback_factory, 

1092 structure_fallback_factory=structure_fallback_factory, 

1093 ) 

1094 self.omit_if_default = omit_if_default 

1095 self.forbid_extra_keys = forbid_extra_keys 

1096 self.type_overrides = dict(type_overrides) 

1097 self.use_alias = use_alias 

1098 

1099 unstruct_collection_overrides = { 

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

1101 } 

1102 

1103 self._unstruct_collection_overrides = unstruct_collection_overrides 

1104 

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

1106 co = unstruct_collection_overrides 

1107 

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

1109 if OriginAbstractSet in co: 

1110 if OriginMutableSet not in co: 

1111 co[OriginMutableSet] = co[OriginAbstractSet] 

1112 if FrozenSetSubscriptable not in co: 

1113 co[FrozenSetSubscriptable] = co[OriginAbstractSet] 

1114 

1115 # abc.MutableSet overrrides, if defined, apply to sets 

1116 if OriginMutableSet in co and set not in co: 

1117 co[set] = co[OriginMutableSet] 

1118 

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

1120 # tuples 

1121 if Sequence in co: 

1122 if MutableSequence not in co: 

1123 co[MutableSequence] = co[Sequence] 

1124 if tuple not in co: 

1125 co[tuple] = co[Sequence] 

1126 

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

1128 if MutableSequence in co: 

1129 if list not in co: 

1130 co[list] = co[MutableSequence] 

1131 if deque not in co: 

1132 co[deque] = co[MutableSequence] 

1133 

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

1135 if Mapping in co and MutableMapping not in co: 

1136 co[MutableMapping] = co[Mapping] 

1137 

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

1139 if MutableMapping in co and dict not in co: 

1140 co[dict] = co[MutableMapping] 

1141 

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

1143 if dict in co and Counter not in co: 

1144 co[Counter] = co[dict] 

1145 

1146 if unstruct_strat is UnstructureStrategy.AS_DICT: 

1147 # Override the attrs handler. 

1148 self.register_unstructure_hook_factory( 

1149 has_with_generic, self.gen_unstructure_attrs_fromdict 

1150 ) 

1151 self.register_structure_hook_factory( 

1152 has_with_generic, self.gen_structure_attrs_fromdict 

1153 ) 

1154 self.register_unstructure_hook_factory( 

1155 is_annotated, self.gen_unstructure_annotated 

1156 ) 

1157 self.register_unstructure_hook_factory( 

1158 is_hetero_tuple, self.gen_unstructure_hetero_tuple 

1159 ) 

1160 self.register_unstructure_hook_factory(is_namedtuple)( 

1161 namedtuple_unstructure_factory 

1162 ) 

1163 self.register_unstructure_hook_factory( 

1164 is_sequence, self.gen_unstructure_iterable 

1165 ) 

1166 self.register_unstructure_hook_factory(is_mapping, self.gen_unstructure_mapping) 

1167 self.register_unstructure_hook_factory( 

1168 is_mutable_set, 

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

1170 ) 

1171 self.register_unstructure_hook_factory( 

1172 is_frozenset, 

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

1174 ) 

1175 self.register_unstructure_hook_factory( 

1176 is_optional, self.gen_unstructure_optional 

1177 ) 

1178 self.register_unstructure_hook_factory( 

1179 is_typeddict, self.gen_unstructure_typeddict 

1180 ) 

1181 self.register_unstructure_hook_factory( 

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

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

1184 ) 

1185 

1186 self.register_structure_hook_factory(is_annotated, self.gen_structure_annotated) 

1187 self.register_structure_hook_factory(is_mapping, self.gen_structure_mapping) 

1188 self.register_structure_hook_factory(is_counter, self.gen_structure_counter) 

1189 self.register_structure_hook_factory( 

1190 is_defaultdict, defaultdict_structure_factory 

1191 ) 

1192 self.register_structure_hook_factory(is_typeddict, self.gen_structure_typeddict) 

1193 self.register_structure_hook_factory( 

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

1195 ) 

1196 

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

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

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

1200 

1201 @overload 

1202 def register_unstructure_hook_factory( 

1203 self, predicate: Predicate 

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

1205 

1206 @overload 

1207 def register_unstructure_hook_factory( 

1208 self, predicate: Predicate, factory: UnstructureHookFactory 

1209 ) -> UnstructureHookFactory: ... 

1210 

1211 @overload 

1212 def register_unstructure_hook_factory( 

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

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

1215 

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

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

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

1219 

1220 @overload 

1221 def register_structure_hook_factory( 

1222 self, predicate: Predicate 

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

1224 

1225 @overload 

1226 def register_structure_hook_factory( 

1227 self, predicate: Predicate, factory: StructureHookFactory 

1228 ) -> StructureHookFactory: ... 

1229 

1230 @overload 

1231 def register_structure_hook_factory( 

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

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

1234 

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

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

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

1238 

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

1240 base = get_newtype_base(type) 

1241 handler = self.get_structure_hook(base) 

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

1243 

1244 def gen_unstructure_annotated(self, type): 

1245 origin = type.__origin__ 

1246 return self.get_unstructure_hook(origin) 

1247 

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

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

1250 origin = type.__origin__ 

1251 hook = self.get_structure_hook(origin) 

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

1253 

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

1255 """Generate a TypedDict unstructure function. 

1256 

1257 Also apply converter-scored modifications. 

1258 """ 

1259 return make_typeddict_dict_unstruct_fn(cl, self) 

1260 

1261 def gen_unstructure_attrs_fromdict( 

1262 self, cl: type[T] 

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

1264 origin = get_origin(cl) 

1265 attribs = fields(origin or cl) 

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

1267 # PEP 563 annotations - need to be resolved. 

1268 resolve_types(origin or cl) 

1269 attrib_overrides = { 

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

1271 for a in attribs 

1272 if a.type in self.type_overrides 

1273 } 

1274 

1275 return make_dict_unstructure_fn( 

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

1277 ) 

1278 

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

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

1281 union_params = cl.__args__ 

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

1283 

1284 if isinstance(other, TypeVar): 

1285 handler = self.unstructure 

1286 else: 

1287 handler = self.get_unstructure_hook(other) 

1288 

1289 def unstructure_optional(val, _handler=handler): 

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

1291 

1292 return unstructure_optional 

1293 

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

1295 """Generate a TypedDict structure function. 

1296 

1297 Also apply converter-scored modifications. 

1298 """ 

1299 return make_typeddict_dict_struct_fn( 

1300 cl, self, _cattrs_detailed_validation=self.detailed_validation 

1301 ) 

1302 

1303 def gen_structure_attrs_fromdict( 

1304 self, cl: type[T] 

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

1306 attribs = fields(get_origin(cl) or cl if is_generic(cl) else cl) 

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

1308 # PEP 563 annotations - need to be resolved. 

1309 resolve_types(cl) 

1310 attrib_overrides = { 

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

1312 for a in attribs 

1313 if a.type in self.type_overrides 

1314 } 

1315 return make_dict_structure_fn( 

1316 cl, 

1317 self, 

1318 _cattrs_forbid_extra_keys=self.forbid_extra_keys, 

1319 _cattrs_prefer_attrib_converters=self._prefer_attrib_converters, 

1320 _cattrs_detailed_validation=self.detailed_validation, 

1321 _cattrs_use_alias=self.use_alias, 

1322 **attrib_overrides, 

1323 ) 

1324 

1325 def gen_unstructure_iterable( 

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

1327 ) -> IterableUnstructureFn: 

1328 unstructure_to = self._unstruct_collection_overrides.get( 

1329 get_origin(cl) or cl, unstructure_to or list 

1330 ) 

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

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

1333 return h 

1334 

1335 def gen_unstructure_hetero_tuple( 

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

1337 ) -> HeteroTupleUnstructureFn: 

1338 unstructure_to = self._unstruct_collection_overrides.get( 

1339 get_origin(cl) or cl, unstructure_to or tuple 

1340 ) 

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

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

1343 return h 

1344 

1345 def gen_unstructure_mapping( 

1346 self, 

1347 cl: Any, 

1348 unstructure_to: Any = None, 

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

1350 ) -> MappingUnstructureFn: 

1351 unstructure_to = self._unstruct_collection_overrides.get( 

1352 get_origin(cl) or cl, unstructure_to or dict 

1353 ) 

1354 h = mapping_unstructure_factory( 

1355 cl, self, unstructure_to=unstructure_to, key_handler=key_handler 

1356 ) 

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

1358 return h 

1359 

1360 def gen_structure_counter( 

1361 self, cl: type[CounterT] 

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

1363 h = mapping_structure_factory( 

1364 cl, 

1365 self, 

1366 structure_to=Counter, 

1367 val_type=int, 

1368 detailed_validation=self.detailed_validation, 

1369 ) 

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

1371 return h 

1372 

1373 def gen_structure_mapping( 

1374 self, cl: Any 

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

1376 structure_to = get_origin(cl) or cl 

1377 if structure_to in ( 

1378 MutableMapping, 

1379 AbcMutableMapping, 

1380 Mapping, 

1381 AbcMapping, 

1382 ): # These default to dicts 

1383 structure_to = dict 

1384 h = mapping_structure_factory( 

1385 cl, self, structure_to, detailed_validation=self.detailed_validation 

1386 ) 

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

1388 return h 

1389 

1390 def copy( 

1391 self, 

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

1393 unstruct_strat: UnstructureStrategy | None = None, 

1394 omit_if_default: bool | None = None, 

1395 forbid_extra_keys: bool | None = None, 

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

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

1398 prefer_attrib_converters: bool | None = None, 

1399 detailed_validation: bool | None = None, 

1400 use_alias: bool | None = None, 

1401 ) -> Self: 

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

1403 

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

1405 validation errors. 

1406 """ 

1407 res = self.__class__( 

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

1409 ( 

1410 unstruct_strat 

1411 if unstruct_strat is not None 

1412 else ( 

1413 UnstructureStrategy.AS_DICT 

1414 if self._unstructure_attrs == self.unstructure_attrs_asdict 

1415 else UnstructureStrategy.AS_TUPLE 

1416 ) 

1417 ), 

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

1419 ( 

1420 forbid_extra_keys 

1421 if forbid_extra_keys is not None 

1422 else self.forbid_extra_keys 

1423 ), 

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

1425 ( 

1426 unstruct_collection_overrides 

1427 if unstruct_collection_overrides is not None 

1428 else self._unstruct_collection_overrides 

1429 ), 

1430 ( 

1431 prefer_attrib_converters 

1432 if prefer_attrib_converters is not None 

1433 else self._prefer_attrib_converters 

1434 ), 

1435 ( 

1436 detailed_validation 

1437 if detailed_validation is not None 

1438 else self.detailed_validation 

1439 ), 

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

1441 ) 

1442 

1443 self._unstructure_func.copy_to( 

1444 res._unstructure_func, skip=self._unstruct_copy_skip 

1445 ) 

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

1447 

1448 return res 

1449 

1450 

1451GenConverter: TypeAlias = Converter