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

490 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 = False, 

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 """ 

100 

101 fn_name = "unstructure_" + cl.__name__ 

102 globs = {} 

103 lines = [] 

104 invocation_lines = [] 

105 internal_arg_parts = {} 

106 

107 for a in attrs: 

108 attr_name = a.name 

109 override = kwargs.get(attr_name, neutral) 

110 if override.omit: 

111 continue 

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

113 continue 

114 if override.rename is None: 

115 kn = attr_name if not _cattrs_use_alias else a.alias 

116 else: 

117 kn = override.rename 

118 d = a.default 

119 

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

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

122 # regenerated. 

123 handler = None 

124 if override.unstruct_hook is not None: 

125 handler = override.unstruct_hook 

126 else: 

127 if a.type is not None: 

128 t = a.type 

129 if isinstance(t, TypeVar): 

130 if t.__name__ in typevar_map: 

131 t = typevar_map[t.__name__] 

132 else: 

133 handler = converter.unstructure 

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

135 t = deep_copy_with(t, typevar_map, cl) 

136 

137 if handler is None: 

138 if ( 

139 is_bare_final(t) 

140 and a.default is not NOTHING 

141 and not isinstance(a.default, Factory) 

142 ): 

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

144 # type of the default to dispatch on. 

145 t = a.default.__class__ 

146 try: 

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

148 except RecursionError: 

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

150 handler = converter.unstructure 

151 else: 

152 handler = converter.unstructure 

153 

154 is_identity = handler == identity 

155 

156 if not is_identity: 

157 unstruct_handler_name = f"__c_unstr_{attr_name}" 

158 globs[unstruct_handler_name] = handler 

159 internal_arg_parts[unstruct_handler_name] = handler 

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

161 else: 

162 invoke = f"instance.{attr_name}" 

163 

164 if d is not NOTHING and ( 

165 (_cattrs_omit_if_default and override.omit_if_default is not False) 

166 or override.omit_if_default 

167 ): 

168 def_name = f"__c_def_{attr_name}" 

169 

170 if isinstance(d, Factory): 

171 globs[def_name] = d.factory 

172 internal_arg_parts[def_name] = d.factory 

173 if d.takes_self: 

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

175 else: 

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

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

178 else: 

179 globs[def_name] = d 

180 internal_arg_parts[def_name] = d 

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

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

183 

184 else: 

185 # No default or no override. 

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

187 

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

189 if internal_arg_line: 

190 internal_arg_line = f", {internal_arg_line}" 

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

192 globs[k] = v 

193 

194 total_lines = ( 

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

196 + [" res = {"] 

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

198 + [" }"] 

199 + lines 

200 + [" return res"] 

201 ) 

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

203 fname = generate_unique_filename( 

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

205 ) 

206 

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

208 

209 res = globs[fn_name] 

210 res.overrides = kwargs 

211 

212 return res 

213 

214 

215def make_dict_unstructure_fn( 

216 cl: type[T], 

217 converter: BaseConverter, 

218 _cattrs_omit_if_default: bool = False, 

219 _cattrs_use_linecache: bool = True, 

220 _cattrs_use_alias: bool = False, 

221 _cattrs_include_init_false: bool = False, 

222 **kwargs: AttributeOverride, 

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

224 """ 

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

226 dataclass. 

227 

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

229 `overrides` attribute. 

230 

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

232 will be omitted in the result dictionary. 

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

234 dictionary key by default. 

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

236 will be included. 

237 

238 .. versionadded:: 23.2.0 *_cattrs_use_alias* 

239 .. versionadded:: 23.2.0 *_cattrs_include_init_false* 

240 """ 

241 origin = get_origin(cl) 

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

243 

244 mapping = {} 

245 if is_generic(cl): 

246 mapping = generate_mapping(cl, mapping) 

247 

248 if origin is not None: 

249 cl = origin 

250 

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

252 # class graphs. 

253 try: 

254 working_set = already_generating.working_set 

255 except AttributeError: 

256 working_set = set() 

257 already_generating.working_set = working_set 

258 if cl in working_set: 

259 raise RecursionError() 

260 

261 working_set.add(cl) 

262 

263 try: 

264 return make_dict_unstructure_fn_from_attrs( 

265 attrs, 

266 cl, 

267 converter, 

268 mapping, 

269 _cattrs_omit_if_default=_cattrs_omit_if_default, 

270 _cattrs_use_linecache=_cattrs_use_linecache, 

271 _cattrs_use_alias=_cattrs_use_alias, 

272 _cattrs_include_init_false=_cattrs_include_init_false, 

273 **kwargs, 

274 ) 

275 finally: 

276 working_set.remove(cl) 

277 if not working_set: 

278 del already_generating.working_set 

279 

280 

281def make_dict_structure_fn_from_attrs( 

282 attrs: list[Attribute], 

283 cl: type[T], 

284 converter: BaseConverter, 

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

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

287 _cattrs_use_linecache: bool = True, 

288 _cattrs_prefer_attrib_converters: ( 

289 bool | Literal["from_converter"] 

290 ) = "from_converter", 

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

292 _cattrs_use_alias: bool = False, 

293 _cattrs_include_init_false: bool = False, 

294 **kwargs: AttributeOverride, 

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

296 """ 

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

298 

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

300 

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

302 `overrides` attribute. 

303 

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

305 `ForbiddenExtraKeysError` if unknown keys are encountered. 

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

307 linecache. 

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

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

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

311 more detailed errors. 

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

313 dictionary key by default. 

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

315 will be included. 

316 

317 .. versionadded:: 24.1.0 

318 """ 

319 

320 cl_name = cl.__name__ 

321 fn_name = "structure_" + cl_name 

322 

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

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

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

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

327 try: 

328 name_base = typevar_map[p.__name__] 

329 except KeyError: 

330 pn = p.__name__ 

331 raise StructureHandlerNotFoundError( 

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

333 p, 

334 ) from None 

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

336 # `<>` can be present in lambdas 

337 # `|` can be present in unions 

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

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

340 fn_name += f"_{name}" 

341 

342 internal_arg_parts = {"__cl": cl} 

343 globs = {} 

344 lines = [] 

345 post_lines = [] 

346 pi_lines = [] # post instantiation lines 

347 invocation_lines = [] 

348 

349 allowed_fields = set() 

350 if _cattrs_forbid_extra_keys == "from_converter": 

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

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

353 if _cattrs_detailed_validation == "from_converter": 

354 _cattrs_detailed_validation = converter.detailed_validation 

355 if _cattrs_prefer_attrib_converters == "from_converter": 

356 _cattrs_prefer_attrib_converters = converter._prefer_attrib_converters 

357 

358 if _cattrs_forbid_extra_keys: 

359 globs["__c_a"] = allowed_fields 

360 globs["__c_feke"] = ForbiddenExtraKeysError 

361 

362 if _cattrs_detailed_validation: 

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

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

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

366 internal_arg_parts["__c_cve"] = ClassValidationError 

367 internal_arg_parts["__c_avn"] = AttributeValidationNote 

368 for a in attrs: 

369 an = a.name 

370 override = kwargs.get(an, neutral) 

371 if override.omit: 

372 continue 

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

374 continue 

375 t = a.type 

376 if isinstance(t, TypeVar): 

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

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

379 t = deep_copy_with(t, typevar_map, cl) 

380 

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

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

383 # regenerated. 

384 if override.struct_hook is not None: 

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

386 handler = override.struct_hook 

387 else: 

388 handler = find_structure_handler( 

389 a, t, converter, _cattrs_prefer_attrib_converters 

390 ) 

391 

392 struct_handler_name = f"__c_structure_{an}" 

393 if handler is not None: 

394 internal_arg_parts[struct_handler_name] = handler 

395 

396 ian = a.alias 

397 if override.rename is None: 

398 kn = an if not _cattrs_use_alias else a.alias 

399 else: 

400 kn = override.rename 

401 

402 allowed_fields.add(kn) 

403 i = " " 

404 

405 if not a.init: 

406 if a.default is not NOTHING: 

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

408 i = f"{i} " 

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

410 i = f"{i} " 

411 type_name = f"__c_type_{an}" 

412 internal_arg_parts[type_name] = t 

413 if handler is not None: 

414 if handler == converter._structure_call: 

415 internal_arg_parts[struct_handler_name] = t 

416 pi_lines.append( 

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

418 ) 

419 else: 

420 tn = f"__c_type_{an}" 

421 internal_arg_parts[tn] = t 

422 pi_lines.append( 

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

424 ) 

425 else: 

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

427 i = i[:-2] 

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

429 i = f"{i} " 

430 pi_lines.append( 

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

432 ) 

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

434 

435 else: 

436 if a.default is not NOTHING: 

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

438 i = f"{i} " 

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

440 i = f"{i} " 

441 type_name = f"__c_type_{an}" 

442 internal_arg_parts[type_name] = t 

443 if handler: 

444 if handler == converter._structure_call: 

445 internal_arg_parts[struct_handler_name] = t 

446 lines.append( 

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

448 ) 

449 else: 

450 lines.append( 

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

452 ) 

453 else: 

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

455 i = i[:-2] 

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

457 i = f"{i} " 

458 lines.append( 

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

460 ) 

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

462 

463 if _cattrs_forbid_extra_keys: 

464 post_lines += [ 

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

466 " if unknown_fields:", 

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

468 ] 

469 

470 post_lines.append( 

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

472 ) 

473 if not pi_lines: 

474 instantiation_lines = ( 

475 [" try:"] 

476 + [" return __cl("] 

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

478 + [" )"] 

479 + [ 

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

481 ] 

482 ) 

483 else: 

484 instantiation_lines = ( 

485 [" try:"] 

486 + [" instance = __cl("] 

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

488 + [" )"] 

489 + [ 

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

491 ] 

492 ) 

493 pi_lines.append(" return instance") 

494 else: 

495 non_required = [] 

496 # The first loop deals with required args. 

497 for a in attrs: 

498 an = a.name 

499 override = kwargs.get(an, neutral) 

500 if override.omit: 

501 continue 

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

503 continue 

504 if a.default is not NOTHING: 

505 non_required.append(a) 

506 continue 

507 t = a.type 

508 if isinstance(t, TypeVar): 

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

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

511 t = deep_copy_with(t, typevar_map, cl) 

512 

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

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

515 # regenerated. 

516 if override.struct_hook is not None: 

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

518 handler = override.struct_hook 

519 else: 

520 handler = find_structure_handler( 

521 a, t, converter, _cattrs_prefer_attrib_converters 

522 ) 

523 

524 if override.rename is None: 

525 kn = an if not _cattrs_use_alias else a.alias 

526 else: 

527 kn = override.rename 

528 allowed_fields.add(kn) 

529 

530 if not a.init: 

531 if handler is not None: 

532 struct_handler_name = f"__c_structure_{an}" 

533 internal_arg_parts[struct_handler_name] = handler 

534 if handler == converter._structure_call: 

535 internal_arg_parts[struct_handler_name] = t 

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

537 else: 

538 tn = f"__c_type_{an}" 

539 internal_arg_parts[tn] = t 

540 pi_line = ( 

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

542 ) 

543 else: 

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

545 

546 pi_lines.append(pi_line) 

547 else: 

548 if handler: 

549 struct_handler_name = f"__c_structure_{an}" 

550 internal_arg_parts[struct_handler_name] = handler 

551 if handler == converter._structure_call: 

552 internal_arg_parts[struct_handler_name] = t 

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

554 else: 

555 tn = f"__c_type_{an}" 

556 internal_arg_parts[tn] = t 

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

558 else: 

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

560 

561 if a.kw_only: 

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

563 invocation_lines.append(invocation_line) 

564 

565 # The second loop is for optional args. 

566 if non_required: 

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

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

569 

570 for a in non_required: 

571 an = a.name 

572 override = kwargs.get(an, neutral) 

573 t = a.type 

574 if isinstance(t, TypeVar): 

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

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

577 t = deep_copy_with(t, typevar_map, cl) 

578 

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

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

581 # regenerated. 

582 if override.struct_hook is not None: 

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

584 handler = override.struct_hook 

585 else: 

586 handler = find_structure_handler( 

587 a, t, converter, _cattrs_prefer_attrib_converters 

588 ) 

589 

590 struct_handler_name = f"__c_structure_{an}" 

591 internal_arg_parts[struct_handler_name] = handler 

592 

593 if override.rename is None: 

594 kn = an if not _cattrs_use_alias else a.alias 

595 else: 

596 kn = override.rename 

597 allowed_fields.add(kn) 

598 if not a.init: 

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

600 if handler: 

601 if handler == converter._structure_call: 

602 internal_arg_parts[struct_handler_name] = t 

603 pi_lines.append( 

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

605 ) 

606 else: 

607 tn = f"__c_type_{an}" 

608 internal_arg_parts[tn] = t 

609 pi_lines.append( 

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

611 ) 

612 else: 

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

614 else: 

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

616 if handler: 

617 if handler == converter._structure_call: 

618 internal_arg_parts[struct_handler_name] = t 

619 post_lines.append( 

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

621 ) 

622 else: 

623 tn = f"__c_type_{an}" 

624 internal_arg_parts[tn] = t 

625 post_lines.append( 

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

627 ) 

628 else: 

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

630 if not pi_lines: 

631 instantiation_lines = ( 

632 [" return __cl("] 

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

634 + [" )"] 

635 ) 

636 else: 

637 instantiation_lines = ( 

638 [" instance = __cl("] 

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

640 + [" )"] 

641 ) 

642 pi_lines.append(" return instance") 

643 

644 if _cattrs_forbid_extra_keys: 

645 post_lines += [ 

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

647 " if unknown_fields:", 

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

649 ] 

650 

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

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

653 globs.update(internal_arg_parts) 

654 

655 total_lines = [ 

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

657 *lines, 

658 *post_lines, 

659 *instantiation_lines, 

660 *pi_lines, 

661 ] 

662 

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

664 fname = generate_unique_filename( 

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

666 ) 

667 

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

669 

670 res = globs[fn_name] 

671 res.overrides = kwargs 

672 

673 return res 

674 

675 

676def make_dict_structure_fn( 

677 cl: type[T], 

678 converter: BaseConverter, 

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

680 _cattrs_use_linecache: bool = True, 

681 _cattrs_prefer_attrib_converters: ( 

682 bool | Literal["from_converter"] 

683 ) = "from_converter", 

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

685 _cattrs_use_alias: bool = False, 

686 _cattrs_include_init_false: bool = False, 

687 **kwargs: AttributeOverride, 

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

689 """ 

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

691 dataclass. 

692 

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

694 `overrides` attribute. 

695 

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

697 `ForbiddenExtraKeysError` if unknown keys are encountered. 

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

699 linecache. 

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

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

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

703 more detailed errors. 

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

705 dictionary key by default. 

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

707 will be included. 

708 

709 .. versionadded:: 23.2.0 *_cattrs_use_alias* 

710 .. versionadded:: 23.2.0 *_cattrs_include_init_false* 

711 .. versionchanged:: 23.2.0 

712 The `_cattrs_forbid_extra_keys` and `_cattrs_detailed_validation` parameters 

713 take their values from the given converter by default. 

714 .. versionchanged:: 24.1.0 

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

716 converter by default. 

717 """ 

718 

719 mapping = {} 

720 if is_generic(cl): 

721 base = get_origin(cl) 

722 mapping = generate_mapping(cl, mapping) 

723 if base is not None: 

724 cl = base 

725 

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

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

728 mapping = generate_mapping(base, mapping) 

729 break 

730 

731 attrs = adapted_fields(cl) 

732 

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

734 # class graphs. 

735 try: 

736 working_set = already_generating.working_set 

737 except AttributeError: 

738 working_set = set() 

739 already_generating.working_set = working_set 

740 else: 

741 if cl in working_set: 

742 raise RecursionError() 

743 

744 working_set.add(cl) 

745 

746 try: 

747 return make_dict_structure_fn_from_attrs( 

748 attrs, 

749 cl, 

750 converter, 

751 mapping, 

752 _cattrs_forbid_extra_keys=_cattrs_forbid_extra_keys, 

753 _cattrs_use_linecache=_cattrs_use_linecache, 

754 _cattrs_prefer_attrib_converters=_cattrs_prefer_attrib_converters, 

755 _cattrs_detailed_validation=_cattrs_detailed_validation, 

756 _cattrs_use_alias=_cattrs_use_alias, 

757 _cattrs_include_init_false=_cattrs_include_init_false, 

758 **kwargs, 

759 ) 

760 finally: 

761 working_set.remove(cl) 

762 if not working_set: 

763 del already_generating.working_set 

764 

765 

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

767 

768 

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

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

771 

772 

773def make_hetero_tuple_unstructure_fn( 

774 cl: Any, 

775 converter: BaseConverter, 

776 unstructure_to: Any = None, 

777 type_args: tuple | None = None, 

778) -> HeteroTupleUnstructureFn: 

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

780 

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

782 """ 

783 fn_name = "unstructure_tuple" 

784 

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

786 

787 # We can do the dispatch here and now. 

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

789 

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

791 if unstructure_to is not tuple: 

792 globs["__cattr_seq_cl"] = unstructure_to or cl 

793 lines = [] 

794 

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

796 if unstructure_to is not tuple: 

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

798 else: 

799 lines.append(" res = (") 

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

801 if handlers[i] == identity: 

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

803 else: 

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

805 

806 if unstructure_to is not tuple: 

807 lines.append(" ))") 

808 else: 

809 lines.append(" )") 

810 

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

812 

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

814 

815 return globs[fn_name] 

816 

817 

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

819 

820 

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

822def mapping_unstructure_factory( 

823 cl: Any, 

824 converter: BaseConverter, 

825 unstructure_to: Any = None, 

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

827) -> MappingUnstructureFn: 

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

829 

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

831 same class as the mapping being unstructured. 

832 """ 

833 kh = key_handler or converter.unstructure 

834 val_handler = converter.unstructure 

835 

836 fn_name = "unstructure_mapping" 

837 origin = cl 

838 

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

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

841 args = get_args(cl) 

842 if len(args) == 2: 

843 key_arg, val_arg = args 

844 else: 

845 # Probably a Counter 

846 key_arg, val_arg = args, Any 

847 # We can do the dispatch here and now. 

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

849 if kh == identity: 

850 kh = None 

851 

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

853 if val_handler == identity: 

854 val_handler = None 

855 

856 origin = get_origin(cl) 

857 

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

859 

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

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

862 

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

864 

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

866 if kh is None and val_handler is None: 

867 # Simplest path. 

868 return dict 

869 

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

871 else: 

872 globs["__cattr_mapping_cl"] = unstructure_to or cl 

873 lines.append( 

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

875 ) 

876 

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

878 

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

880 

881 return globs[fn_name] 

882 

883 

884make_mapping_unstructure_fn: Final = mapping_unstructure_factory 

885 

886 

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

888def mapping_structure_factory( 

889 cl: type[T], 

890 converter: BaseConverter, 

891 structure_to: type = dict, 

892 key_type=NOTHING, 

893 val_type=NOTHING, 

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

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

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

897 fn_name = "structure_mapping" 

898 

899 if detailed_validation == "from_converter": 

900 detailed_validation = converter.detailed_validation 

901 

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

903 

904 lines = [] 

905 internal_arg_parts = {} 

906 

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

908 if not is_bare(cl): 

909 args = get_args(cl) 

910 if len(args) == 2: 

911 key_arg_cand, val_arg_cand = args 

912 if key_type is NOTHING: 

913 key_type = key_arg_cand 

914 if val_type is NOTHING: 

915 val_type = val_arg_cand 

916 else: 

917 if key_type is not NOTHING and val_type is NOTHING: 

918 (val_type,) = args 

919 elif key_type is NOTHING and val_type is not NOTHING: 

920 (key_type,) = args 

921 else: 

922 # Probably a Counter 

923 (key_type,) = args 

924 val_type = Any 

925 

926 is_bare_dict = val_type in ANIES and key_type in ANIES 

927 if not is_bare_dict: 

928 # We can do the dispatch here and now. 

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

930 if key_handler == converter._structure_call: 

931 key_handler = key_type 

932 

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

934 if val_handler == converter._structure_call: 

935 val_handler = val_type 

936 

937 globs["__cattr_k_t"] = key_type 

938 globs["__cattr_v_t"] = val_type 

939 globs["__cattr_k_s"] = key_handler 

940 globs["__cattr_v_s"] = val_handler 

941 k_s = ( 

942 "__cattr_k_s(k, __cattr_k_t)" 

943 if key_handler != key_type 

944 else "__cattr_k_s(k)" 

945 ) 

946 v_s = ( 

947 "__cattr_v_s(v, __cattr_v_t)" 

948 if val_handler != val_type 

949 else "__cattr_v_s(v)" 

950 ) 

951 else: 

952 is_bare_dict = True 

953 

954 if is_bare_dict: 

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

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

957 else: 

958 if detailed_validation: 

959 internal_arg_parts["IterableValidationError"] = IterableValidationError 

960 internal_arg_parts["IterableValidationNote"] = IterableValidationNote 

961 internal_arg_parts["val_type"] = ( 

962 val_type if val_type is not NOTHING else Any 

963 ) 

964 internal_arg_parts["key_type"] = ( 

965 key_type if key_type is not NOTHING else Any 

966 ) 

967 globs["enumerate"] = enumerate 

968 

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

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

971 lines.append(" try:") 

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

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

974 lines.append( 

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

976 ) 

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

978 lines.append(" continue") 

979 lines.append(" try:") 

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

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

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

983 lines.append( 

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

985 ) 

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

987 lines.append(" if errors:") 

988 lines.append( 

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

990 ) 

991 else: 

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

993 if structure_to is not dict: 

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

995 

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

997 if internal_arg_line: 

998 internal_arg_line = f", {internal_arg_line}" 

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

1000 globs[k] = v 

1001 

1002 globs["cl"] = cl 

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

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

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

1006 

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

1008 

1009 return globs[fn_name] 

1010 

1011 

1012make_mapping_structure_fn: Final = mapping_structure_factory 

1013 

1014 

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

1016def iterable_unstructure_factory( 

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

1018) -> UnstructureHook: 

1019 """A hook factory for unstructuring iterables. 

1020 

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

1022 

1023 .. versionchanged:: 24.2.0 

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

1025 """ 

1026 handler = converter.unstructure 

1027 

1028 # Let's try fishing out the type args 

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

1030 # against IndexError. 

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

1032 type_arg = cl.__args__[0] 

1033 if isinstance(type_arg, TypeVar): 

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

1035 if type_arg is NoDefault: 

1036 type_arg = Any 

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

1038 if handler == identity: 

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

1040 return unstructure_to or cl 

1041 

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

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

1044 

1045 return unstructure_iterable 

1046 

1047 

1048make_iterable_unstructure_fn: Final = iterable_unstructure_factory