Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/cattrs/gen/__init__.py: 7%

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

496 statements  

1from __future__ import annotations 

2 

3import re 

4from collections.abc import Callable, Iterable, Mapping 

5from typing import TYPE_CHECKING, Any, Final, Literal, TypeVar 

6 

7from attrs import NOTHING, Attribute, Factory 

8from typing_extensions import NoDefault 

9 

10from .._compat import ( 

11 ANIES, 

12 TypeAlias, 

13 adapted_fields, 

14 get_args, 

15 get_origin, 

16 is_annotated, 

17 is_bare, 

18 is_bare_final, 

19 is_generic, 

20) 

21from .._generics import deep_copy_with 

22from ..dispatch import UnstructureHook 

23from ..errors import ( 

24 AttributeValidationNote, 

25 ClassValidationError, 

26 ForbiddenExtraKeysError, 

27 IterableValidationError, 

28 IterableValidationNote, 

29 StructureHandlerNotFoundError, 

30) 

31from ..fns import identity 

32from ..types import SimpleStructureHook 

33from ._consts import AttributeOverride, already_generating, neutral 

34from ._generics import generate_mapping 

35from ._lc import generate_unique_filename 

36from ._shared import find_structure_handler 

37 

38if TYPE_CHECKING: 

39 from ..converters import BaseConverter 

40 

41__all__ = [ 

42 "make_dict_structure_fn", 

43 "make_dict_structure_fn_from_attrs", 

44 "make_dict_unstructure_fn", 

45 "make_dict_unstructure_fn_from_attrs", 

46 "make_hetero_tuple_unstructure_fn", 

47 "make_iterable_unstructure_fn", 

48 "make_mapping_structure_fn", 

49 "make_mapping_unstructure_fn", 

50] 

51 

52 

53def override( 

54 omit_if_default: bool | None = None, 

55 rename: str | None = None, 

56 omit: bool | None = None, 

57 struct_hook: Callable[[Any, Any], Any] | None = None, 

58 unstruct_hook: Callable[[Any], Any] | None = None, 

59) -> AttributeOverride: 

60 """Override how a particular field is handled. 

61 

62 :param omit: Whether to skip the field or not. `None` means apply default handling. 

63 """ 

64 return AttributeOverride(omit_if_default, rename, omit, struct_hook, unstruct_hook) 

65 

66 

67T = TypeVar("T") 

68 

69 

70def make_dict_unstructure_fn_from_attrs( 

71 attrs: list[Attribute], 

72 cl: type[T], 

73 converter: BaseConverter, 

74 typevar_map: dict[str, Any] = {}, 

75 _cattrs_omit_if_default: bool = False, 

76 _cattrs_use_linecache: bool = True, 

77 _cattrs_use_alias: bool | Literal["from_converter"] = "from_converter", 

78 _cattrs_include_init_false: bool = False, 

79 **kwargs: AttributeOverride, 

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

81 """ 

82 Generate a specialized dict unstructuring function for a list of attributes. 

83 

84 Usually used as a building block by more specialized hook factories. 

85 

86 Any provided overrides are attached to the generated function under the 

87 `overrides` attribute. 

88 

89 :param cl: The class for which the function is generated; used mostly for its name, 

90 module name and qualname. 

91 :param _cattrs_omit_if_default: if true, attributes equal to their default values 

92 will be omitted in the result dictionary. 

93 :param _cattrs_use_alias: If true, the attribute alias will be used as the 

94 dictionary key by default. 

95 :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False` 

96 will be included. 

97 

98 .. versionadded:: 24.1.0 

99 .. versionchanged:: 25.2.0 

100 The `_cattrs_use_alias` parameter takes its value from the given converter 

101 by default. 

102 """ 

103 

104 fn_name = "unstructure_" + cl.__name__ 

105 globs = {} 

106 lines = [] 

107 invocation_lines = [] 

108 internal_arg_parts = {} 

109 

110 if _cattrs_use_alias == "from_converter": 

111 # BaseConverter doesn't have it so we're careful. 

112 _cattrs_use_alias = getattr(converter, "use_alias", False) 

113 

114 for a in attrs: 

115 attr_name = a.name 

116 override = kwargs.get(attr_name, neutral) 

117 if override.omit: 

118 continue 

119 if override.omit is None and not a.init and not _cattrs_include_init_false: 

120 continue 

121 if override.rename is None: 

122 kn = attr_name if not _cattrs_use_alias else a.alias 

123 else: 

124 kn = override.rename 

125 d = a.default 

126 

127 # For each attribute, we try resolving the type here and now. 

128 # If a type is manually overwritten, this function should be 

129 # regenerated. 

130 handler = None 

131 if override.unstruct_hook is not None: 

132 handler = override.unstruct_hook 

133 else: 

134 if a.type is not None: 

135 t = a.type 

136 if isinstance(t, TypeVar): 

137 if t.__name__ in typevar_map: 

138 t = typevar_map[t.__name__] 

139 else: 

140 handler = converter.unstructure 

141 elif is_generic(t) and not is_bare(t) and not is_annotated(t): 

142 t = deep_copy_with(t, typevar_map, cl) 

143 

144 if handler is None: 

145 if ( 

146 is_bare_final(t) 

147 and a.default is not NOTHING 

148 and not isinstance(a.default, Factory) 

149 ): 

150 # This is a special case where we can use the 

151 # type of the default to dispatch on. 

152 t = a.default.__class__ 

153 try: 

154 handler = converter.get_unstructure_hook(t, cache_result=False) 

155 except RecursionError: 

156 # There's a circular reference somewhere down the line 

157 handler = converter.unstructure 

158 else: 

159 handler = converter.unstructure 

160 

161 is_identity = handler == identity 

162 

163 if not is_identity: 

164 unstruct_handler_name = f"__c_unstr_{attr_name}" 

165 globs[unstruct_handler_name] = handler 

166 internal_arg_parts[unstruct_handler_name] = handler 

167 invoke = f"{unstruct_handler_name}(instance.{attr_name})" 

168 else: 

169 invoke = f"instance.{attr_name}" 

170 

171 if d is not NOTHING and ( 

172 (_cattrs_omit_if_default and override.omit_if_default is not False) 

173 or override.omit_if_default 

174 ): 

175 def_name = f"__c_def_{attr_name}" 

176 

177 if isinstance(d, Factory): 

178 globs[def_name] = d.factory 

179 internal_arg_parts[def_name] = d.factory 

180 if d.takes_self: 

181 lines.append(f" if instance.{attr_name} != {def_name}(instance):") 

182 else: 

183 lines.append(f" if instance.{attr_name} != {def_name}():") 

184 lines.append(f" res['{kn}'] = {invoke}") 

185 else: 

186 globs[def_name] = d 

187 internal_arg_parts[def_name] = d 

188 lines.append(f" if instance.{attr_name} != {def_name}:") 

189 lines.append(f" res['{kn}'] = {invoke}") 

190 

191 else: 

192 # No default or no override. 

193 invocation_lines.append(f"'{kn}': {invoke},") 

194 

195 internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) 

196 if internal_arg_line: 

197 internal_arg_line = f", {internal_arg_line}" 

198 for k, v in internal_arg_parts.items(): 

199 globs[k] = v 

200 

201 total_lines = ( 

202 [f"def {fn_name}(instance{internal_arg_line}):"] 

203 + [" res = {"] 

204 + [f" {line}" for line in invocation_lines] 

205 + [" }"] 

206 + lines 

207 + [" return res"] 

208 ) 

209 script = "\n".join(total_lines) 

210 fname = generate_unique_filename( 

211 cl, "unstructure", lines=total_lines if _cattrs_use_linecache else [] 

212 ) 

213 

214 eval(compile(script, fname, "exec"), globs) 

215 

216 res = globs[fn_name] 

217 res.overrides = kwargs 

218 

219 return res 

220 

221 

222def make_dict_unstructure_fn( 

223 cl: type[T], 

224 converter: BaseConverter, 

225 _cattrs_omit_if_default: bool = False, 

226 _cattrs_use_linecache: bool = True, 

227 _cattrs_use_alias: bool | Literal["from_converter"] = "from_converter", 

228 _cattrs_include_init_false: bool = False, 

229 **kwargs: AttributeOverride, 

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

231 """ 

232 Generate a specialized dict unstructuring function for an attrs class or a 

233 dataclass. 

234 

235 Any provided overrides are attached to the generated function under the 

236 `overrides` attribute. 

237 

238 :param _cattrs_omit_if_default: if true, attributes equal to their default values 

239 will be omitted in the result dictionary. 

240 :param _cattrs_use_alias: If true, the attribute alias will be used as the 

241 dictionary key by default. 

242 :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False` 

243 will be included. 

244 

245 .. versionadded:: 23.2.0 *_cattrs_use_alias* 

246 .. versionadded:: 23.2.0 *_cattrs_include_init_false* 

247 .. versionchanged:: 25.2.0 

248 The `_cattrs_use_alias` parameter takes its value from the given converter 

249 by default. 

250 """ 

251 origin = get_origin(cl) 

252 attrs = adapted_fields(origin or cl) # type: ignore 

253 

254 mapping = {} 

255 if _cattrs_use_alias == "from_converter": 

256 # BaseConverter doesn't have it so we're careful. 

257 _cattrs_use_alias = getattr(converter, "use_alias", False) 

258 if is_generic(cl): 

259 mapping = generate_mapping(cl, mapping) 

260 

261 if origin is not None: 

262 cl = origin 

263 

264 # We keep track of what we're generating to help with recursive 

265 # class graphs. 

266 try: 

267 working_set = already_generating.working_set 

268 except AttributeError: 

269 working_set = set() 

270 already_generating.working_set = working_set 

271 if cl in working_set: 

272 raise RecursionError() 

273 

274 working_set.add(cl) 

275 

276 try: 

277 return make_dict_unstructure_fn_from_attrs( 

278 attrs, 

279 cl, 

280 converter, 

281 mapping, 

282 _cattrs_omit_if_default=_cattrs_omit_if_default, 

283 _cattrs_use_linecache=_cattrs_use_linecache, 

284 _cattrs_use_alias=_cattrs_use_alias, 

285 _cattrs_include_init_false=_cattrs_include_init_false, 

286 **kwargs, 

287 ) 

288 finally: 

289 working_set.remove(cl) 

290 if not working_set: 

291 del already_generating.working_set 

292 

293 

294def make_dict_structure_fn_from_attrs( 

295 attrs: list[Attribute], 

296 cl: type[T], 

297 converter: BaseConverter, 

298 typevar_map: dict[str, Any] = {}, 

299 _cattrs_forbid_extra_keys: bool | Literal["from_converter"] = "from_converter", 

300 _cattrs_use_linecache: bool = True, 

301 _cattrs_prefer_attrib_converters: ( 

302 bool | Literal["from_converter"] 

303 ) = "from_converter", 

304 _cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter", 

305 _cattrs_use_alias: bool | Literal["from_converter"] = "from_converter", 

306 _cattrs_include_init_false: bool = False, 

307 **kwargs: AttributeOverride, 

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

309 """ 

310 Generate a specialized dict structuring function for a list of attributes. 

311 

312 Usually used as a building block by more specialized hook factories. 

313 

314 Any provided overrides are attached to the generated function under the 

315 `overrides` attribute. 

316 

317 :param _cattrs_forbid_extra_keys: Whether the structuring function should raise a 

318 `ForbiddenExtraKeysError` if unknown keys are encountered. 

319 :param _cattrs_use_linecache: Whether to store the source code in the Python 

320 linecache. 

321 :param _cattrs_prefer_attrib_converters: If an _attrs_ converter is present on a 

322 field, use it instead of processing the field normally. 

323 :param _cattrs_detailed_validation: Whether to use a slower mode that produces 

324 more detailed errors. 

325 :param _cattrs_use_alias: If true, the attribute alias will be used as the 

326 dictionary key by default. 

327 :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False` 

328 will be included. 

329 

330 .. versionadded:: 24.1.0 

331 .. versionchanged:: 25.2.0 

332 The `_cattrs_use_alias` parameter takes its value from the given converter 

333 by default. 

334 """ 

335 

336 cl_name = cl.__name__ 

337 fn_name = "structure_" + cl_name 

338 

339 # We have generic parameters and need to generate a unique name for the function 

340 for p in getattr(cl, "__parameters__", ()): 

341 # This is nasty, I am not sure how best to handle `typing.List[str]` or 

342 # `TClass[int, int]` as a parameter type here 

343 try: 

344 name_base = typevar_map[p.__name__] 

345 except KeyError: 

346 pn = p.__name__ 

347 raise StructureHandlerNotFoundError( 

348 f"Missing type for generic argument {pn}, specify it when structuring.", 

349 p, 

350 ) from None 

351 name = getattr(name_base, "__name__", None) or str(name_base) 

352 # `<>` can be present in lambdas 

353 # `|` can be present in unions 

354 name = re.sub(r"[\[\.\] ,<>]", "_", name) 

355 name = re.sub(r"\|", "u", name) 

356 fn_name += f"_{name}" 

357 

358 internal_arg_parts = {"__cl": cl} 

359 globs = {} 

360 lines = [] 

361 post_lines = [] 

362 pi_lines = [] # post instantiation lines 

363 invocation_lines = [] 

364 

365 allowed_fields = set() 

366 if _cattrs_forbid_extra_keys == "from_converter": 

367 # BaseConverter doesn't have it so we're careful. 

368 _cattrs_forbid_extra_keys = getattr(converter, "forbid_extra_keys", False) 

369 if _cattrs_use_alias == "from_converter": 

370 # BaseConverter doesn't have it so we're careful. 

371 _cattrs_use_alias = getattr(converter, "use_alias", False) 

372 if _cattrs_detailed_validation == "from_converter": 

373 _cattrs_detailed_validation = converter.detailed_validation 

374 if _cattrs_prefer_attrib_converters == "from_converter": 

375 _cattrs_prefer_attrib_converters = converter._prefer_attrib_converters 

376 

377 if _cattrs_forbid_extra_keys: 

378 globs["__c_a"] = allowed_fields 

379 globs["__c_feke"] = ForbiddenExtraKeysError 

380 

381 if _cattrs_detailed_validation: 

382 lines.append(" res = {}") 

383 lines.append(" errors = []") 

384 invocation_lines.append("**res,") 

385 internal_arg_parts["__c_cve"] = ClassValidationError 

386 internal_arg_parts["__c_avn"] = AttributeValidationNote 

387 for a in attrs: 

388 an = a.name 

389 override = kwargs.get(an, neutral) 

390 if override.omit: 

391 continue 

392 if override.omit is None and not a.init and not _cattrs_include_init_false: 

393 continue 

394 t = a.type 

395 if isinstance(t, TypeVar): 

396 t = typevar_map.get(t.__name__, t) 

397 elif is_generic(t) and not is_bare(t) and not is_annotated(t): 

398 t = deep_copy_with(t, typevar_map, cl) 

399 

400 # For each attribute, we try resolving the type here and now. 

401 # If a type is manually overwritten, this function should be 

402 # regenerated. 

403 if override.struct_hook is not None: 

404 # If the user has requested an override, just use that. 

405 handler = override.struct_hook 

406 else: 

407 handler = find_structure_handler( 

408 a, t, converter, _cattrs_prefer_attrib_converters 

409 ) 

410 

411 struct_handler_name = f"__c_structure_{an}" 

412 if handler is not None: 

413 internal_arg_parts[struct_handler_name] = handler 

414 

415 ian = a.alias 

416 if override.rename is None: 

417 kn = an if not _cattrs_use_alias else a.alias 

418 else: 

419 kn = override.rename 

420 

421 allowed_fields.add(kn) 

422 i = " " 

423 

424 if not a.init: 

425 if a.default is not NOTHING: 

426 pi_lines.append(f"{i}if '{kn}' in o:") 

427 i = f"{i} " 

428 pi_lines.append(f"{i}try:") 

429 i = f"{i} " 

430 type_name = f"__c_type_{an}" 

431 internal_arg_parts[type_name] = t 

432 if handler is not None: 

433 if handler == converter._structure_call: 

434 internal_arg_parts[struct_handler_name] = t 

435 pi_lines.append( 

436 f"{i}instance.{an} = {struct_handler_name}(o['{kn}'])" 

437 ) 

438 else: 

439 tn = f"__c_type_{an}" 

440 internal_arg_parts[tn] = t 

441 pi_lines.append( 

442 f"{i}instance.{an} = {struct_handler_name}(o['{kn}'], {tn})" 

443 ) 

444 else: 

445 pi_lines.append(f"{i}instance.{an} = o['{kn}']") 

446 i = i[:-2] 

447 pi_lines.append(f"{i}except Exception as e:") 

448 i = f"{i} " 

449 pi_lines.append( 

450 f'{i}e.__notes__ = getattr(e, \'__notes__\', []) + [__c_avn("Structuring class {cl.__qualname__} @ attribute {an}", "{an}", __c_type_{an})]' 

451 ) 

452 pi_lines.append(f"{i}errors.append(e)") 

453 

454 else: 

455 if a.default is not NOTHING: 

456 lines.append(f"{i}if '{kn}' in o:") 

457 i = f"{i} " 

458 lines.append(f"{i}try:") 

459 i = f"{i} " 

460 type_name = f"__c_type_{an}" 

461 internal_arg_parts[type_name] = t 

462 if handler: 

463 if handler == converter._structure_call: 

464 internal_arg_parts[struct_handler_name] = t 

465 lines.append( 

466 f"{i}res['{ian}'] = {struct_handler_name}(o['{kn}'])" 

467 ) 

468 else: 

469 lines.append( 

470 f"{i}res['{ian}'] = {struct_handler_name}(o['{kn}'], {type_name})" 

471 ) 

472 else: 

473 lines.append(f"{i}res['{ian}'] = o['{kn}']") 

474 i = i[:-2] 

475 lines.append(f"{i}except Exception as e:") 

476 i = f"{i} " 

477 lines.append( 

478 f'{i}e.__notes__ = getattr(e, \'__notes__\', []) + [__c_avn("Structuring class {cl.__qualname__} @ attribute {an}", "{an}", __c_type_{an})]' 

479 ) 

480 lines.append(f"{i}errors.append(e)") 

481 

482 if _cattrs_forbid_extra_keys: 

483 post_lines += [ 

484 " unknown_fields = set(o.keys()) - __c_a", 

485 " if unknown_fields:", 

486 " errors.append(__c_feke('', __cl, unknown_fields))", 

487 ] 

488 

489 post_lines.append( 

490 f" if errors: raise __c_cve('While structuring ' + {cl_name!r}, errors, __cl)" 

491 ) 

492 if not pi_lines: 

493 instantiation_lines = ( 

494 [" try:"] 

495 + [" return __cl("] 

496 + [f" {line}" for line in invocation_lines] 

497 + [" )"] 

498 + [ 

499 f" except Exception as exc: raise __c_cve('While structuring ' + {cl_name!r}, [exc], __cl)" 

500 ] 

501 ) 

502 else: 

503 instantiation_lines = ( 

504 [" try:"] 

505 + [" instance = __cl("] 

506 + [f" {line}" for line in invocation_lines] 

507 + [" )"] 

508 + [ 

509 f" except Exception as exc: raise __c_cve('While structuring ' + {cl_name!r}, [exc], __cl)" 

510 ] 

511 ) 

512 pi_lines.append(" return instance") 

513 else: 

514 non_required = [] 

515 # The first loop deals with required args. 

516 for a in attrs: 

517 an = a.name 

518 override = kwargs.get(an, neutral) 

519 if override.omit: 

520 continue 

521 if override.omit is None and not a.init and not _cattrs_include_init_false: 

522 continue 

523 if a.default is not NOTHING: 

524 non_required.append(a) 

525 continue 

526 t = a.type 

527 if isinstance(t, TypeVar): 

528 t = typevar_map.get(t.__name__, t) 

529 elif is_generic(t) and not is_bare(t) and not is_annotated(t): 

530 t = deep_copy_with(t, typevar_map, cl) 

531 

532 # For each attribute, we try resolving the type here and now. 

533 # If a type is manually overwritten, this function should be 

534 # regenerated. 

535 if override.struct_hook is not None: 

536 # If the user has requested an override, just use that. 

537 handler = override.struct_hook 

538 else: 

539 handler = find_structure_handler( 

540 a, t, converter, _cattrs_prefer_attrib_converters 

541 ) 

542 

543 if override.rename is None: 

544 kn = an if not _cattrs_use_alias else a.alias 

545 else: 

546 kn = override.rename 

547 allowed_fields.add(kn) 

548 

549 if not a.init: 

550 if handler is not None: 

551 struct_handler_name = f"__c_structure_{an}" 

552 internal_arg_parts[struct_handler_name] = handler 

553 if handler == converter._structure_call: 

554 internal_arg_parts[struct_handler_name] = t 

555 pi_line = f" instance.{an} = {struct_handler_name}(o['{kn}'])" 

556 else: 

557 tn = f"__c_type_{an}" 

558 internal_arg_parts[tn] = t 

559 pi_line = ( 

560 f" instance.{an} = {struct_handler_name}(o['{kn}'], {tn})" 

561 ) 

562 else: 

563 pi_line = f" instance.{an} = o['{kn}']" 

564 

565 pi_lines.append(pi_line) 

566 else: 

567 if handler: 

568 struct_handler_name = f"__c_structure_{an}" 

569 internal_arg_parts[struct_handler_name] = handler 

570 if handler == converter._structure_call: 

571 internal_arg_parts[struct_handler_name] = t 

572 invocation_line = f"{struct_handler_name}(o['{kn}'])," 

573 else: 

574 tn = f"__c_type_{an}" 

575 internal_arg_parts[tn] = t 

576 invocation_line = f"{struct_handler_name}(o['{kn}'], {tn})," 

577 else: 

578 invocation_line = f"o['{kn}']," 

579 

580 if a.kw_only: 

581 invocation_line = f"{a.alias}={invocation_line}" 

582 invocation_lines.append(invocation_line) 

583 

584 # The second loop is for optional args. 

585 if non_required: 

586 invocation_lines.append("**res,") 

587 lines.append(" res = {}") 

588 

589 for a in non_required: 

590 an = a.name 

591 override = kwargs.get(an, neutral) 

592 t = a.type 

593 if isinstance(t, TypeVar): 

594 t = typevar_map.get(t.__name__, t) 

595 elif is_generic(t) and not is_bare(t) and not is_annotated(t): 

596 t = deep_copy_with(t, typevar_map, cl) 

597 

598 # For each attribute, we try resolving the type here and now. 

599 # If a type is manually overwritten, this function should be 

600 # regenerated. 

601 if override.struct_hook is not None: 

602 # If the user has requested an override, just use that. 

603 handler = override.struct_hook 

604 else: 

605 handler = find_structure_handler( 

606 a, t, converter, _cattrs_prefer_attrib_converters 

607 ) 

608 

609 struct_handler_name = f"__c_structure_{an}" 

610 internal_arg_parts[struct_handler_name] = handler 

611 

612 if override.rename is None: 

613 kn = an if not _cattrs_use_alias else a.alias 

614 else: 

615 kn = override.rename 

616 allowed_fields.add(kn) 

617 if not a.init: 

618 pi_lines.append(f" if '{kn}' in o:") 

619 if handler: 

620 if handler == converter._structure_call: 

621 internal_arg_parts[struct_handler_name] = t 

622 pi_lines.append( 

623 f" instance.{an} = {struct_handler_name}(o['{kn}'])" 

624 ) 

625 else: 

626 tn = f"__c_type_{an}" 

627 internal_arg_parts[tn] = t 

628 pi_lines.append( 

629 f" instance.{an} = {struct_handler_name}(o['{kn}'], {tn})" 

630 ) 

631 else: 

632 pi_lines.append(f" instance.{an} = o['{kn}']") 

633 else: 

634 post_lines.append(f" if '{kn}' in o:") 

635 if handler: 

636 if handler == converter._structure_call: 

637 internal_arg_parts[struct_handler_name] = t 

638 post_lines.append( 

639 f" res['{a.alias}'] = {struct_handler_name}(o['{kn}'])" 

640 ) 

641 else: 

642 tn = f"__c_type_{an}" 

643 internal_arg_parts[tn] = t 

644 post_lines.append( 

645 f" res['{a.alias}'] = {struct_handler_name}(o['{kn}'], {tn})" 

646 ) 

647 else: 

648 post_lines.append(f" res['{a.alias}'] = o['{kn}']") 

649 if not pi_lines: 

650 instantiation_lines = ( 

651 [" return __cl("] 

652 + [f" {line}" for line in invocation_lines] 

653 + [" )"] 

654 ) 

655 else: 

656 instantiation_lines = ( 

657 [" instance = __cl("] 

658 + [f" {line}" for line in invocation_lines] 

659 + [" )"] 

660 ) 

661 pi_lines.append(" return instance") 

662 

663 if _cattrs_forbid_extra_keys: 

664 post_lines += [ 

665 " unknown_fields = set(o.keys()) - __c_a", 

666 " if unknown_fields:", 

667 " raise __c_feke('', __cl, unknown_fields)", 

668 ] 

669 

670 # At the end, we create the function header. 

671 internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) 

672 globs.update(internal_arg_parts) 

673 

674 total_lines = [ 

675 f"def {fn_name}(o, _=__cl, {internal_arg_line}):", 

676 *lines, 

677 *post_lines, 

678 *instantiation_lines, 

679 *pi_lines, 

680 ] 

681 

682 script = "\n".join(total_lines) 

683 fname = generate_unique_filename( 

684 cl, "structure", lines=total_lines if _cattrs_use_linecache else [] 

685 ) 

686 

687 eval(compile(script, fname, "exec"), globs) 

688 

689 res = globs[fn_name] 

690 res.overrides = kwargs 

691 

692 return res 

693 

694 

695def make_dict_structure_fn( 

696 cl: type[T], 

697 converter: BaseConverter, 

698 _cattrs_forbid_extra_keys: bool | Literal["from_converter"] = "from_converter", 

699 _cattrs_use_linecache: bool = True, 

700 _cattrs_prefer_attrib_converters: ( 

701 bool | Literal["from_converter"] 

702 ) = "from_converter", 

703 _cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter", 

704 _cattrs_use_alias: bool | Literal["from_converter"] = "from_converter", 

705 _cattrs_include_init_false: bool = False, 

706 **kwargs: AttributeOverride, 

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

708 """ 

709 Generate a specialized dict structuring function for an attrs class or 

710 dataclass. 

711 

712 Any provided overrides are attached to the generated function under the 

713 `overrides` attribute. 

714 

715 :param _cattrs_forbid_extra_keys: Whether the structuring function should raise a 

716 `ForbiddenExtraKeysError` if unknown keys are encountered. 

717 :param _cattrs_use_linecache: Whether to store the source code in the Python 

718 linecache. 

719 :param _cattrs_prefer_attrib_converters: If an _attrs_ converter is present on a 

720 field, use it instead of processing the field normally. 

721 :param _cattrs_detailed_validation: Whether to use a slower mode that produces 

722 more detailed errors. 

723 :param _cattrs_use_alias: If true, the attribute alias will be used as the 

724 dictionary key by default. 

725 :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False` 

726 will be included. 

727 

728 .. versionadded:: 23.2.0 *_cattrs_use_alias* 

729 .. versionadded:: 23.2.0 *_cattrs_include_init_false* 

730 .. versionchanged:: 23.2.0 

731 The `_cattrs_forbid_extra_keys` and `_cattrs_detailed_validation` parameters 

732 take their values from the given converter by default. 

733 .. versionchanged:: 24.1.0 

734 The `_cattrs_prefer_attrib_converters` parameter takes its value from the given 

735 converter by default. 

736 .. versionchanged:: 25.2.0 

737 The `_cattrs_use_alias` parameter takes its value from the given converter 

738 by default. 

739 """ 

740 

741 mapping = {} 

742 if is_generic(cl): 

743 base = get_origin(cl) 

744 mapping = generate_mapping(cl, mapping) 

745 if base is not None: 

746 cl = base 

747 

748 for base in getattr(cl, "__orig_bases__", ()): 

749 if is_generic(base) and not str(base).startswith("typing.Generic"): 

750 mapping = generate_mapping(base, mapping) 

751 break 

752 

753 attrs = adapted_fields(cl) 

754 

755 # We keep track of what we're generating to help with recursive 

756 # class graphs. 

757 try: 

758 working_set = already_generating.working_set 

759 except AttributeError: 

760 working_set = set() 

761 already_generating.working_set = working_set 

762 else: 

763 if cl in working_set: 

764 raise RecursionError() 

765 

766 working_set.add(cl) 

767 

768 try: 

769 return make_dict_structure_fn_from_attrs( 

770 attrs, 

771 cl, 

772 converter, 

773 mapping, 

774 _cattrs_forbid_extra_keys=_cattrs_forbid_extra_keys, 

775 _cattrs_use_linecache=_cattrs_use_linecache, 

776 _cattrs_prefer_attrib_converters=_cattrs_prefer_attrib_converters, 

777 _cattrs_detailed_validation=_cattrs_detailed_validation, 

778 _cattrs_use_alias=_cattrs_use_alias, 

779 _cattrs_include_init_false=_cattrs_include_init_false, 

780 **kwargs, 

781 ) 

782 finally: 

783 working_set.remove(cl) 

784 if not working_set: 

785 del already_generating.working_set 

786 

787 

788IterableUnstructureFn = Callable[[Iterable[Any]], Any] 

789 

790 

791#: A type alias for heterogeneous tuple unstructure hooks. 

792HeteroTupleUnstructureFn: TypeAlias = Callable[[tuple[Any, ...]], Any] 

793 

794 

795def make_hetero_tuple_unstructure_fn( 

796 cl: Any, 

797 converter: BaseConverter, 

798 unstructure_to: Any = None, 

799 type_args: tuple | None = None, 

800) -> HeteroTupleUnstructureFn: 

801 """Generate a specialized unstructure function for a heterogenous tuple. 

802 

803 :param type_args: If provided, override the type arguments. 

804 """ 

805 fn_name = "unstructure_tuple" 

806 

807 type_args = get_args(cl) if type_args is None else type_args 

808 

809 # We can do the dispatch here and now. 

810 handlers = [converter.get_unstructure_hook(type_arg) for type_arg in type_args] 

811 

812 globs = {f"__cattr_u_{i}": h for i, h in enumerate(handlers)} 

813 if unstructure_to is not tuple: 

814 globs["__cattr_seq_cl"] = unstructure_to or cl 

815 lines = [] 

816 

817 lines.append(f"def {fn_name}(tup):") 

818 if unstructure_to is not tuple: 

819 lines.append(" res = __cattr_seq_cl((") 

820 else: 

821 lines.append(" res = (") 

822 for i in range(len(handlers)): 

823 if handlers[i] == identity: 

824 lines.append(f" tup[{i}],") 

825 else: 

826 lines.append(f" __cattr_u_{i}(tup[{i}]),") 

827 

828 if unstructure_to is not tuple: 

829 lines.append(" ))") 

830 else: 

831 lines.append(" )") 

832 

833 total_lines = [*lines, " return res"] 

834 

835 eval(compile("\n".join(total_lines), "", "exec"), globs) 

836 

837 return globs[fn_name] 

838 

839 

840MappingUnstructureFn = Callable[[Mapping[Any, Any]], Any] 

841 

842 

843# This factory is here for backwards compatibility and circular imports. 

844def mapping_unstructure_factory( 

845 cl: Any, 

846 converter: BaseConverter, 

847 unstructure_to: Any = None, 

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

849) -> MappingUnstructureFn: 

850 """Generate a specialized unstructure function for a mapping. 

851 

852 :param unstructure_to: The class to unstructure to; defaults to the 

853 same class as the mapping being unstructured. 

854 """ 

855 kh = key_handler or converter.unstructure 

856 val_handler = converter.unstructure 

857 

858 fn_name = "unstructure_mapping" 

859 origin = cl 

860 

861 # Let's try fishing out the type args. 

862 if getattr(cl, "__args__", None) is not None: 

863 args = get_args(cl) 

864 if len(args) == 2: 

865 key_arg, val_arg = args 

866 else: 

867 # Probably a Counter 

868 key_arg, val_arg = args, Any 

869 # We can do the dispatch here and now. 

870 kh = key_handler or converter.get_unstructure_hook(key_arg, cache_result=False) 

871 if kh == identity: 

872 kh = None 

873 

874 val_handler = converter.get_unstructure_hook(val_arg, cache_result=False) 

875 if val_handler == identity: 

876 val_handler = None 

877 

878 origin = get_origin(cl) 

879 

880 globs = {"__cattr_k_u": kh, "__cattr_v_u": val_handler} 

881 

882 k_u = "__cattr_k_u(k)" if kh is not None else "k" 

883 v_u = "__cattr_v_u(v)" if val_handler is not None else "v" 

884 

885 lines = [f"def {fn_name}(mapping):"] 

886 

887 if unstructure_to is dict or (unstructure_to is None and origin is dict): 

888 if kh is None and val_handler is None: 

889 # Simplest path. 

890 return dict 

891 

892 lines.append(f" return {{{k_u}: {v_u} for k, v in mapping.items()}}") 

893 else: 

894 globs["__cattr_mapping_cl"] = unstructure_to or cl 

895 lines.append( 

896 f" res = __cattr_mapping_cl(({k_u}, {v_u}) for k, v in mapping.items())" 

897 ) 

898 

899 lines = [*lines, " return res"] 

900 

901 eval(compile("\n".join(lines), "", "exec"), globs) 

902 

903 return globs[fn_name] 

904 

905 

906make_mapping_unstructure_fn: Final = mapping_unstructure_factory 

907 

908 

909# This factory is here for backwards compatibility and circular imports. 

910def mapping_structure_factory( 

911 cl: type[T], 

912 converter: BaseConverter, 

913 structure_to: type = dict, 

914 key_type=NOTHING, 

915 val_type=NOTHING, 

916 detailed_validation: bool | Literal["from_converter"] = "from_converter", 

917) -> SimpleStructureHook[Mapping[Any, Any], T]: 

918 """Generate a specialized structure function for a mapping.""" 

919 fn_name = "structure_mapping" 

920 

921 if detailed_validation == "from_converter": 

922 detailed_validation = converter.detailed_validation 

923 

924 globs: dict[str, type] = {"__cattr_mapping_cl": structure_to} 

925 

926 lines = [] 

927 internal_arg_parts = {} 

928 

929 # Let's try fishing out the type args. 

930 if not is_bare(cl): 

931 args = get_args(cl) 

932 if len(args) == 2: 

933 key_arg_cand, val_arg_cand = args 

934 if key_type is NOTHING: 

935 key_type = key_arg_cand 

936 if val_type is NOTHING: 

937 val_type = val_arg_cand 

938 else: 

939 if key_type is not NOTHING and val_type is NOTHING: 

940 (val_type,) = args 

941 elif key_type is NOTHING and val_type is not NOTHING: 

942 (key_type,) = args 

943 else: 

944 # Probably a Counter 

945 (key_type,) = args 

946 val_type = Any 

947 

948 is_bare_dict = val_type in ANIES and key_type in ANIES 

949 if not is_bare_dict: 

950 # We can do the dispatch here and now. 

951 key_handler = converter.get_structure_hook(key_type, cache_result=False) 

952 if key_handler == converter._structure_call: 

953 key_handler = key_type 

954 

955 val_handler = converter.get_structure_hook(val_type, cache_result=False) 

956 if val_handler == converter._structure_call: 

957 val_handler = val_type 

958 

959 globs["__cattr_k_t"] = key_type 

960 globs["__cattr_v_t"] = val_type 

961 globs["__cattr_k_s"] = key_handler 

962 globs["__cattr_v_s"] = val_handler 

963 k_s = ( 

964 "__cattr_k_s(k, __cattr_k_t)" 

965 if key_handler != key_type 

966 else "__cattr_k_s(k)" 

967 ) 

968 v_s = ( 

969 "__cattr_v_s(v, __cattr_v_t)" 

970 if val_handler != val_type 

971 else "__cattr_v_s(v)" 

972 ) 

973 else: 

974 is_bare_dict = True 

975 

976 if is_bare_dict: 

977 # No args, it's a bare dict. 

978 lines.append(" res = dict(mapping)") 

979 else: 

980 if detailed_validation: 

981 internal_arg_parts["IterableValidationError"] = IterableValidationError 

982 internal_arg_parts["IterableValidationNote"] = IterableValidationNote 

983 internal_arg_parts["val_type"] = ( 

984 val_type if val_type is not NOTHING else Any 

985 ) 

986 internal_arg_parts["key_type"] = ( 

987 key_type if key_type is not NOTHING else Any 

988 ) 

989 globs["enumerate"] = enumerate 

990 

991 lines.append(" res = {}; errors = []") 

992 lines.append(" for k, v in mapping.items():") 

993 lines.append(" try:") 

994 lines.append(f" value = {v_s}") 

995 lines.append(" except Exception as e:") 

996 lines.append( 

997 " e.__notes__ = getattr(e, '__notes__', []) + [IterableValidationNote(f'Structuring mapping value @ key {k!r}', k, val_type)]" 

998 ) 

999 lines.append(" errors.append(e)") 

1000 lines.append(" continue") 

1001 lines.append(" try:") 

1002 lines.append(f" key = {k_s}") 

1003 lines.append(" res[key] = value") 

1004 lines.append(" except Exception as e:") 

1005 lines.append( 

1006 " e.__notes__ = getattr(e, '__notes__', []) + [IterableValidationNote(f'Structuring mapping key @ key {k!r}', k, key_type)]" 

1007 ) 

1008 lines.append(" errors.append(e)") 

1009 lines.append(" if errors:") 

1010 lines.append( 

1011 f" raise IterableValidationError('While structuring ' + {repr(cl)!r}, errors, __cattr_mapping_cl)" 

1012 ) 

1013 else: 

1014 lines.append(f" res = {{{k_s}: {v_s} for k, v in mapping.items()}}") 

1015 if structure_to is not dict: 

1016 lines.append(" res = __cattr_mapping_cl(res)") 

1017 

1018 internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) 

1019 if internal_arg_line: 

1020 internal_arg_line = f", {internal_arg_line}" 

1021 for k, v in internal_arg_parts.items(): 

1022 globs[k] = v 

1023 

1024 globs["cl"] = cl 

1025 def_line = f"def {fn_name}(mapping, cl=cl{internal_arg_line}):" 

1026 total_lines = [def_line, *lines, " return res"] 

1027 script = "\n".join(total_lines) 

1028 

1029 eval(compile(script, "", "exec"), globs) 

1030 

1031 return globs[fn_name] 

1032 

1033 

1034make_mapping_structure_fn: Final = mapping_structure_factory 

1035 

1036 

1037# This factory is here for backwards compatibility and circular imports. 

1038def iterable_unstructure_factory( 

1039 cl: Any, converter: BaseConverter, unstructure_to: Any = None 

1040) -> UnstructureHook: 

1041 """A hook factory for unstructuring iterables. 

1042 

1043 :param unstructure_to: Force unstructuring to this type, if provided. 

1044 

1045 .. versionchanged:: 24.2.0 

1046 `typing.NoDefault` is now correctly handled as `Any`. 

1047 """ 

1048 handler = converter.unstructure 

1049 

1050 # Let's try fishing out the type args 

1051 # Unspecified tuples have `__args__` as empty tuples, so guard 

1052 # against IndexError. 

1053 if getattr(cl, "__args__", None) not in (None, ()): 

1054 type_arg = cl.__args__[0] 

1055 if isinstance(type_arg, TypeVar): 

1056 type_arg = getattr(type_arg, "__default__", Any) 

1057 if type_arg is NoDefault: 

1058 type_arg = Any 

1059 handler = converter.get_unstructure_hook(type_arg, cache_result=False) 

1060 if handler == identity: 

1061 # Save ourselves the trouble of iterating over it all. 

1062 return unstructure_to or cl 

1063 

1064 def unstructure_iterable(iterable, _seq_cl=unstructure_to or cl, _hook=handler): 

1065 return _seq_cl(_hook(i) for i in iterable) 

1066 

1067 return unstructure_iterable 

1068 

1069 

1070make_iterable_unstructure_fn: Final = iterable_unstructure_factory