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

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

530 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, Converter, Factory, evolve 

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 _annotated_override_or_default, 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 .. versionchanged:: 26.1.0 

103 `typing.Annotated[T, override()]` is now recognized and can be used to customize 

104 unstructuring. 

105 .. versionchanged:: 26.1.0 

106 When `_cattrs_omit_if_default` is true and the attribute has an attrs converter 

107 specified, the converter is applied to the default value before checking if it 

108 is equal to the attribute's value. 

109 """ 

110 

111 fn_name = "unstructure_" + cl.__name__ 

112 globs = {} 

113 lines = [] 

114 invocation_lines = [] 

115 internal_arg_parts = {} 

116 

117 if _cattrs_use_alias == "from_converter": 

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

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

120 

121 for a in attrs: 

122 attr_name = a.name 

123 if attr_name in kwargs: 

124 override = kwargs[attr_name] 

125 else: 

126 override = _annotated_override_or_default(a.type, neutral) 

127 if override != neutral: 

128 kwargs[attr_name] = override 

129 

130 if override.omit: 

131 continue 

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

133 continue 

134 if override.rename is None: 

135 kn = attr_name if not _cattrs_use_alias else a.alias 

136 if kn != attr_name: 

137 kwargs[attr_name] = evolve(override, rename=kn) 

138 else: 

139 kn = override.rename 

140 d = a.default 

141 

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

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

144 # regenerated. 

145 handler = None 

146 if override.unstruct_hook is not None: 

147 handler = override.unstruct_hook 

148 else: 

149 if a.type is not None: 

150 t = a.type 

151 if isinstance(t, TypeVar): 

152 if t.__name__ in typevar_map: 

153 t = typevar_map[t.__name__] 

154 else: 

155 handler = converter.unstructure 

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

157 t = deep_copy_with(t, typevar_map, cl) 

158 

159 if handler is None: 

160 if ( 

161 is_bare_final(t) 

162 and a.default is not NOTHING 

163 and not isinstance(a.default, Factory) 

164 ): 

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

166 # type of the default to dispatch on. 

167 t = a.default.__class__ 

168 try: 

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

170 except RecursionError: 

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

172 handler = converter.unstructure 

173 else: 

174 handler = converter.unstructure 

175 

176 is_identity = handler == identity 

177 

178 if not is_identity: 

179 unstruct_handler_name = f"__c_unstr_{attr_name}" 

180 globs[unstruct_handler_name] = handler 

181 internal_arg_parts[unstruct_handler_name] = handler 

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

183 else: 

184 invoke = f"instance.{attr_name}" 

185 

186 if d is not NOTHING and ( 

187 (_cattrs_omit_if_default and override.omit_if_default is not False) 

188 or override.omit_if_default 

189 ): 

190 def_name = f"__c_def_{attr_name}" 

191 

192 if isinstance(d, Factory): 

193 globs[def_name] = d.factory 

194 internal_arg_parts[def_name] = d.factory 

195 def_str = f"{def_name}(instance)" if d.takes_self else f"{def_name}()" 

196 else: 

197 globs[def_name] = d 

198 internal_arg_parts[def_name] = d 

199 def_str = def_name 

200 

201 c = a.converter 

202 if c is not None: 

203 conv_name = f"__c_conv_{attr_name}" 

204 if isinstance(c, Converter): 

205 globs[conv_name] = c 

206 internal_arg_parts[conv_name] = c 

207 field_name = f"__c_field_{attr_name}" 

208 globs[field_name] = a 

209 internal_arg_parts[field_name] = a 

210 def_str = f"{conv_name}({def_str}, instance, {field_name})" 

211 elif isinstance(d, Factory): 

212 globs[conv_name] = c 

213 internal_arg_parts[conv_name] = c 

214 def_str = f"{conv_name}({def_str})" 

215 else: 

216 globs[def_name] = c(d) 

217 internal_arg_parts[def_name] = c(d) 

218 

219 lines.append(f" if instance.{attr_name} != {def_str}:") 

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

221 

222 else: 

223 # No default or no override. 

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

225 

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

227 if internal_arg_line: 

228 internal_arg_line = f", {internal_arg_line}" 

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

230 globs[k] = v 

231 

232 total_lines = ( 

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

234 + [" res = {"] 

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

236 + [" }"] 

237 + lines 

238 + [" return res"] 

239 ) 

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

241 fname = generate_unique_filename( 

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

243 ) 

244 

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

246 

247 res = globs[fn_name] 

248 res.overrides = kwargs 

249 

250 return res 

251 

252 

253def make_dict_unstructure_fn( 

254 cl: type[T], 

255 converter: BaseConverter, 

256 _cattrs_omit_if_default: bool = False, 

257 _cattrs_use_linecache: bool = True, 

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

259 _cattrs_include_init_false: bool = False, 

260 **kwargs: AttributeOverride, 

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

262 """ 

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

264 dataclass. 

265 

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

267 `overrides` attribute. 

268 

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

270 will be omitted in the result dictionary. 

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

272 dictionary key by default. 

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

274 will be included. 

275 

276 .. versionadded:: 23.2.0 *_cattrs_use_alias* 

277 .. versionadded:: 23.2.0 *_cattrs_include_init_false* 

278 .. versionchanged:: 25.2.0 

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

280 by default. 

281 .. versionchanged:: 26.1.0 

282 `typing.Annotated[T, override()]` is now recognized and can be used to customize 

283 unstructuring. 

284 """ 

285 origin = get_origin(cl) 

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

287 

288 mapping = {} 

289 if _cattrs_use_alias == "from_converter": 

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

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

292 if is_generic(cl): 

293 mapping = generate_mapping(cl, mapping) 

294 

295 if origin is not None: 

296 cl = origin 

297 

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

299 # class graphs. 

300 try: 

301 working_set = already_generating.working_set 

302 except AttributeError: 

303 working_set = set() 

304 already_generating.working_set = working_set 

305 if cl in working_set: 

306 raise RecursionError() 

307 

308 working_set.add(cl) 

309 

310 try: 

311 return make_dict_unstructure_fn_from_attrs( 

312 attrs, 

313 cl, 

314 converter, 

315 mapping, 

316 _cattrs_omit_if_default=_cattrs_omit_if_default, 

317 _cattrs_use_linecache=_cattrs_use_linecache, 

318 _cattrs_use_alias=_cattrs_use_alias, 

319 _cattrs_include_init_false=_cattrs_include_init_false, 

320 **kwargs, 

321 ) 

322 finally: 

323 working_set.remove(cl) 

324 if not working_set: 

325 del already_generating.working_set 

326 

327 

328def make_dict_structure_fn_from_attrs( 

329 attrs: list[Attribute], 

330 cl: type[T], 

331 converter: BaseConverter, 

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

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

334 _cattrs_use_linecache: bool = True, 

335 _cattrs_prefer_attrib_converters: ( 

336 bool | Literal["from_converter"] 

337 ) = "from_converter", 

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

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

340 _cattrs_include_init_false: bool = False, 

341 **kwargs: AttributeOverride, 

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

343 """ 

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

345 

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

347 

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

349 `overrides` attribute. 

350 

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

352 `ForbiddenExtraKeysError` if unknown keys are encountered. 

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

354 linecache. 

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

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

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

358 more detailed errors. 

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

360 dictionary key by default. 

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

362 will be included. 

363 

364 .. versionadded:: 24.1.0 

365 .. versionchanged:: 25.2.0 

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

367 by default. 

368 .. versionchanged:: 26.1.0 

369 `typing.Annotated[T, override()]` is now recognized and can be used to customize 

370 unstructuring. 

371 """ 

372 

373 cl_name = cl.__name__ 

374 fn_name = "structure_" + cl_name 

375 

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

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

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

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

380 try: 

381 name_base = typevar_map[p.__name__] 

382 except KeyError: 

383 pn = p.__name__ 

384 raise StructureHandlerNotFoundError( 

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

386 p, 

387 ) from None 

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

389 # `<>` can be present in lambdas 

390 # `|` can be present in unions 

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

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

393 fn_name += f"_{name}" 

394 

395 internal_arg_parts = {"__cl": cl} 

396 globs = {} 

397 lines = [] 

398 post_lines = [] 

399 pi_lines = [] # post instantiation lines 

400 invocation_lines = [] 

401 

402 allowed_fields = set() 

403 if _cattrs_forbid_extra_keys == "from_converter": 

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

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

406 if _cattrs_use_alias == "from_converter": 

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

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

409 if _cattrs_detailed_validation == "from_converter": 

410 _cattrs_detailed_validation = converter.detailed_validation 

411 if _cattrs_prefer_attrib_converters == "from_converter": 

412 _cattrs_prefer_attrib_converters = converter._prefer_attrib_converters 

413 

414 if _cattrs_forbid_extra_keys: 

415 globs["__c_a"] = allowed_fields 

416 globs["__c_feke"] = ForbiddenExtraKeysError 

417 

418 if _cattrs_detailed_validation: 

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

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

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

422 internal_arg_parts["__c_cve"] = ClassValidationError 

423 internal_arg_parts["__c_avn"] = AttributeValidationNote 

424 for a in attrs: 

425 an = a.name 

426 if an in kwargs: 

427 override = kwargs[an] 

428 else: 

429 override = _annotated_override_or_default(a.type, neutral) 

430 if override != neutral: 

431 kwargs[an] = override 

432 

433 if override.omit: 

434 continue 

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

436 continue 

437 t = a.type 

438 if isinstance(t, TypeVar): 

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

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

441 t = deep_copy_with(t, typevar_map, cl) 

442 

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

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

445 # regenerated. 

446 if override.struct_hook is not None: 

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

448 handler = override.struct_hook 

449 else: 

450 handler = find_structure_handler( 

451 a, t, converter, _cattrs_prefer_attrib_converters 

452 ) 

453 

454 struct_handler_name = f"__c_structure_{an}" 

455 if handler is not None: 

456 internal_arg_parts[struct_handler_name] = handler 

457 

458 ian = a.alias 

459 if override.rename is None: 

460 kn = an if not _cattrs_use_alias else a.alias 

461 if kn != an: 

462 kwargs[an] = evolve(override, rename=kn) 

463 else: 

464 kn = override.rename 

465 

466 allowed_fields.add(kn) 

467 i = " " 

468 

469 if not a.init: 

470 if a.default is not NOTHING: 

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

472 i = f"{i} " 

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

474 i = f"{i} " 

475 type_name = f"__c_type_{an}" 

476 internal_arg_parts[type_name] = t 

477 if handler is not None: 

478 if handler == converter._structure_call: 

479 internal_arg_parts[struct_handler_name] = t 

480 pi_lines.append( 

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

482 ) 

483 else: 

484 tn = f"__c_type_{an}" 

485 internal_arg_parts[tn] = t 

486 pi_lines.append( 

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

488 ) 

489 else: 

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

491 i = i[:-2] 

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

493 i = f"{i} " 

494 pi_lines.append( 

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

496 ) 

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

498 

499 else: 

500 if a.default is not NOTHING: 

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

502 i = f"{i} " 

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

504 i = f"{i} " 

505 type_name = f"__c_type_{an}" 

506 internal_arg_parts[type_name] = t 

507 if handler: 

508 if handler == converter._structure_call: 

509 internal_arg_parts[struct_handler_name] = t 

510 lines.append( 

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

512 ) 

513 else: 

514 lines.append( 

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

516 ) 

517 else: 

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

519 i = i[:-2] 

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

521 i = f"{i} " 

522 lines.append( 

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

524 ) 

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

526 

527 if _cattrs_forbid_extra_keys: 

528 post_lines += [ 

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

530 " if unknown_fields:", 

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

532 ] 

533 

534 post_lines.append( 

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

536 ) 

537 if not pi_lines: 

538 instantiation_lines = ( 

539 [" try:"] 

540 + [" return __cl("] 

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

542 + [" )"] 

543 + [ 

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

545 ] 

546 ) 

547 else: 

548 instantiation_lines = ( 

549 [" try:"] 

550 + [" instance = __cl("] 

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

552 + [" )"] 

553 + [ 

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

555 ] 

556 ) 

557 pi_lines.append(" return instance") 

558 else: 

559 non_required = [] 

560 # The first loop deals with required args. 

561 for a in attrs: 

562 an = a.name 

563 

564 if an in kwargs: 

565 override = kwargs[an] 

566 else: 

567 override = _annotated_override_or_default(a.type, neutral) 

568 if override != neutral: 

569 kwargs[an] = override 

570 

571 if override.omit: 

572 continue 

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

574 continue 

575 

576 if a.default is not NOTHING: 

577 non_required.append(a) 

578 # The next loop will handle it. 

579 continue 

580 

581 t = a.type 

582 if isinstance(t, TypeVar): 

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

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

585 t = deep_copy_with(t, typevar_map, cl) 

586 

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

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

589 # regenerated. 

590 if override.struct_hook is not None: 

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

592 handler = override.struct_hook 

593 else: 

594 handler = find_structure_handler( 

595 a, t, converter, _cattrs_prefer_attrib_converters 

596 ) 

597 

598 if override.rename is None: 

599 kn = an if not _cattrs_use_alias else a.alias 

600 if kn != an: 

601 kwargs[an] = evolve(override, rename=kn) 

602 else: 

603 kn = override.rename 

604 allowed_fields.add(kn) 

605 

606 if not a.init: 

607 if handler is not None: 

608 struct_handler_name = f"__c_structure_{an}" 

609 internal_arg_parts[struct_handler_name] = handler 

610 if handler == converter._structure_call: 

611 internal_arg_parts[struct_handler_name] = t 

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

613 else: 

614 tn = f"__c_type_{an}" 

615 internal_arg_parts[tn] = t 

616 pi_line = ( 

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

618 ) 

619 else: 

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

621 

622 pi_lines.append(pi_line) 

623 else: 

624 if handler: 

625 struct_handler_name = f"__c_structure_{an}" 

626 internal_arg_parts[struct_handler_name] = handler 

627 if handler == converter._structure_call: 

628 internal_arg_parts[struct_handler_name] = t 

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

630 else: 

631 tn = f"__c_type_{an}" 

632 internal_arg_parts[tn] = t 

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

634 else: 

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

636 

637 if a.kw_only: 

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

639 invocation_lines.append(invocation_line) 

640 

641 # The second loop is for optional args. 

642 if non_required: 

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

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

645 

646 for a in non_required: 

647 an = a.name 

648 override = kwargs.get(an, neutral) 

649 t = a.type 

650 if isinstance(t, TypeVar): 

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

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

653 t = deep_copy_with(t, typevar_map, cl) 

654 

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

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

657 # regenerated. 

658 if override.struct_hook is not None: 

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

660 handler = override.struct_hook 

661 else: 

662 handler = find_structure_handler( 

663 a, t, converter, _cattrs_prefer_attrib_converters 

664 ) 

665 

666 struct_handler_name = f"__c_structure_{an}" 

667 internal_arg_parts[struct_handler_name] = handler 

668 

669 if override.rename is None: 

670 kn = an if not _cattrs_use_alias else a.alias 

671 if kn != an: 

672 kwargs[an] = evolve(override, rename=kn) 

673 else: 

674 kn = override.rename 

675 allowed_fields.add(kn) 

676 if not a.init: 

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

678 if handler: 

679 if handler == converter._structure_call: 

680 internal_arg_parts[struct_handler_name] = t 

681 pi_lines.append( 

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

683 ) 

684 else: 

685 tn = f"__c_type_{an}" 

686 internal_arg_parts[tn] = t 

687 pi_lines.append( 

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

689 ) 

690 else: 

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

692 else: 

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

694 if handler: 

695 if handler == converter._structure_call: 

696 internal_arg_parts[struct_handler_name] = t 

697 post_lines.append( 

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

699 ) 

700 else: 

701 tn = f"__c_type_{an}" 

702 internal_arg_parts[tn] = t 

703 post_lines.append( 

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

705 ) 

706 else: 

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

708 if not pi_lines: 

709 instantiation_lines = ( 

710 [" return __cl("] 

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

712 + [" )"] 

713 ) 

714 else: 

715 instantiation_lines = ( 

716 [" instance = __cl("] 

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

718 + [" )"] 

719 ) 

720 pi_lines.append(" return instance") 

721 

722 if _cattrs_forbid_extra_keys: 

723 post_lines += [ 

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

725 " if unknown_fields:", 

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

727 ] 

728 

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

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

731 globs.update(internal_arg_parts) 

732 

733 total_lines = [ 

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

735 *lines, 

736 *post_lines, 

737 *instantiation_lines, 

738 *pi_lines, 

739 ] 

740 

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

742 fname = generate_unique_filename( 

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

744 ) 

745 

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

747 

748 res = globs[fn_name] 

749 res.overrides = kwargs 

750 

751 return res 

752 

753 

754def make_dict_structure_fn( 

755 cl: type[T], 

756 converter: BaseConverter, 

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

758 _cattrs_use_linecache: bool = True, 

759 _cattrs_prefer_attrib_converters: ( 

760 bool | Literal["from_converter"] 

761 ) = "from_converter", 

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

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

764 _cattrs_include_init_false: bool = False, 

765 **kwargs: AttributeOverride, 

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

767 """ 

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

769 dataclass. 

770 

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

772 `overrides` attribute. 

773 

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

775 `ForbiddenExtraKeysError` if unknown keys are encountered. 

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

777 linecache. 

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

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

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

781 more detailed errors. 

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

783 dictionary key by default. 

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

785 will be included. 

786 

787 .. versionadded:: 23.2.0 *_cattrs_use_alias* 

788 .. versionadded:: 23.2.0 *_cattrs_include_init_false* 

789 .. versionchanged:: 23.2.0 

790 The `_cattrs_forbid_extra_keys` and `_cattrs_detailed_validation` parameters 

791 take their values from the given converter by default. 

792 .. versionchanged:: 24.1.0 

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

794 converter by default. 

795 .. versionchanged:: 25.2.0 

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

797 by default. 

798 .. versionchanged:: 26.1.0 

799 `typing.Annotated[T, override()]` is now recognized and can be used to customize 

800 unstructuring. 

801 """ 

802 

803 mapping = {} 

804 if is_generic(cl): 

805 base = get_origin(cl) 

806 mapping = generate_mapping(cl, mapping) 

807 if base is not None: 

808 cl = base 

809 

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

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

812 mapping = generate_mapping(base, mapping) 

813 break 

814 

815 attrs = adapted_fields(cl) 

816 

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

818 # class graphs. 

819 try: 

820 working_set = already_generating.working_set 

821 except AttributeError: 

822 working_set = set() 

823 already_generating.working_set = working_set 

824 else: 

825 if cl in working_set: 

826 raise RecursionError() 

827 

828 working_set.add(cl) 

829 

830 try: 

831 return make_dict_structure_fn_from_attrs( 

832 attrs, 

833 cl, 

834 converter, 

835 mapping, 

836 _cattrs_forbid_extra_keys=_cattrs_forbid_extra_keys, 

837 _cattrs_use_linecache=_cattrs_use_linecache, 

838 _cattrs_prefer_attrib_converters=_cattrs_prefer_attrib_converters, 

839 _cattrs_detailed_validation=_cattrs_detailed_validation, 

840 _cattrs_use_alias=_cattrs_use_alias, 

841 _cattrs_include_init_false=_cattrs_include_init_false, 

842 **kwargs, 

843 ) 

844 finally: 

845 working_set.remove(cl) 

846 if not working_set: 

847 del already_generating.working_set 

848 

849 

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

851 

852 

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

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

855 

856 

857def make_hetero_tuple_unstructure_fn( 

858 cl: Any, 

859 converter: BaseConverter, 

860 unstructure_to: Any = None, 

861 type_args: tuple | None = None, 

862) -> HeteroTupleUnstructureFn: 

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

864 

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

866 """ 

867 fn_name = "unstructure_tuple" 

868 

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

870 

871 # We can do the dispatch here and now. 

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

873 

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

875 if unstructure_to is not tuple: 

876 globs["__cattr_seq_cl"] = unstructure_to or cl 

877 lines = [] 

878 

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

880 if unstructure_to is not tuple: 

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

882 else: 

883 lines.append(" res = (") 

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

885 if handlers[i] == identity: 

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

887 else: 

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

889 

890 if unstructure_to is not tuple: 

891 lines.append(" ))") 

892 else: 

893 lines.append(" )") 

894 

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

896 

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

898 

899 return globs[fn_name] 

900 

901 

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

903 

904 

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

906def mapping_unstructure_factory( 

907 cl: Any, 

908 converter: BaseConverter, 

909 unstructure_to: Any = None, 

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

911) -> MappingUnstructureFn: 

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

913 

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

915 same class as the mapping being unstructured. 

916 """ 

917 kh = key_handler or converter.unstructure 

918 val_handler = converter.unstructure 

919 

920 fn_name = "unstructure_mapping" 

921 origin = cl 

922 

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

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

925 args = get_args(cl) 

926 if len(args) == 2: 

927 key_arg, val_arg = args 

928 else: 

929 # Probably a Counter 

930 key_arg, val_arg = args, Any 

931 # We can do the dispatch here and now. 

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

933 if kh == identity: 

934 kh = None 

935 

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

937 if val_handler == identity: 

938 val_handler = None 

939 

940 origin = get_origin(cl) 

941 

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

943 

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

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

946 

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

948 

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

950 if kh is None and val_handler is None: 

951 # Simplest path. 

952 return dict 

953 

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

955 else: 

956 globs["__cattr_mapping_cl"] = unstructure_to or cl 

957 lines.append( 

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

959 ) 

960 

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

962 

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

964 

965 return globs[fn_name] 

966 

967 

968make_mapping_unstructure_fn: Final = mapping_unstructure_factory 

969 

970 

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

972def mapping_structure_factory( 

973 cl: type[T], 

974 converter: BaseConverter, 

975 structure_to: type = dict, 

976 key_type=NOTHING, 

977 val_type=NOTHING, 

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

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

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

981 fn_name = "structure_mapping" 

982 

983 if detailed_validation == "from_converter": 

984 detailed_validation = converter.detailed_validation 

985 

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

987 

988 lines = [] 

989 internal_arg_parts = {} 

990 

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

992 if not is_bare(cl): 

993 args = get_args(cl) 

994 if len(args) == 2: 

995 key_arg_cand, val_arg_cand = args 

996 if key_type is NOTHING: 

997 key_type = key_arg_cand 

998 if val_type is NOTHING: 

999 val_type = val_arg_cand 

1000 else: 

1001 if key_type is not NOTHING and val_type is NOTHING: 

1002 (val_type,) = args 

1003 elif key_type is NOTHING and val_type is not NOTHING: 

1004 (key_type,) = args 

1005 else: 

1006 # Probably a Counter 

1007 (key_type,) = args 

1008 val_type = Any 

1009 

1010 is_bare_dict = val_type in ANIES and key_type in ANIES 

1011 if not is_bare_dict: 

1012 # We can do the dispatch here and now. 

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

1014 if key_handler == converter._structure_call: 

1015 key_handler = key_type 

1016 

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

1018 if val_handler == converter._structure_call: 

1019 val_handler = val_type 

1020 

1021 globs["__cattr_k_t"] = key_type 

1022 globs["__cattr_v_t"] = val_type 

1023 globs["__cattr_k_s"] = key_handler 

1024 globs["__cattr_v_s"] = val_handler 

1025 k_s = ( 

1026 "__cattr_k_s(k, __cattr_k_t)" 

1027 if key_handler != key_type 

1028 else "__cattr_k_s(k)" 

1029 ) 

1030 v_s = ( 

1031 "__cattr_v_s(v, __cattr_v_t)" 

1032 if val_handler != val_type 

1033 else "__cattr_v_s(v)" 

1034 ) 

1035 else: 

1036 is_bare_dict = True 

1037 

1038 if is_bare_dict: 

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

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

1041 else: 

1042 if detailed_validation: 

1043 internal_arg_parts["IterableValidationError"] = IterableValidationError 

1044 internal_arg_parts["IterableValidationNote"] = IterableValidationNote 

1045 internal_arg_parts["val_type"] = ( 

1046 val_type if val_type is not NOTHING else Any 

1047 ) 

1048 internal_arg_parts["key_type"] = ( 

1049 key_type if key_type is not NOTHING else Any 

1050 ) 

1051 globs["enumerate"] = enumerate 

1052 

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

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

1055 lines.append(" try:") 

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

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

1058 lines.append( 

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

1060 ) 

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

1062 lines.append(" continue") 

1063 lines.append(" try:") 

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

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

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

1067 lines.append( 

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

1069 ) 

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

1071 lines.append(" if errors:") 

1072 lines.append( 

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

1074 ) 

1075 else: 

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

1077 if structure_to is not dict: 

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

1079 

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

1081 if internal_arg_line: 

1082 internal_arg_line = f", {internal_arg_line}" 

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

1084 globs[k] = v 

1085 

1086 globs["cl"] = cl 

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

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

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

1090 

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

1092 

1093 return globs[fn_name] 

1094 

1095 

1096make_mapping_structure_fn: Final = mapping_structure_factory 

1097 

1098 

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

1100def iterable_unstructure_factory( 

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

1102) -> UnstructureHook: 

1103 """A hook factory for unstructuring iterables. 

1104 

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

1106 

1107 .. versionchanged:: 24.2.0 

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

1109 """ 

1110 handler = converter.unstructure 

1111 

1112 # Let's try fishing out the type args 

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

1114 # against IndexError. 

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

1116 type_arg = cl.__args__[0] 

1117 if isinstance(type_arg, TypeVar): 

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

1119 if type_arg is NoDefault: 

1120 type_arg = Any 

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

1122 if handler == identity: 

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

1124 return unstructure_to or cl 

1125 

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

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

1128 

1129 return unstructure_iterable 

1130 

1131 

1132make_iterable_unstructure_fn: Final = iterable_unstructure_factory