Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/attr/_make.py: 63%

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

1068 statements  

1# SPDX-License-Identifier: MIT 

2 

3from __future__ import annotations 

4 

5import abc 

6import contextlib 

7import copy 

8import enum 

9import inspect 

10import itertools 

11import linecache 

12import sys 

13import types 

14import unicodedata 

15 

16from collections.abc import Callable, Mapping 

17from functools import cached_property 

18from typing import Any, NamedTuple, TypeVar 

19 

20# We need to import _compat itself in addition to the _compat members to avoid 

21# having the thread-local in the globals here. 

22from . import _compat, _config, setters 

23from ._compat import ( 

24 PY_3_10_PLUS, 

25 PY_3_11_PLUS, 

26 PY_3_13_PLUS, 

27 _AnnotationExtractor, 

28 _get_annotations, 

29 get_generic_base, 

30) 

31from .exceptions import ( 

32 DefaultAlreadySetError, 

33 FrozenInstanceError, 

34 NotAnAttrsClassError, 

35 UnannotatedAttributeError, 

36) 

37 

38 

39# This is used at least twice, so cache it here. 

40_OBJ_SETATTR = object.__setattr__ 

41_INIT_FACTORY_PAT = "__attr_factory_%s" 

42_CLASSVAR_PREFIXES = ( 

43 "typing.ClassVar", 

44 "t.ClassVar", 

45 "ClassVar", 

46 "typing_extensions.ClassVar", 

47) 

48# we don't use a double-underscore prefix because that triggers 

49# name mangling when trying to create a slot for the field 

50# (when slots=True) 

51_HASH_CACHE_FIELD = "_attrs_cached_hash" 

52 

53_EMPTY_METADATA_SINGLETON = types.MappingProxyType({}) 

54 

55# Unique object for unequivocal getattr() defaults. 

56_SENTINEL = object() 

57 

58_DEFAULT_ON_SETATTR = setters.pipe(setters.convert, setters.validate) 

59 

60 

61class _Nothing(enum.Enum): 

62 """ 

63 Sentinel to indicate the lack of a value when `None` is ambiguous. 

64 

65 If extending attrs, you can use ``typing.Literal[NOTHING]`` to show 

66 that a value may be ``NOTHING``. 

67 

68 .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False. 

69 .. versionchanged:: 22.2.0 ``NOTHING`` is now an ``enum.Enum`` variant. 

70 """ 

71 

72 NOTHING = enum.auto() 

73 

74 def __repr__(self): 

75 return "NOTHING" 

76 

77 def __bool__(self): 

78 return False 

79 

80 

81NOTHING = _Nothing.NOTHING 

82""" 

83Sentinel to indicate the lack of a value when `None` is ambiguous. 

84 

85When using in 3rd party code, use `attrs.NothingType` for type annotations. 

86""" 

87 

88 

89class _CacheHashWrapper(int): 

90 """ 

91 An integer subclass that pickles / copies as None 

92 

93 This is used for non-slots classes with ``cache_hash=True``, to avoid 

94 serializing a potentially (even likely) invalid hash value. Since `None` 

95 is the default value for uncalculated hashes, whenever this is copied, 

96 the copy's value for the hash should automatically reset. 

97 

98 See GH #613 for more details. 

99 """ 

100 

101 def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008 

102 return _none_constructor, _args 

103 

104 

105def attrib( 

106 default=NOTHING, 

107 validator=None, 

108 repr=True, 

109 cmp=None, 

110 hash=None, 

111 init=True, 

112 metadata=None, 

113 type=None, 

114 converter=None, 

115 factory=None, 

116 kw_only=None, 

117 eq=None, 

118 order=None, 

119 on_setattr=None, 

120 alias=None, 

121): 

122 """ 

123 Create a new field / attribute on a class. 

124 

125 Identical to `attrs.field`, except it's not keyword-only. 

126 

127 Consider using `attrs.field` in new code (``attr.ib`` will *never* go away, 

128 though). 

129 

130 .. warning:: 

131 

132 Does **nothing** unless the class is also decorated with 

133 `attr.s` (or similar)! 

134 

135 

136 .. versionadded:: 15.2.0 *convert* 

137 .. versionadded:: 16.3.0 *metadata* 

138 .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. 

139 .. versionchanged:: 17.1.0 

140 *hash* is `None` and therefore mirrors *eq* by default. 

141 .. versionadded:: 17.3.0 *type* 

142 .. deprecated:: 17.4.0 *convert* 

143 .. versionadded:: 17.4.0 

144 *converter* as a replacement for the deprecated *convert* to achieve 

145 consistency with other noun-based arguments. 

146 .. versionadded:: 18.1.0 

147 ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``. 

148 .. versionadded:: 18.2.0 *kw_only* 

149 .. versionchanged:: 19.2.0 *convert* keyword argument removed. 

150 .. versionchanged:: 19.2.0 *repr* also accepts a custom callable. 

151 .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. 

152 .. versionadded:: 19.2.0 *eq* and *order* 

153 .. versionadded:: 20.1.0 *on_setattr* 

154 .. versionchanged:: 20.3.0 *kw_only* backported to Python 2 

155 .. versionchanged:: 21.1.0 

156 *eq*, *order*, and *cmp* also accept a custom callable 

157 .. versionchanged:: 21.1.0 *cmp* undeprecated 

158 .. versionadded:: 22.2.0 *alias* 

159 .. versionchanged:: 25.4.0 

160 *kw_only* can now be None, and its default is also changed from False to 

161 None. 

162 """ 

163 eq, eq_key, order, order_key = _determine_attrib_eq_order( 

164 cmp, eq, order, True 

165 ) 

166 

167 if hash is not None and hash is not True and hash is not False: 

168 msg = "Invalid value for hash. Must be True, False, or None." 

169 raise TypeError(msg) 

170 

171 if factory is not None: 

172 if default is not NOTHING: 

173 msg = ( 

174 "The `default` and `factory` arguments are mutually exclusive." 

175 ) 

176 raise ValueError(msg) 

177 if not callable(factory): 

178 msg = "The `factory` argument must be a callable." 

179 raise ValueError(msg) 

180 default = Factory(factory) 

181 

182 if metadata is None: 

183 metadata = {} 

184 

185 # Apply syntactic sugar by auto-wrapping. 

186 if isinstance(on_setattr, (list, tuple)): 

187 on_setattr = setters.pipe(*on_setattr) 

188 

189 if validator and isinstance(validator, (list, tuple)): 

190 validator = and_(*validator) 

191 

192 if converter and isinstance(converter, (list, tuple)): 

193 converter = pipe(*converter) 

194 

195 return _CountingAttr( 

196 default=default, 

197 validator=validator, 

198 repr=repr, 

199 cmp=None, 

200 hash=hash, 

201 init=init, 

202 converter=converter, 

203 metadata=metadata, 

204 type=type, 

205 kw_only=kw_only, 

206 eq=eq, 

207 eq_key=eq_key, 

208 order=order, 

209 order_key=order_key, 

210 on_setattr=on_setattr, 

211 alias=alias, 

212 ) 

213 

214 

215def _compile_and_eval( 

216 script: str, 

217 globs: dict[str, Any] | None, 

218 locs: Mapping[str, object] | None = None, 

219 filename: str = "", 

220) -> None: 

221 """ 

222 Evaluate the script with the given global (globs) and local (locs) 

223 variables. 

224 """ 

225 bytecode = compile(script, filename, "exec") 

226 eval(bytecode, globs, locs) 

227 

228 

229def _linecache_and_compile( 

230 script: str, 

231 filename: str, 

232 globs: dict[str, Any] | None, 

233 locals: Mapping[str, object] | None = None, 

234) -> dict[str, Any]: 

235 """ 

236 Cache the script with _linecache_, compile it and return the _locals_. 

237 """ 

238 

239 locs = {} if locals is None else locals 

240 

241 # In order of debuggers like PDB being able to step through the code, 

242 # we add a fake linecache entry. 

243 count = 1 

244 base_filename = filename 

245 while True: 

246 linecache_tuple = ( 

247 len(script), 

248 None, 

249 script.splitlines(True), 

250 filename, 

251 ) 

252 old_val = linecache.cache.setdefault(filename, linecache_tuple) 

253 if old_val == linecache_tuple: 

254 break 

255 

256 filename = f"{base_filename[:-1]}-{count}>" 

257 count += 1 

258 

259 _compile_and_eval(script, globs, locs, filename) 

260 

261 return locs 

262 

263 

264def _make_attr_tuple_class(cls_name: str, attr_names: list[str]) -> type: 

265 """ 

266 Create a tuple subclass to hold `Attribute`s for an `attrs` class. 

267 

268 The subclass is a bare tuple with properties for names. 

269 

270 class MyClassAttributes(tuple): 

271 __slots__ = () 

272 x = property(itemgetter(0)) 

273 """ 

274 attr_class_name = f"{cls_name}Attributes" 

275 body = {} 

276 for i, attr_name in enumerate(attr_names): 

277 

278 def getter(self, i=i): 

279 return self[i] 

280 

281 body[attr_name] = property(getter) 

282 return type(attr_class_name, (tuple,), body) 

283 

284 

285# Tuple class for extracted attributes from a class definition. 

286# `base_attrs` is a subset of `attrs`. 

287class _Attributes(NamedTuple): 

288 attrs: type 

289 base_attrs: list[Attribute] 

290 base_attrs_map: dict[str, type] 

291 

292 

293def _is_class_var(annot): 

294 """ 

295 Check whether *annot* is a typing.ClassVar. 

296 

297 The string comparison hack is used to avoid evaluating all string 

298 annotations which would put attrs-based classes at a performance 

299 disadvantage compared to plain old classes. 

300 """ 

301 annot = str(annot) 

302 

303 # Annotation can be quoted. 

304 if annot.startswith(("'", '"')) and annot.endswith(("'", '"')): 

305 annot = annot[1:-1] 

306 

307 return annot.startswith(_CLASSVAR_PREFIXES) 

308 

309 

310def _has_own_attribute(cls, attrib_name): 

311 """ 

312 Check whether *cls* defines *attrib_name* (and doesn't just inherit it). 

313 """ 

314 return attrib_name in cls.__dict__ 

315 

316 

317def _collect_base_attrs( 

318 cls, taken_attr_names 

319) -> tuple[list[Attribute], dict[str, type]]: 

320 """ 

321 Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. 

322 """ 

323 base_attrs = [] 

324 base_attr_map = {} # A dictionary of base attrs to their classes. 

325 

326 # Traverse the MRO and collect attributes. 

327 for base_cls in reversed(cls.__mro__[1:-1]): 

328 for a in getattr(base_cls, "__attrs_attrs__", []): 

329 if a.inherited or a.name in taken_attr_names: 

330 continue 

331 

332 a = a.evolve(inherited=True) # noqa: PLW2901 

333 base_attrs.append(a) 

334 base_attr_map[a.name] = base_cls 

335 

336 # For each name, only keep the freshest definition i.e. the furthest at the 

337 # back. base_attr_map is fine because it gets overwritten with every new 

338 # instance. 

339 filtered = [] 

340 seen = set() 

341 for a in reversed(base_attrs): 

342 if a.name in seen: 

343 continue 

344 filtered.insert(0, a) 

345 seen.add(a.name) 

346 

347 return filtered, base_attr_map 

348 

349 

350def _collect_base_attrs_broken(cls, taken_attr_names): 

351 """ 

352 Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. 

353 

354 N.B. *taken_attr_names* will be mutated. 

355 

356 Adhere to the old incorrect behavior. 

357 

358 Notably it collects from the front and considers inherited attributes which 

359 leads to the buggy behavior reported in #428. 

360 """ 

361 base_attrs = [] 

362 base_attr_map = {} # A dictionary of base attrs to their classes. 

363 

364 # Traverse the MRO and collect attributes. 

365 for base_cls in cls.__mro__[1:-1]: 

366 for a in getattr(base_cls, "__attrs_attrs__", []): 

367 if a.name in taken_attr_names: 

368 continue 

369 

370 a = a.evolve(inherited=True) # noqa: PLW2901 

371 taken_attr_names.add(a.name) 

372 base_attrs.append(a) 

373 base_attr_map[a.name] = base_cls 

374 

375 return base_attrs, base_attr_map 

376 

377 

378def _transform_attrs( 

379 cls, 

380 these, 

381 auto_attribs, 

382 kw_only, 

383 force_kw_only, 

384 collect_by_mro, 

385 field_transformer, 

386) -> _Attributes: 

387 """ 

388 Transform all `_CountingAttr`s on a class into `Attribute`s. 

389 

390 If *these* is passed, use that and don't look for them on the class. 

391 

392 If *collect_by_mro* is True, collect them in the correct MRO order, 

393 otherwise use the old -- incorrect -- order. See #428. 

394 

395 Return an `_Attributes`. 

396 """ 

397 cd = cls.__dict__ 

398 anns = _get_annotations(cls) 

399 

400 if these is not None: 

401 ca_list = list(these.items()) 

402 elif auto_attribs is True: 

403 ca_names = { 

404 name 

405 for name, attr in cd.items() 

406 if attr.__class__ is _CountingAttr 

407 } 

408 ca_list = [] 

409 annot_names = set() 

410 for attr_name, type in anns.items(): 

411 if _is_class_var(type): 

412 continue 

413 annot_names.add(attr_name) 

414 a = cd.get(attr_name, NOTHING) 

415 

416 if a.__class__ is not _CountingAttr: 

417 a = attrib(a) 

418 ca_list.append((attr_name, a)) 

419 

420 unannotated = ca_names - annot_names 

421 if unannotated: 

422 raise UnannotatedAttributeError( 

423 "The following `attr.ib`s lack a type annotation: " 

424 + ", ".join( 

425 sorted(unannotated, key=lambda n: cd.get(n).counter) 

426 ) 

427 + "." 

428 ) 

429 else: 

430 ca_list = sorted( 

431 ( 

432 (name, attr) 

433 for name, attr in cd.items() 

434 if attr.__class__ is _CountingAttr 

435 ), 

436 key=lambda e: e[1].counter, 

437 ) 

438 

439 fca = Attribute.from_counting_attr 

440 own_attrs = [ 

441 fca(attr_name, ca, kw_only, anns.get(attr_name)) 

442 for attr_name, ca in ca_list 

443 ] 

444 

445 if collect_by_mro: 

446 base_attrs, base_attr_map = _collect_base_attrs( 

447 cls, {a.name for a in own_attrs} 

448 ) 

449 else: 

450 base_attrs, base_attr_map = _collect_base_attrs_broken( 

451 cls, {a.name for a in own_attrs} 

452 ) 

453 

454 if kw_only and force_kw_only: 

455 own_attrs = [a.evolve(kw_only=True) for a in own_attrs] 

456 base_attrs = [a.evolve(kw_only=True) for a in base_attrs] 

457 

458 attrs = base_attrs + own_attrs 

459 

460 if field_transformer is not None: 

461 attrs = tuple(field_transformer(cls, attrs)) 

462 

463 # Check attr order after executing the field_transformer. 

464 # Mandatory vs non-mandatory attr order only matters when they are part of 

465 # the __init__ signature and when they aren't kw_only (which are moved to 

466 # the end and can be mandatory or non-mandatory in any order, as they will 

467 # be specified as keyword args anyway). Check the order of those attrs: 

468 had_default = False 

469 for a in (a for a in attrs if a.init is not False and a.kw_only is False): 

470 if had_default is True and a.default is NOTHING: 

471 msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}" 

472 raise ValueError(msg) 

473 

474 if had_default is False and a.default is not NOTHING: 

475 had_default = True 

476 

477 # Resolve default field alias after executing field_transformer. 

478 # This allows field_transformer to differentiate between explicit vs 

479 # default aliases and supply their own defaults. 

480 for a in attrs: 

481 if not a.alias: 

482 # Evolve is very slow, so we hold our nose and do it dirty. 

483 _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name)) 

484 

485 # Create AttrsClass *after* applying the field_transformer since it may 

486 # add or remove attributes! 

487 attr_names = [a.name for a in attrs] 

488 AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) 

489 

490 return _Attributes(AttrsClass(attrs), base_attrs, base_attr_map) 

491 

492 

493def _make_cached_property_getattr(cached_properties, original_getattr, cls): 

494 lines = [ 

495 # Wrapped to get `__class__` into closure cell for super() 

496 # (It will be replaced with the newly constructed class after construction). 

497 "def wrapper(_cls):", 

498 " __class__ = _cls", 

499 " def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):", 

500 " func = cached_properties.get(item)", 

501 " if func is not None:", 

502 " result = func(self)", 

503 " _setter = _cached_setattr_get(self)", 

504 " _setter(item, result)", 

505 " return result", 

506 ] 

507 if original_getattr is not None: 

508 lines.append( 

509 " return original_getattr(self, item)", 

510 ) 

511 else: 

512 lines.extend( 

513 [ 

514 " try:", 

515 " return super().__getattribute__(item)", 

516 " except AttributeError:", 

517 " if not hasattr(super(), '__getattr__'):", 

518 " raise", 

519 " return super().__getattr__(item)", 

520 " original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"", 

521 " raise AttributeError(original_error)", 

522 ] 

523 ) 

524 

525 lines.extend( 

526 [ 

527 " return __getattr__", 

528 "__getattr__ = wrapper(_cls)", 

529 ] 

530 ) 

531 

532 unique_filename = _generate_unique_filename(cls, "getattr") 

533 

534 glob = { 

535 "cached_properties": cached_properties, 

536 "_cached_setattr_get": _OBJ_SETATTR.__get__, 

537 "original_getattr": original_getattr, 

538 } 

539 

540 return _linecache_and_compile( 

541 "\n".join(lines), unique_filename, glob, locals={"_cls": cls} 

542 )["__getattr__"] 

543 

544 

545def _frozen_setattrs(self, name, value): 

546 """ 

547 Attached to frozen classes as __setattr__. 

548 """ 

549 if isinstance(self, BaseException) and name in ( 

550 "__cause__", 

551 "__context__", 

552 "__traceback__", 

553 "__suppress_context__", 

554 "__notes__", 

555 ): 

556 BaseException.__setattr__(self, name, value) 

557 return 

558 

559 raise FrozenInstanceError 

560 

561 

562def _frozen_delattrs(self, name): 

563 """ 

564 Attached to frozen classes as __delattr__. 

565 """ 

566 if isinstance(self, BaseException) and name in ("__notes__",): 

567 BaseException.__delattr__(self, name) 

568 return 

569 

570 raise FrozenInstanceError 

571 

572 

573def evolve(*args, **changes): 

574 """ 

575 Create a new instance, based on the first positional argument with 

576 *changes* applied. 

577 

578 .. tip:: 

579 

580 On Python 3.13 and later, you can also use `copy.replace` instead. 

581 

582 Args: 

583 

584 inst: 

585 Instance of a class with *attrs* attributes. *inst* must be passed 

586 as a positional argument. 

587 

588 changes: 

589 Keyword changes in the new copy. 

590 

591 Returns: 

592 A copy of inst with *changes* incorporated. 

593 

594 Raises: 

595 TypeError: 

596 If *attr_name* couldn't be found in the class ``__init__``. 

597 

598 attrs.exceptions.NotAnAttrsClassError: 

599 If *cls* is not an *attrs* class. 

600 

601 .. versionadded:: 17.1.0 

602 .. deprecated:: 23.1.0 

603 It is now deprecated to pass the instance using the keyword argument 

604 *inst*. It will raise a warning until at least April 2024, after which 

605 it will become an error. Always pass the instance as a positional 

606 argument. 

607 .. versionchanged:: 24.1.0 

608 *inst* can't be passed as a keyword argument anymore. 

609 """ 

610 try: 

611 (inst,) = args 

612 except ValueError: 

613 msg = ( 

614 f"evolve() takes 1 positional argument, but {len(args)} were given" 

615 ) 

616 raise TypeError(msg) from None 

617 

618 cls = inst.__class__ 

619 attrs = fields(cls) 

620 for a in attrs: 

621 if not a.init: 

622 continue 

623 attr_name = a.name # To deal with private attributes. 

624 init_name = a.alias 

625 if init_name not in changes: 

626 changes[init_name] = getattr(inst, attr_name) 

627 

628 return cls(**changes) 

629 

630 

631class _ClassBuilder: 

632 """ 

633 Iteratively build *one* class. 

634 """ 

635 

636 __slots__ = ( 

637 "_add_method_dunders", 

638 "_attr_names", 

639 "_attrs", 

640 "_base_attr_map", 

641 "_base_names", 

642 "_cache_hash", 

643 "_cls", 

644 "_cls_dict", 

645 "_delete_attribs", 

646 "_frozen", 

647 "_has_custom_setattr", 

648 "_has_post_init", 

649 "_has_pre_init", 

650 "_is_exc", 

651 "_on_setattr", 

652 "_pre_init_has_args", 

653 "_repr_added", 

654 "_script_snippets", 

655 "_slots", 

656 "_weakref_slot", 

657 "_wrote_own_setattr", 

658 ) 

659 

660 def __init__( 

661 self, 

662 cls: type, 

663 these, 

664 slots, 

665 frozen, 

666 weakref_slot, 

667 getstate_setstate, 

668 auto_attribs, 

669 kw_only, 

670 force_kw_only, 

671 cache_hash, 

672 is_exc, 

673 collect_by_mro, 

674 on_setattr, 

675 has_custom_setattr, 

676 field_transformer, 

677 ): 

678 attrs, base_attrs, base_map = _transform_attrs( 

679 cls, 

680 these, 

681 auto_attribs, 

682 kw_only, 

683 force_kw_only, 

684 collect_by_mro, 

685 field_transformer, 

686 ) 

687 

688 self._cls = cls 

689 self._cls_dict = dict(cls.__dict__) if slots else {} 

690 self._attrs = attrs 

691 self._base_names = {a.name for a in base_attrs} 

692 self._base_attr_map = base_map 

693 self._attr_names = tuple(a.name for a in attrs) 

694 self._slots = slots 

695 self._frozen = frozen 

696 self._weakref_slot = weakref_slot 

697 self._cache_hash = cache_hash 

698 self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False)) 

699 self._pre_init_has_args = False 

700 if self._has_pre_init: 

701 # Check if the pre init method has more arguments than just `self` 

702 # We want to pass arguments if pre init expects arguments 

703 pre_init_func = cls.__attrs_pre_init__ 

704 pre_init_signature = inspect.signature(pre_init_func) 

705 self._pre_init_has_args = len(pre_init_signature.parameters) > 1 

706 self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) 

707 self._delete_attribs = not bool(these) 

708 self._is_exc = is_exc 

709 self._on_setattr = on_setattr 

710 

711 self._has_custom_setattr = has_custom_setattr 

712 self._wrote_own_setattr = False 

713 

714 self._cls_dict["__attrs_attrs__"] = self._attrs 

715 

716 if frozen: 

717 self._cls_dict["__setattr__"] = _frozen_setattrs 

718 self._cls_dict["__delattr__"] = _frozen_delattrs 

719 

720 self._wrote_own_setattr = True 

721 elif on_setattr in ( 

722 _DEFAULT_ON_SETATTR, 

723 setters.validate, 

724 setters.convert, 

725 ): 

726 has_validator = has_converter = False 

727 for a in attrs: 

728 if a.validator is not None: 

729 has_validator = True 

730 if a.converter is not None: 

731 has_converter = True 

732 

733 if has_validator and has_converter: 

734 break 

735 if ( 

736 ( 

737 on_setattr == _DEFAULT_ON_SETATTR 

738 and not (has_validator or has_converter) 

739 ) 

740 or (on_setattr == setters.validate and not has_validator) 

741 or (on_setattr == setters.convert and not has_converter) 

742 ): 

743 # If class-level on_setattr is set to convert + validate, but 

744 # there's no field to convert or validate, pretend like there's 

745 # no on_setattr. 

746 self._on_setattr = None 

747 

748 if getstate_setstate: 

749 ( 

750 self._cls_dict["__getstate__"], 

751 self._cls_dict["__setstate__"], 

752 ) = self._make_getstate_setstate() 

753 

754 # tuples of script, globs, hook 

755 self._script_snippets: list[ 

756 tuple[str, dict, Callable[[dict, dict], Any]] 

757 ] = [] 

758 self._repr_added = False 

759 

760 # We want to only do this check once; in 99.9% of cases these 

761 # exist. 

762 if not hasattr(self._cls, "__module__") or not hasattr( 

763 self._cls, "__qualname__" 

764 ): 

765 self._add_method_dunders = self._add_method_dunders_safe 

766 else: 

767 self._add_method_dunders = self._add_method_dunders_unsafe 

768 

769 def __repr__(self): 

770 return f"<_ClassBuilder(cls={self._cls.__name__})>" 

771 

772 def _eval_snippets(self) -> None: 

773 """ 

774 Evaluate any registered snippets in one go. 

775 """ 

776 script = "\n".join([snippet[0] for snippet in self._script_snippets]) 

777 globs = {} 

778 for _, snippet_globs, _ in self._script_snippets: 

779 globs.update(snippet_globs) 

780 

781 locs = _linecache_and_compile( 

782 script, 

783 _generate_unique_filename(self._cls, "methods"), 

784 globs, 

785 ) 

786 

787 for _, _, hook in self._script_snippets: 

788 hook(self._cls_dict, locs) 

789 

790 def build_class(self): 

791 """ 

792 Finalize class based on the accumulated configuration. 

793 

794 Builder cannot be used after calling this method. 

795 """ 

796 self._eval_snippets() 

797 if self._slots is True: 

798 cls = self._create_slots_class() 

799 else: 

800 cls = self._patch_original_class() 

801 if PY_3_10_PLUS: 

802 cls = abc.update_abstractmethods(cls) 

803 

804 # The method gets only called if it's not inherited from a base class. 

805 # _has_own_attribute does NOT work properly for classmethods. 

806 if ( 

807 getattr(cls, "__attrs_init_subclass__", None) 

808 and "__attrs_init_subclass__" not in cls.__dict__ 

809 ): 

810 cls.__attrs_init_subclass__() 

811 

812 return cls 

813 

814 def _patch_original_class(self): 

815 """ 

816 Apply accumulated methods and return the class. 

817 """ 

818 cls = self._cls 

819 base_names = self._base_names 

820 

821 # Clean class of attribute definitions (`attr.ib()`s). 

822 if self._delete_attribs: 

823 for name in self._attr_names: 

824 if ( 

825 name not in base_names 

826 and getattr(cls, name, _SENTINEL) is not _SENTINEL 

827 ): 

828 # An AttributeError can happen if a base class defines a 

829 # class variable and we want to set an attribute with the 

830 # same name by using only a type annotation. 

831 with contextlib.suppress(AttributeError): 

832 delattr(cls, name) 

833 

834 # Attach our dunder methods. 

835 for name, value in self._cls_dict.items(): 

836 setattr(cls, name, value) 

837 

838 # If we've inherited an attrs __setattr__ and don't write our own, 

839 # reset it to object's. 

840 if not self._wrote_own_setattr and getattr( 

841 cls, "__attrs_own_setattr__", False 

842 ): 

843 cls.__attrs_own_setattr__ = False 

844 

845 if not self._has_custom_setattr: 

846 cls.__setattr__ = _OBJ_SETATTR 

847 

848 return cls 

849 

850 def _create_slots_class(self): 

851 """ 

852 Build and return a new class with a `__slots__` attribute. 

853 """ 

854 cd = { 

855 k: v 

856 for k, v in self._cls_dict.items() 

857 if k not in (*tuple(self._attr_names), "__dict__", "__weakref__") 

858 } 

859 

860 # 3.14.0rc2+ 

861 if hasattr(sys, "_clear_type_descriptors"): 

862 sys._clear_type_descriptors(self._cls) 

863 

864 # If our class doesn't have its own implementation of __setattr__ 

865 # (either from the user or by us), check the bases, if one of them has 

866 # an attrs-made __setattr__, that needs to be reset. We don't walk the 

867 # MRO because we only care about our immediate base classes. 

868 # XXX: This can be confused by subclassing a slotted attrs class with 

869 # XXX: a non-attrs class and subclass the resulting class with an attrs 

870 # XXX: class. See `test_slotted_confused` for details. For now that's 

871 # XXX: OK with us. 

872 if not self._wrote_own_setattr: 

873 cd["__attrs_own_setattr__"] = False 

874 

875 if not self._has_custom_setattr: 

876 for base_cls in self._cls.__bases__: 

877 if base_cls.__dict__.get("__attrs_own_setattr__", False): 

878 cd["__setattr__"] = _OBJ_SETATTR 

879 break 

880 

881 # Traverse the MRO to collect existing slots 

882 # and check for an existing __weakref__. 

883 existing_slots = {} 

884 weakref_inherited = False 

885 for base_cls in self._cls.__mro__[1:-1]: 

886 if base_cls.__dict__.get("__weakref__", None) is not None: 

887 weakref_inherited = True 

888 existing_slots.update( 

889 { 

890 name: getattr(base_cls, name) 

891 for name in getattr(base_cls, "__slots__", []) 

892 } 

893 ) 

894 

895 base_names = set(self._base_names) 

896 

897 names = self._attr_names 

898 if ( 

899 self._weakref_slot 

900 and "__weakref__" not in getattr(self._cls, "__slots__", ()) 

901 and "__weakref__" not in names 

902 and not weakref_inherited 

903 ): 

904 names += ("__weakref__",) 

905 

906 cached_properties = { 

907 name: cached_prop.func 

908 for name, cached_prop in cd.items() 

909 if isinstance(cached_prop, cached_property) 

910 } 

911 

912 # Collect methods with a `__class__` reference that are shadowed in the new class. 

913 # To know to update them. 

914 additional_closure_functions_to_update = [] 

915 if cached_properties: 

916 class_annotations = _get_annotations(self._cls) 

917 for name, func in cached_properties.items(): 

918 # Add cached properties to names for slotting. 

919 names += (name,) 

920 # Clear out function from class to avoid clashing. 

921 del cd[name] 

922 additional_closure_functions_to_update.append(func) 

923 annotation = inspect.signature(func).return_annotation 

924 if annotation is not inspect.Parameter.empty: 

925 class_annotations[name] = annotation 

926 

927 original_getattr = cd.get("__getattr__") 

928 if original_getattr is not None: 

929 additional_closure_functions_to_update.append(original_getattr) 

930 

931 cd["__getattr__"] = _make_cached_property_getattr( 

932 cached_properties, original_getattr, self._cls 

933 ) 

934 

935 # We only add the names of attributes that aren't inherited. 

936 # Setting __slots__ to inherited attributes wastes memory. 

937 slot_names = [name for name in names if name not in base_names] 

938 

939 # There are slots for attributes from current class 

940 # that are defined in parent classes. 

941 # As their descriptors may be overridden by a child class, 

942 # we collect them here and update the class dict 

943 reused_slots = { 

944 slot: slot_descriptor 

945 for slot, slot_descriptor in existing_slots.items() 

946 if slot in slot_names 

947 } 

948 slot_names = [name for name in slot_names if name not in reused_slots] 

949 cd.update(reused_slots) 

950 if self._cache_hash: 

951 slot_names.append(_HASH_CACHE_FIELD) 

952 

953 cd["__slots__"] = tuple(slot_names) 

954 

955 cd["__qualname__"] = self._cls.__qualname__ 

956 

957 # Create new class based on old class and our methods. 

958 cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd) 

959 

960 # The following is a fix for 

961 # <https://github.com/python-attrs/attrs/issues/102>. 

962 # If a method mentions `__class__` or uses the no-arg super(), the 

963 # compiler will bake a reference to the class in the method itself 

964 # as `method.__closure__`. Since we replace the class with a 

965 # clone, we rewrite these references so it keeps working. 

966 for item in itertools.chain( 

967 cls.__dict__.values(), additional_closure_functions_to_update 

968 ): 

969 if isinstance(item, (classmethod, staticmethod)): 

970 # Class- and staticmethods hide their functions inside. 

971 # These might need to be rewritten as well. 

972 closure_cells = getattr(item.__func__, "__closure__", None) 

973 elif isinstance(item, property): 

974 # Workaround for property `super()` shortcut (PY3-only). 

975 # There is no universal way for other descriptors. 

976 closure_cells = getattr(item.fget, "__closure__", None) 

977 else: 

978 closure_cells = getattr(item, "__closure__", None) 

979 

980 if not closure_cells: # Catch None or the empty list. 

981 continue 

982 for cell in closure_cells: 

983 try: 

984 match = cell.cell_contents is self._cls 

985 except ValueError: # noqa: PERF203 

986 # ValueError: Cell is empty 

987 pass 

988 else: 

989 if match: 

990 cell.cell_contents = cls 

991 return cls 

992 

993 def add_repr(self, ns): 

994 script, globs = _make_repr_script(self._attrs, ns) 

995 

996 def _attach_repr(cls_dict, globs): 

997 cls_dict["__repr__"] = self._add_method_dunders(globs["__repr__"]) 

998 

999 self._script_snippets.append((script, globs, _attach_repr)) 

1000 self._repr_added = True 

1001 return self 

1002 

1003 def add_str(self): 

1004 if not self._repr_added: 

1005 msg = "__str__ can only be generated if a __repr__ exists." 

1006 raise ValueError(msg) 

1007 

1008 def __str__(self): 

1009 return self.__repr__() 

1010 

1011 self._cls_dict["__str__"] = self._add_method_dunders(__str__) 

1012 return self 

1013 

1014 def _make_getstate_setstate(self): 

1015 """ 

1016 Create custom __setstate__ and __getstate__ methods. 

1017 """ 

1018 # __weakref__ is not writable. 

1019 state_attr_names = tuple( 

1020 an for an in self._attr_names if an != "__weakref__" 

1021 ) 

1022 

1023 def slots_getstate(self): 

1024 """ 

1025 Automatically created by attrs. 

1026 """ 

1027 return {name: getattr(self, name) for name in state_attr_names} 

1028 

1029 hash_caching_enabled = self._cache_hash 

1030 

1031 def slots_setstate(self, state): 

1032 """ 

1033 Automatically created by attrs. 

1034 """ 

1035 __bound_setattr = _OBJ_SETATTR.__get__(self) 

1036 if isinstance(state, tuple): 

1037 # Backward compatibility with attrs instances pickled with 

1038 # attrs versions before v22.2.0 which stored tuples. 

1039 for name, value in zip(state_attr_names, state): 

1040 __bound_setattr(name, value) 

1041 else: 

1042 for name in state_attr_names: 

1043 if name in state: 

1044 __bound_setattr(name, state[name]) 

1045 

1046 # The hash code cache is not included when the object is 

1047 # serialized, but it still needs to be initialized to None to 

1048 # indicate that the first call to __hash__ should be a cache 

1049 # miss. 

1050 if hash_caching_enabled: 

1051 __bound_setattr(_HASH_CACHE_FIELD, None) 

1052 

1053 return slots_getstate, slots_setstate 

1054 

1055 def make_unhashable(self): 

1056 self._cls_dict["__hash__"] = None 

1057 return self 

1058 

1059 def add_hash(self): 

1060 script, globs = _make_hash_script( 

1061 self._cls, 

1062 self._attrs, 

1063 frozen=self._frozen, 

1064 cache_hash=self._cache_hash, 

1065 ) 

1066 

1067 def attach_hash(cls_dict: dict, locs: dict) -> None: 

1068 cls_dict["__hash__"] = self._add_method_dunders(locs["__hash__"]) 

1069 

1070 self._script_snippets.append((script, globs, attach_hash)) 

1071 

1072 return self 

1073 

1074 def add_init(self): 

1075 script, globs, annotations = _make_init_script( 

1076 self._cls, 

1077 self._attrs, 

1078 self._has_pre_init, 

1079 self._pre_init_has_args, 

1080 self._has_post_init, 

1081 self._frozen, 

1082 self._slots, 

1083 self._cache_hash, 

1084 self._base_attr_map, 

1085 self._is_exc, 

1086 self._on_setattr, 

1087 attrs_init=False, 

1088 ) 

1089 

1090 def _attach_init(cls_dict, globs): 

1091 init = globs["__init__"] 

1092 init.__annotations__ = annotations 

1093 cls_dict["__init__"] = self._add_method_dunders(init) 

1094 

1095 self._script_snippets.append((script, globs, _attach_init)) 

1096 

1097 return self 

1098 

1099 def add_replace(self): 

1100 self._cls_dict["__replace__"] = self._add_method_dunders( 

1101 lambda self, **changes: evolve(self, **changes) 

1102 ) 

1103 return self 

1104 

1105 def add_match_args(self): 

1106 self._cls_dict["__match_args__"] = tuple( 

1107 field.name 

1108 for field in self._attrs 

1109 if field.init and not field.kw_only 

1110 ) 

1111 

1112 def add_attrs_init(self): 

1113 script, globs, annotations = _make_init_script( 

1114 self._cls, 

1115 self._attrs, 

1116 self._has_pre_init, 

1117 self._pre_init_has_args, 

1118 self._has_post_init, 

1119 self._frozen, 

1120 self._slots, 

1121 self._cache_hash, 

1122 self._base_attr_map, 

1123 self._is_exc, 

1124 self._on_setattr, 

1125 attrs_init=True, 

1126 ) 

1127 

1128 def _attach_attrs_init(cls_dict, globs): 

1129 init = globs["__attrs_init__"] 

1130 init.__annotations__ = annotations 

1131 cls_dict["__attrs_init__"] = self._add_method_dunders(init) 

1132 

1133 self._script_snippets.append((script, globs, _attach_attrs_init)) 

1134 

1135 return self 

1136 

1137 def add_eq(self): 

1138 cd = self._cls_dict 

1139 

1140 script, globs = _make_eq_script(self._attrs) 

1141 

1142 def _attach_eq(cls_dict, globs): 

1143 cls_dict["__eq__"] = self._add_method_dunders(globs["__eq__"]) 

1144 

1145 self._script_snippets.append((script, globs, _attach_eq)) 

1146 

1147 cd["__ne__"] = __ne__ 

1148 

1149 return self 

1150 

1151 def add_order(self): 

1152 cd = self._cls_dict 

1153 

1154 cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = ( 

1155 self._add_method_dunders(meth) 

1156 for meth in _make_order(self._cls, self._attrs) 

1157 ) 

1158 

1159 return self 

1160 

1161 def add_setattr(self): 

1162 sa_attrs = {} 

1163 for a in self._attrs: 

1164 on_setattr = a.on_setattr or self._on_setattr 

1165 if on_setattr and on_setattr is not setters.NO_OP: 

1166 sa_attrs[a.name] = a, on_setattr 

1167 

1168 if not sa_attrs: 

1169 return self 

1170 

1171 if self._has_custom_setattr: 

1172 # We need to write a __setattr__ but there already is one! 

1173 msg = "Can't combine custom __setattr__ with on_setattr hooks." 

1174 raise ValueError(msg) 

1175 

1176 # docstring comes from _add_method_dunders 

1177 def __setattr__(self, name, val): 

1178 try: 

1179 a, hook = sa_attrs[name] 

1180 except KeyError: 

1181 nval = val 

1182 else: 

1183 nval = hook(self, a, val) 

1184 

1185 _OBJ_SETATTR(self, name, nval) 

1186 

1187 self._cls_dict["__attrs_own_setattr__"] = True 

1188 self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__) 

1189 self._wrote_own_setattr = True 

1190 

1191 return self 

1192 

1193 def _add_method_dunders_unsafe(self, method: Callable) -> Callable: 

1194 """ 

1195 Add __module__ and __qualname__ to a *method*. 

1196 """ 

1197 method.__module__ = self._cls.__module__ 

1198 

1199 method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}" 

1200 

1201 method.__doc__ = ( 

1202 f"Method generated by attrs for class {self._cls.__qualname__}." 

1203 ) 

1204 

1205 return method 

1206 

1207 def _add_method_dunders_safe(self, method: Callable) -> Callable: 

1208 """ 

1209 Add __module__ and __qualname__ to a *method* if possible. 

1210 """ 

1211 with contextlib.suppress(AttributeError): 

1212 method.__module__ = self._cls.__module__ 

1213 

1214 with contextlib.suppress(AttributeError): 

1215 method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}" 

1216 

1217 with contextlib.suppress(AttributeError): 

1218 method.__doc__ = f"Method generated by attrs for class {self._cls.__qualname__}." 

1219 

1220 return method 

1221 

1222 

1223def _determine_attrs_eq_order(cmp, eq, order, default_eq): 

1224 """ 

1225 Validate the combination of *cmp*, *eq*, and *order*. Derive the effective 

1226 values of eq and order. If *eq* is None, set it to *default_eq*. 

1227 """ 

1228 if cmp is not None and any((eq is not None, order is not None)): 

1229 msg = "Don't mix `cmp` with `eq' and `order`." 

1230 raise ValueError(msg) 

1231 

1232 # cmp takes precedence due to bw-compatibility. 

1233 if cmp is not None: 

1234 return cmp, cmp 

1235 

1236 # If left None, equality is set to the specified default and ordering 

1237 # mirrors equality. 

1238 if eq is None: 

1239 eq = default_eq 

1240 

1241 if order is None: 

1242 order = eq 

1243 

1244 if eq is False and order is True: 

1245 msg = "`order` can only be True if `eq` is True too." 

1246 raise ValueError(msg) 

1247 

1248 return eq, order 

1249 

1250 

1251def _determine_attrib_eq_order(cmp, eq, order, default_eq): 

1252 """ 

1253 Validate the combination of *cmp*, *eq*, and *order*. Derive the effective 

1254 values of eq and order. If *eq* is None, set it to *default_eq*. 

1255 """ 

1256 if cmp is not None and any((eq is not None, order is not None)): 

1257 msg = "Don't mix `cmp` with `eq' and `order`." 

1258 raise ValueError(msg) 

1259 

1260 def decide_callable_or_boolean(value): 

1261 """ 

1262 Decide whether a key function is used. 

1263 """ 

1264 if callable(value): 

1265 value, key = True, value 

1266 else: 

1267 key = None 

1268 return value, key 

1269 

1270 # cmp takes precedence due to bw-compatibility. 

1271 if cmp is not None: 

1272 cmp, cmp_key = decide_callable_or_boolean(cmp) 

1273 return cmp, cmp_key, cmp, cmp_key 

1274 

1275 # If left None, equality is set to the specified default and ordering 

1276 # mirrors equality. 

1277 if eq is None: 

1278 eq, eq_key = default_eq, None 

1279 else: 

1280 eq, eq_key = decide_callable_or_boolean(eq) 

1281 

1282 if order is None: 

1283 order, order_key = eq, eq_key 

1284 else: 

1285 order, order_key = decide_callable_or_boolean(order) 

1286 

1287 if eq is False and order is True: 

1288 msg = "`order` can only be True if `eq` is True too." 

1289 raise ValueError(msg) 

1290 

1291 return eq, eq_key, order, order_key 

1292 

1293 

1294def _determine_whether_to_implement( 

1295 cls, flag, auto_detect, dunders, default=True 

1296): 

1297 """ 

1298 Check whether we should implement a set of methods for *cls*. 

1299 

1300 *flag* is the argument passed into @attr.s like 'init', *auto_detect* the 

1301 same as passed into @attr.s and *dunders* is a tuple of attribute names 

1302 whose presence signal that the user has implemented it themselves. 

1303 

1304 Return *default* if no reason for either for or against is found. 

1305 """ 

1306 if flag is True or flag is False: 

1307 return flag 

1308 

1309 if flag is None and auto_detect is False: 

1310 return default 

1311 

1312 # Logically, flag is None and auto_detect is True here. 

1313 for dunder in dunders: 

1314 if _has_own_attribute(cls, dunder): 

1315 return False 

1316 

1317 return default 

1318 

1319 

1320def attrs( 

1321 maybe_cls=None, 

1322 these=None, 

1323 repr_ns=None, 

1324 repr=None, 

1325 cmp=None, 

1326 hash=None, 

1327 init=None, 

1328 slots=False, 

1329 frozen=False, 

1330 weakref_slot=True, 

1331 str=False, 

1332 auto_attribs=False, 

1333 kw_only=False, 

1334 cache_hash=False, 

1335 auto_exc=False, 

1336 eq=None, 

1337 order=None, 

1338 auto_detect=False, 

1339 collect_by_mro=False, 

1340 getstate_setstate=None, 

1341 on_setattr=None, 

1342 field_transformer=None, 

1343 match_args=True, 

1344 unsafe_hash=None, 

1345 force_kw_only=True, 

1346): 

1347 r""" 

1348 A class decorator that adds :term:`dunder methods` according to the 

1349 specified attributes using `attr.ib` or the *these* argument. 

1350 

1351 Consider using `attrs.define` / `attrs.frozen` in new code (``attr.s`` will 

1352 *never* go away, though). 

1353 

1354 Args: 

1355 repr_ns (str): 

1356 When using nested classes, there was no way in Python 2 to 

1357 automatically detect that. This argument allows to set a custom 

1358 name for a more meaningful ``repr`` output. This argument is 

1359 pointless in Python 3 and is therefore deprecated. 

1360 

1361 .. caution:: 

1362 Refer to `attrs.define` for the rest of the parameters, but note that they 

1363 can have different defaults. 

1364 

1365 Notably, leaving *on_setattr* as `None` will **not** add any hooks. 

1366 

1367 .. versionadded:: 16.0.0 *slots* 

1368 .. versionadded:: 16.1.0 *frozen* 

1369 .. versionadded:: 16.3.0 *str* 

1370 .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``. 

1371 .. versionchanged:: 17.1.0 

1372 *hash* supports `None` as value which is also the default now. 

1373 .. versionadded:: 17.3.0 *auto_attribs* 

1374 .. versionchanged:: 18.1.0 

1375 If *these* is passed, no attributes are deleted from the class body. 

1376 .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained. 

1377 .. versionadded:: 18.2.0 *weakref_slot* 

1378 .. deprecated:: 18.2.0 

1379 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a 

1380 `DeprecationWarning` if the classes compared are subclasses of 

1381 each other. ``__eq`` and ``__ne__`` never tried to compared subclasses 

1382 to each other. 

1383 .. versionchanged:: 19.2.0 

1384 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider 

1385 subclasses comparable anymore. 

1386 .. versionadded:: 18.2.0 *kw_only* 

1387 .. versionadded:: 18.2.0 *cache_hash* 

1388 .. versionadded:: 19.1.0 *auto_exc* 

1389 .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. 

1390 .. versionadded:: 19.2.0 *eq* and *order* 

1391 .. versionadded:: 20.1.0 *auto_detect* 

1392 .. versionadded:: 20.1.0 *collect_by_mro* 

1393 .. versionadded:: 20.1.0 *getstate_setstate* 

1394 .. versionadded:: 20.1.0 *on_setattr* 

1395 .. versionadded:: 20.3.0 *field_transformer* 

1396 .. versionchanged:: 21.1.0 

1397 ``init=False`` injects ``__attrs_init__`` 

1398 .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__`` 

1399 .. versionchanged:: 21.1.0 *cmp* undeprecated 

1400 .. versionadded:: 21.3.0 *match_args* 

1401 .. versionadded:: 22.2.0 

1402 *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). 

1403 .. deprecated:: 24.1.0 *repr_ns* 

1404 .. versionchanged:: 24.1.0 

1405 Instances are not compared as tuples of attributes anymore, but using a 

1406 big ``and`` condition. This is faster and has more correct behavior for 

1407 uncomparable values like `math.nan`. 

1408 .. versionadded:: 24.1.0 

1409 If a class has an *inherited* classmethod called 

1410 ``__attrs_init_subclass__``, it is executed after the class is created. 

1411 .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*. 

1412 .. versionchanged:: 25.4.0 

1413 *kw_only* now only applies to attributes defined in the current class, 

1414 and respects attribute-level ``kw_only=False`` settings. 

1415 .. versionadded:: 25.4.0 *force_kw_only* 

1416 """ 

1417 if repr_ns is not None: 

1418 import warnings 

1419 

1420 warnings.warn( 

1421 DeprecationWarning( 

1422 "The `repr_ns` argument is deprecated and will be removed in or after August 2025." 

1423 ), 

1424 stacklevel=2, 

1425 ) 

1426 

1427 eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None) 

1428 

1429 # unsafe_hash takes precedence due to PEP 681. 

1430 if unsafe_hash is not None: 

1431 hash = unsafe_hash 

1432 

1433 if isinstance(on_setattr, (list, tuple)): 

1434 on_setattr = setters.pipe(*on_setattr) 

1435 

1436 def wrap(cls): 

1437 is_frozen = frozen or _has_frozen_base_class(cls) 

1438 is_exc = auto_exc is True and issubclass(cls, BaseException) 

1439 has_own_setattr = auto_detect and _has_own_attribute( 

1440 cls, "__setattr__" 

1441 ) 

1442 

1443 if has_own_setattr and is_frozen: 

1444 msg = "Can't freeze a class with a custom __setattr__." 

1445 raise ValueError(msg) 

1446 

1447 builder = _ClassBuilder( 

1448 cls, 

1449 these, 

1450 slots, 

1451 is_frozen, 

1452 weakref_slot, 

1453 _determine_whether_to_implement( 

1454 cls, 

1455 getstate_setstate, 

1456 auto_detect, 

1457 ("__getstate__", "__setstate__"), 

1458 default=slots, 

1459 ), 

1460 auto_attribs, 

1461 kw_only, 

1462 force_kw_only, 

1463 cache_hash, 

1464 is_exc, 

1465 collect_by_mro, 

1466 on_setattr, 

1467 has_own_setattr, 

1468 field_transformer, 

1469 ) 

1470 

1471 if _determine_whether_to_implement( 

1472 cls, repr, auto_detect, ("__repr__",) 

1473 ): 

1474 builder.add_repr(repr_ns) 

1475 

1476 if str is True: 

1477 builder.add_str() 

1478 

1479 eq = _determine_whether_to_implement( 

1480 cls, eq_, auto_detect, ("__eq__", "__ne__") 

1481 ) 

1482 if not is_exc and eq is True: 

1483 builder.add_eq() 

1484 if not is_exc and _determine_whether_to_implement( 

1485 cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__") 

1486 ): 

1487 builder.add_order() 

1488 

1489 if not frozen: 

1490 builder.add_setattr() 

1491 

1492 nonlocal hash 

1493 if ( 

1494 hash is None 

1495 and auto_detect is True 

1496 and _has_own_attribute(cls, "__hash__") 

1497 ): 

1498 hash = False 

1499 

1500 if hash is not True and hash is not False and hash is not None: 

1501 # Can't use `hash in` because 1 == True for example. 

1502 msg = "Invalid value for hash. Must be True, False, or None." 

1503 raise TypeError(msg) 

1504 

1505 if hash is False or (hash is None and eq is False) or is_exc: 

1506 # Don't do anything. Should fall back to __object__'s __hash__ 

1507 # which is by id. 

1508 if cache_hash: 

1509 msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled." 

1510 raise TypeError(msg) 

1511 elif hash is True or ( 

1512 hash is None and eq is True and is_frozen is True 

1513 ): 

1514 # Build a __hash__ if told so, or if it's safe. 

1515 builder.add_hash() 

1516 else: 

1517 # Raise TypeError on attempts to hash. 

1518 if cache_hash: 

1519 msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled." 

1520 raise TypeError(msg) 

1521 builder.make_unhashable() 

1522 

1523 if _determine_whether_to_implement( 

1524 cls, init, auto_detect, ("__init__",) 

1525 ): 

1526 builder.add_init() 

1527 else: 

1528 builder.add_attrs_init() 

1529 if cache_hash: 

1530 msg = "Invalid value for cache_hash. To use hash caching, init must be True." 

1531 raise TypeError(msg) 

1532 

1533 if PY_3_13_PLUS and not _has_own_attribute(cls, "__replace__"): 

1534 builder.add_replace() 

1535 

1536 if ( 

1537 PY_3_10_PLUS 

1538 and match_args 

1539 and not _has_own_attribute(cls, "__match_args__") 

1540 ): 

1541 builder.add_match_args() 

1542 

1543 return builder.build_class() 

1544 

1545 # maybe_cls's type depends on the usage of the decorator. It's a class 

1546 # if it's used as `@attrs` but `None` if used as `@attrs()`. 

1547 if maybe_cls is None: 

1548 return wrap 

1549 

1550 return wrap(maybe_cls) 

1551 

1552 

1553_attrs = attrs 

1554""" 

1555Internal alias so we can use it in functions that take an argument called 

1556*attrs*. 

1557""" 

1558 

1559 

1560def _has_frozen_base_class(cls): 

1561 """ 

1562 Check whether *cls* has a frozen ancestor by looking at its 

1563 __setattr__. 

1564 """ 

1565 return cls.__setattr__ is _frozen_setattrs 

1566 

1567 

1568def _generate_unique_filename(cls: type, func_name: str) -> str: 

1569 """ 

1570 Create a "filename" suitable for a function being generated. 

1571 """ 

1572 return ( 

1573 f"<attrs generated {func_name} {cls.__module__}." 

1574 f"{getattr(cls, '__qualname__', cls.__name__)}>" 

1575 ) 

1576 

1577 

1578def _make_hash_script( 

1579 cls: type, attrs: list[Attribute], frozen: bool, cache_hash: bool 

1580) -> tuple[str, dict]: 

1581 attrs = tuple( 

1582 a for a in attrs if a.hash is True or (a.hash is None and a.eq is True) 

1583 ) 

1584 

1585 tab = " " 

1586 

1587 type_hash = hash(_generate_unique_filename(cls, "hash")) 

1588 # If eq is custom generated, we need to include the functions in globs 

1589 globs = {} 

1590 

1591 hash_def = "def __hash__(self" 

1592 hash_func = "hash((" 

1593 closing_braces = "))" 

1594 if not cache_hash: 

1595 hash_def += "):" 

1596 else: 

1597 hash_def += ", *" 

1598 

1599 hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):" 

1600 hash_func = "_cache_wrapper(" + hash_func 

1601 closing_braces += ")" 

1602 

1603 method_lines = [hash_def] 

1604 

1605 def append_hash_computation_lines(prefix, indent): 

1606 """ 

1607 Generate the code for actually computing the hash code. 

1608 Below this will either be returned directly or used to compute 

1609 a value which is then cached, depending on the value of cache_hash 

1610 """ 

1611 

1612 method_lines.extend( 

1613 [ 

1614 indent + prefix + hash_func, 

1615 indent + f" {type_hash},", 

1616 ] 

1617 ) 

1618 

1619 for a in attrs: 

1620 if a.eq_key: 

1621 cmp_name = f"_{a.name}_key" 

1622 globs[cmp_name] = a.eq_key 

1623 method_lines.append( 

1624 indent + f" {cmp_name}(self.{a.name})," 

1625 ) 

1626 else: 

1627 method_lines.append(indent + f" self.{a.name},") 

1628 

1629 method_lines.append(indent + " " + closing_braces) 

1630 

1631 if cache_hash: 

1632 method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:") 

1633 if frozen: 

1634 append_hash_computation_lines( 

1635 f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2 

1636 ) 

1637 method_lines.append(tab * 2 + ")") # close __setattr__ 

1638 else: 

1639 append_hash_computation_lines( 

1640 f"self.{_HASH_CACHE_FIELD} = ", tab * 2 

1641 ) 

1642 method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}") 

1643 else: 

1644 append_hash_computation_lines("return ", tab) 

1645 

1646 script = "\n".join(method_lines) 

1647 return script, globs 

1648 

1649 

1650def _add_hash(cls: type, attrs: list[Attribute]): 

1651 """ 

1652 Add a hash method to *cls*. 

1653 """ 

1654 script, globs = _make_hash_script( 

1655 cls, attrs, frozen=False, cache_hash=False 

1656 ) 

1657 _compile_and_eval( 

1658 script, globs, filename=_generate_unique_filename(cls, "__hash__") 

1659 ) 

1660 cls.__hash__ = globs["__hash__"] 

1661 return cls 

1662 

1663 

1664def __ne__(self, other): 

1665 """ 

1666 Check equality and either forward a NotImplemented or 

1667 return the result negated. 

1668 """ 

1669 result = self.__eq__(other) 

1670 if result is NotImplemented: 

1671 return NotImplemented 

1672 

1673 return not result 

1674 

1675 

1676def _make_eq_script(attrs: list) -> tuple[str, dict]: 

1677 """ 

1678 Create __eq__ method for *cls* with *attrs*. 

1679 """ 

1680 attrs = [a for a in attrs if a.eq] 

1681 

1682 lines = [ 

1683 "def __eq__(self, other):", 

1684 " if other.__class__ is not self.__class__:", 

1685 " return NotImplemented", 

1686 ] 

1687 

1688 globs = {} 

1689 if attrs: 

1690 lines.append(" return (") 

1691 for a in attrs: 

1692 if a.eq_key: 

1693 cmp_name = f"_{a.name}_key" 

1694 # Add the key function to the global namespace 

1695 # of the evaluated function. 

1696 globs[cmp_name] = a.eq_key 

1697 lines.append( 

1698 f" {cmp_name}(self.{a.name}) == {cmp_name}(other.{a.name})" 

1699 ) 

1700 else: 

1701 lines.append(f" self.{a.name} == other.{a.name}") 

1702 if a is not attrs[-1]: 

1703 lines[-1] = f"{lines[-1]} and" 

1704 lines.append(" )") 

1705 else: 

1706 lines.append(" return True") 

1707 

1708 script = "\n".join(lines) 

1709 

1710 return script, globs 

1711 

1712 

1713def _make_order(cls, attrs): 

1714 """ 

1715 Create ordering methods for *cls* with *attrs*. 

1716 """ 

1717 attrs = [a for a in attrs if a.order] 

1718 

1719 def attrs_to_tuple(obj): 

1720 """ 

1721 Save us some typing. 

1722 """ 

1723 return tuple( 

1724 key(value) if key else value 

1725 for value, key in ( 

1726 (getattr(obj, a.name), a.order_key) for a in attrs 

1727 ) 

1728 ) 

1729 

1730 def __lt__(self, other): 

1731 """ 

1732 Automatically created by attrs. 

1733 """ 

1734 if other.__class__ is self.__class__: 

1735 return attrs_to_tuple(self) < attrs_to_tuple(other) 

1736 

1737 return NotImplemented 

1738 

1739 def __le__(self, other): 

1740 """ 

1741 Automatically created by attrs. 

1742 """ 

1743 if other.__class__ is self.__class__: 

1744 return attrs_to_tuple(self) <= attrs_to_tuple(other) 

1745 

1746 return NotImplemented 

1747 

1748 def __gt__(self, other): 

1749 """ 

1750 Automatically created by attrs. 

1751 """ 

1752 if other.__class__ is self.__class__: 

1753 return attrs_to_tuple(self) > attrs_to_tuple(other) 

1754 

1755 return NotImplemented 

1756 

1757 def __ge__(self, other): 

1758 """ 

1759 Automatically created by attrs. 

1760 """ 

1761 if other.__class__ is self.__class__: 

1762 return attrs_to_tuple(self) >= attrs_to_tuple(other) 

1763 

1764 return NotImplemented 

1765 

1766 return __lt__, __le__, __gt__, __ge__ 

1767 

1768 

1769def _add_eq(cls, attrs=None): 

1770 """ 

1771 Add equality methods to *cls* with *attrs*. 

1772 """ 

1773 if attrs is None: 

1774 attrs = cls.__attrs_attrs__ 

1775 

1776 script, globs = _make_eq_script(attrs) 

1777 _compile_and_eval( 

1778 script, globs, filename=_generate_unique_filename(cls, "__eq__") 

1779 ) 

1780 cls.__eq__ = globs["__eq__"] 

1781 cls.__ne__ = __ne__ 

1782 

1783 return cls 

1784 

1785 

1786def _make_repr_script(attrs, ns) -> tuple[str, dict]: 

1787 """ 

1788 Create the source and globs for a __repr__ and return it. 

1789 """ 

1790 # Figure out which attributes to include, and which function to use to 

1791 # format them. The a.repr value can be either bool or a custom 

1792 # callable. 

1793 attr_names_with_reprs = tuple( 

1794 (a.name, (repr if a.repr is True else a.repr), a.init) 

1795 for a in attrs 

1796 if a.repr is not False 

1797 ) 

1798 globs = { 

1799 name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr 

1800 } 

1801 globs["_compat"] = _compat 

1802 globs["AttributeError"] = AttributeError 

1803 globs["NOTHING"] = NOTHING 

1804 attribute_fragments = [] 

1805 for name, r, i in attr_names_with_reprs: 

1806 accessor = ( 

1807 "self." + name if i else 'getattr(self, "' + name + '", NOTHING)' 

1808 ) 

1809 fragment = ( 

1810 "%s={%s!r}" % (name, accessor) 

1811 if r == repr 

1812 else "%s={%s_repr(%s)}" % (name, name, accessor) 

1813 ) 

1814 attribute_fragments.append(fragment) 

1815 repr_fragment = ", ".join(attribute_fragments) 

1816 

1817 if ns is None: 

1818 cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}' 

1819 else: 

1820 cls_name_fragment = ns + ".{self.__class__.__name__}" 

1821 

1822 lines = [ 

1823 "def __repr__(self):", 

1824 " try:", 

1825 " already_repring = _compat.repr_context.already_repring", 

1826 " except AttributeError:", 

1827 " already_repring = {id(self),}", 

1828 " _compat.repr_context.already_repring = already_repring", 

1829 " else:", 

1830 " if id(self) in already_repring:", 

1831 " return '...'", 

1832 " else:", 

1833 " already_repring.add(id(self))", 

1834 " try:", 

1835 f" return f'{cls_name_fragment}({repr_fragment})'", 

1836 " finally:", 

1837 " already_repring.remove(id(self))", 

1838 ] 

1839 

1840 return "\n".join(lines), globs 

1841 

1842 

1843def _add_repr(cls, ns=None, attrs=None): 

1844 """ 

1845 Add a repr method to *cls*. 

1846 """ 

1847 if attrs is None: 

1848 attrs = cls.__attrs_attrs__ 

1849 

1850 script, globs = _make_repr_script(attrs, ns) 

1851 _compile_and_eval( 

1852 script, globs, filename=_generate_unique_filename(cls, "__repr__") 

1853 ) 

1854 cls.__repr__ = globs["__repr__"] 

1855 return cls 

1856 

1857 

1858def fields(cls): 

1859 """ 

1860 Return the tuple of *attrs* attributes for a class. 

1861 

1862 The tuple also allows accessing the fields by their names (see below for 

1863 examples). 

1864 

1865 Args: 

1866 cls (type): Class to introspect. 

1867 

1868 Raises: 

1869 TypeError: If *cls* is not a class. 

1870 

1871 attrs.exceptions.NotAnAttrsClassError: 

1872 If *cls* is not an *attrs* class. 

1873 

1874 Returns: 

1875 tuple (with name accessors) of `attrs.Attribute` 

1876 

1877 .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields 

1878 by name. 

1879 .. versionchanged:: 23.1.0 Add support for generic classes. 

1880 """ 

1881 generic_base = get_generic_base(cls) 

1882 

1883 if generic_base is None and not isinstance(cls, type): 

1884 msg = "Passed object must be a class." 

1885 raise TypeError(msg) 

1886 

1887 attrs = getattr(cls, "__attrs_attrs__", None) 

1888 

1889 if attrs is None: 

1890 if generic_base is not None: 

1891 attrs = getattr(generic_base, "__attrs_attrs__", None) 

1892 if attrs is not None: 

1893 # Even though this is global state, stick it on here to speed 

1894 # it up. We rely on `cls` being cached for this to be 

1895 # efficient. 

1896 cls.__attrs_attrs__ = attrs 

1897 return attrs 

1898 msg = f"{cls!r} is not an attrs-decorated class." 

1899 raise NotAnAttrsClassError(msg) 

1900 

1901 return attrs 

1902 

1903 

1904def fields_dict(cls): 

1905 """ 

1906 Return an ordered dictionary of *attrs* attributes for a class, whose keys 

1907 are the attribute names. 

1908 

1909 Args: 

1910 cls (type): Class to introspect. 

1911 

1912 Raises: 

1913 TypeError: If *cls* is not a class. 

1914 

1915 attrs.exceptions.NotAnAttrsClassError: 

1916 If *cls* is not an *attrs* class. 

1917 

1918 Returns: 

1919 dict[str, attrs.Attribute]: Dict of attribute name to definition 

1920 

1921 .. versionadded:: 18.1.0 

1922 """ 

1923 if not isinstance(cls, type): 

1924 msg = "Passed object must be a class." 

1925 raise TypeError(msg) 

1926 attrs = getattr(cls, "__attrs_attrs__", None) 

1927 if attrs is None: 

1928 msg = f"{cls!r} is not an attrs-decorated class." 

1929 raise NotAnAttrsClassError(msg) 

1930 return {a.name: a for a in attrs} 

1931 

1932 

1933def validate(inst): 

1934 """ 

1935 Validate all attributes on *inst* that have a validator. 

1936 

1937 Leaves all exceptions through. 

1938 

1939 Args: 

1940 inst: Instance of a class with *attrs* attributes. 

1941 """ 

1942 if _config._run_validators is False: 

1943 return 

1944 

1945 for a in fields(inst.__class__): 

1946 v = a.validator 

1947 if v is not None: 

1948 v(inst, a, getattr(inst, a.name)) 

1949 

1950 

1951def _is_slot_attr(a_name, base_attr_map): 

1952 """ 

1953 Check if the attribute name comes from a slot class. 

1954 """ 

1955 cls = base_attr_map.get(a_name) 

1956 return cls and "__slots__" in cls.__dict__ 

1957 

1958 

1959def _make_init_script( 

1960 cls, 

1961 attrs, 

1962 pre_init, 

1963 pre_init_has_args, 

1964 post_init, 

1965 frozen, 

1966 slots, 

1967 cache_hash, 

1968 base_attr_map, 

1969 is_exc, 

1970 cls_on_setattr, 

1971 attrs_init, 

1972) -> tuple[str, dict, dict]: 

1973 has_cls_on_setattr = ( 

1974 cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP 

1975 ) 

1976 

1977 if frozen and has_cls_on_setattr: 

1978 msg = "Frozen classes can't use on_setattr." 

1979 raise ValueError(msg) 

1980 

1981 needs_cached_setattr = cache_hash or frozen 

1982 filtered_attrs = [] 

1983 attr_dict = {} 

1984 for a in attrs: 

1985 if not a.init and a.default is NOTHING: 

1986 continue 

1987 

1988 filtered_attrs.append(a) 

1989 attr_dict[a.name] = a 

1990 

1991 if a.on_setattr is not None: 

1992 if frozen is True: 

1993 msg = "Frozen classes can't use on_setattr." 

1994 raise ValueError(msg) 

1995 

1996 needs_cached_setattr = True 

1997 elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP: 

1998 needs_cached_setattr = True 

1999 

2000 script, globs, annotations = _attrs_to_init_script( 

2001 filtered_attrs, 

2002 frozen, 

2003 slots, 

2004 pre_init, 

2005 pre_init_has_args, 

2006 post_init, 

2007 cache_hash, 

2008 base_attr_map, 

2009 is_exc, 

2010 needs_cached_setattr, 

2011 has_cls_on_setattr, 

2012 "__attrs_init__" if attrs_init else "__init__", 

2013 ) 

2014 if cls.__module__ in sys.modules: 

2015 # This makes typing.get_type_hints(CLS.__init__) resolve string types. 

2016 globs.update(sys.modules[cls.__module__].__dict__) 

2017 

2018 globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict}) 

2019 

2020 if needs_cached_setattr: 

2021 # Save the lookup overhead in __init__ if we need to circumvent 

2022 # setattr hooks. 

2023 globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__ 

2024 

2025 return script, globs, annotations 

2026 

2027 

2028def _setattr(attr_name: str, value_var: str, has_on_setattr: bool) -> str: 

2029 """ 

2030 Use the cached object.setattr to set *attr_name* to *value_var*. 

2031 """ 

2032 return f"_setattr('{attr_name}', {value_var})" 

2033 

2034 

2035def _setattr_with_converter( 

2036 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter 

2037) -> str: 

2038 """ 

2039 Use the cached object.setattr to set *attr_name* to *value_var*, but run 

2040 its converter first. 

2041 """ 

2042 return f"_setattr('{attr_name}', {converter._fmt_converter_call(attr_name, value_var)})" 

2043 

2044 

2045def _assign(attr_name: str, value: str, has_on_setattr: bool) -> str: 

2046 """ 

2047 Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise 

2048 relegate to _setattr. 

2049 """ 

2050 if has_on_setattr: 

2051 return _setattr(attr_name, value, True) 

2052 

2053 return f"self.{attr_name} = {value}" 

2054 

2055 

2056def _assign_with_converter( 

2057 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter 

2058) -> str: 

2059 """ 

2060 Unless *attr_name* has an on_setattr hook, use normal assignment after 

2061 conversion. Otherwise relegate to _setattr_with_converter. 

2062 """ 

2063 if has_on_setattr: 

2064 return _setattr_with_converter(attr_name, value_var, True, converter) 

2065 

2066 return f"self.{attr_name} = {converter._fmt_converter_call(attr_name, value_var)}" 

2067 

2068 

2069def _determine_setters( 

2070 frozen: bool, slots: bool, base_attr_map: dict[str, type] 

2071): 

2072 """ 

2073 Determine the correct setter functions based on whether a class is frozen 

2074 and/or slotted. 

2075 """ 

2076 if frozen is True: 

2077 if slots is True: 

2078 return (), _setattr, _setattr_with_converter 

2079 

2080 # Dict frozen classes assign directly to __dict__. 

2081 # But only if the attribute doesn't come from an ancestor slot 

2082 # class. 

2083 # Note _inst_dict will be used again below if cache_hash is True 

2084 

2085 def fmt_setter( 

2086 attr_name: str, value_var: str, has_on_setattr: bool 

2087 ) -> str: 

2088 if _is_slot_attr(attr_name, base_attr_map): 

2089 return _setattr(attr_name, value_var, has_on_setattr) 

2090 

2091 return f"_inst_dict['{attr_name}'] = {value_var}" 

2092 

2093 def fmt_setter_with_converter( 

2094 attr_name: str, 

2095 value_var: str, 

2096 has_on_setattr: bool, 

2097 converter: Converter, 

2098 ) -> str: 

2099 if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): 

2100 return _setattr_with_converter( 

2101 attr_name, value_var, has_on_setattr, converter 

2102 ) 

2103 

2104 return f"_inst_dict['{attr_name}'] = {converter._fmt_converter_call(attr_name, value_var)}" 

2105 

2106 return ( 

2107 ("_inst_dict = self.__dict__",), 

2108 fmt_setter, 

2109 fmt_setter_with_converter, 

2110 ) 

2111 

2112 # Not frozen -- we can just assign directly. 

2113 return (), _assign, _assign_with_converter 

2114 

2115 

2116def _attrs_to_init_script( 

2117 attrs: list[Attribute], 

2118 is_frozen: bool, 

2119 is_slotted: bool, 

2120 call_pre_init: bool, 

2121 pre_init_has_args: bool, 

2122 call_post_init: bool, 

2123 does_cache_hash: bool, 

2124 base_attr_map: dict[str, type], 

2125 is_exc: bool, 

2126 needs_cached_setattr: bool, 

2127 has_cls_on_setattr: bool, 

2128 method_name: str, 

2129) -> tuple[str, dict, dict]: 

2130 """ 

2131 Return a script of an initializer for *attrs*, a dict of globals, and 

2132 annotations for the initializer. 

2133 

2134 The globals are required by the generated script. 

2135 """ 

2136 lines = ["self.__attrs_pre_init__()"] if call_pre_init else [] 

2137 

2138 if needs_cached_setattr: 

2139 lines.append( 

2140 # Circumvent the __setattr__ descriptor to save one lookup per 

2141 # assignment. Note _setattr will be used again below if 

2142 # does_cache_hash is True. 

2143 "_setattr = _cached_setattr_get(self)" 

2144 ) 

2145 

2146 extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters( 

2147 is_frozen, is_slotted, base_attr_map 

2148 ) 

2149 lines.extend(extra_lines) 

2150 

2151 args = [] # Parameters in the definition of __init__ 

2152 pre_init_args = [] # Parameters in the call to __attrs_pre_init__ 

2153 kw_only_args = [] # Used for both 'args' and 'pre_init_args' above 

2154 attrs_to_validate = [] 

2155 

2156 # This is a dictionary of names to validator and converter callables. 

2157 # Injecting this into __init__ globals lets us avoid lookups. 

2158 names_for_globals = {} 

2159 annotations = {"return": None} 

2160 

2161 for a in attrs: 

2162 if a.validator: 

2163 attrs_to_validate.append(a) 

2164 

2165 attr_name = a.name 

2166 has_on_setattr = a.on_setattr is not None or ( 

2167 a.on_setattr is not setters.NO_OP and has_cls_on_setattr 

2168 ) 

2169 # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not 

2170 # explicitly provided 

2171 arg_name = a.alias 

2172 

2173 has_factory = isinstance(a.default, Factory) 

2174 maybe_self = "self" if has_factory and a.default.takes_self else "" 

2175 

2176 if a.converter is not None and not isinstance(a.converter, Converter): 

2177 converter = Converter(a.converter) 

2178 else: 

2179 converter = a.converter 

2180 

2181 if a.init is False: 

2182 if has_factory: 

2183 init_factory_name = _INIT_FACTORY_PAT % (a.name,) 

2184 if converter is not None: 

2185 lines.append( 

2186 fmt_setter_with_converter( 

2187 attr_name, 

2188 init_factory_name + f"({maybe_self})", 

2189 has_on_setattr, 

2190 converter, 

2191 ) 

2192 ) 

2193 names_for_globals[converter._get_global_name(a.name)] = ( 

2194 converter.converter 

2195 ) 

2196 else: 

2197 lines.append( 

2198 fmt_setter( 

2199 attr_name, 

2200 init_factory_name + f"({maybe_self})", 

2201 has_on_setattr, 

2202 ) 

2203 ) 

2204 names_for_globals[init_factory_name] = a.default.factory 

2205 elif converter is not None: 

2206 lines.append( 

2207 fmt_setter_with_converter( 

2208 attr_name, 

2209 f"attr_dict['{attr_name}'].default", 

2210 has_on_setattr, 

2211 converter, 

2212 ) 

2213 ) 

2214 names_for_globals[converter._get_global_name(a.name)] = ( 

2215 converter.converter 

2216 ) 

2217 else: 

2218 lines.append( 

2219 fmt_setter( 

2220 attr_name, 

2221 f"attr_dict['{attr_name}'].default", 

2222 has_on_setattr, 

2223 ) 

2224 ) 

2225 elif a.default is not NOTHING and not has_factory: 

2226 arg = f"{arg_name}=attr_dict['{attr_name}'].default" 

2227 if a.kw_only: 

2228 kw_only_args.append(arg) 

2229 else: 

2230 args.append(arg) 

2231 pre_init_args.append(arg_name) 

2232 

2233 if converter is not None: 

2234 lines.append( 

2235 fmt_setter_with_converter( 

2236 attr_name, arg_name, has_on_setattr, converter 

2237 ) 

2238 ) 

2239 names_for_globals[converter._get_global_name(a.name)] = ( 

2240 converter.converter 

2241 ) 

2242 else: 

2243 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) 

2244 

2245 elif has_factory: 

2246 arg = f"{arg_name}=NOTHING" 

2247 if a.kw_only: 

2248 kw_only_args.append(arg) 

2249 else: 

2250 args.append(arg) 

2251 pre_init_args.append(arg_name) 

2252 lines.append(f"if {arg_name} is not NOTHING:") 

2253 

2254 init_factory_name = _INIT_FACTORY_PAT % (a.name,) 

2255 if converter is not None: 

2256 lines.append( 

2257 " " 

2258 + fmt_setter_with_converter( 

2259 attr_name, arg_name, has_on_setattr, converter 

2260 ) 

2261 ) 

2262 lines.append("else:") 

2263 lines.append( 

2264 " " 

2265 + fmt_setter_with_converter( 

2266 attr_name, 

2267 init_factory_name + "(" + maybe_self + ")", 

2268 has_on_setattr, 

2269 converter, 

2270 ) 

2271 ) 

2272 names_for_globals[converter._get_global_name(a.name)] = ( 

2273 converter.converter 

2274 ) 

2275 else: 

2276 lines.append( 

2277 " " + fmt_setter(attr_name, arg_name, has_on_setattr) 

2278 ) 

2279 lines.append("else:") 

2280 lines.append( 

2281 " " 

2282 + fmt_setter( 

2283 attr_name, 

2284 init_factory_name + "(" + maybe_self + ")", 

2285 has_on_setattr, 

2286 ) 

2287 ) 

2288 names_for_globals[init_factory_name] = a.default.factory 

2289 else: 

2290 if a.kw_only: 

2291 kw_only_args.append(arg_name) 

2292 else: 

2293 args.append(arg_name) 

2294 pre_init_args.append(arg_name) 

2295 

2296 if converter is not None: 

2297 lines.append( 

2298 fmt_setter_with_converter( 

2299 attr_name, arg_name, has_on_setattr, converter 

2300 ) 

2301 ) 

2302 names_for_globals[converter._get_global_name(a.name)] = ( 

2303 converter.converter 

2304 ) 

2305 else: 

2306 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) 

2307 

2308 if a.init is True: 

2309 if a.type is not None and converter is None: 

2310 annotations[arg_name] = a.type 

2311 elif converter is not None and converter._first_param_type: 

2312 # Use the type from the converter if present. 

2313 annotations[arg_name] = converter._first_param_type 

2314 

2315 if attrs_to_validate: # we can skip this if there are no validators. 

2316 names_for_globals["_config"] = _config 

2317 lines.append("if _config._run_validators is True:") 

2318 for a in attrs_to_validate: 

2319 val_name = "__attr_validator_" + a.name 

2320 attr_name = "__attr_" + a.name 

2321 lines.append(f" {val_name}(self, {attr_name}, self.{a.name})") 

2322 names_for_globals[val_name] = a.validator 

2323 names_for_globals[attr_name] = a 

2324 

2325 if call_post_init: 

2326 lines.append("self.__attrs_post_init__()") 

2327 

2328 # Because this is set only after __attrs_post_init__ is called, a crash 

2329 # will result if post-init tries to access the hash code. This seemed 

2330 # preferable to setting this beforehand, in which case alteration to field 

2331 # values during post-init combined with post-init accessing the hash code 

2332 # would result in silent bugs. 

2333 if does_cache_hash: 

2334 if is_frozen: 

2335 if is_slotted: 

2336 init_hash_cache = f"_setattr('{_HASH_CACHE_FIELD}', None)" 

2337 else: 

2338 init_hash_cache = f"_inst_dict['{_HASH_CACHE_FIELD}'] = None" 

2339 else: 

2340 init_hash_cache = f"self.{_HASH_CACHE_FIELD} = None" 

2341 lines.append(init_hash_cache) 

2342 

2343 # For exceptions we rely on BaseException.__init__ for proper 

2344 # initialization. 

2345 if is_exc: 

2346 vals = ",".join(f"self.{a.name}" for a in attrs if a.init) 

2347 

2348 lines.append(f"BaseException.__init__(self, {vals})") 

2349 

2350 args = ", ".join(args) 

2351 pre_init_args = ", ".join(pre_init_args) 

2352 if kw_only_args: 

2353 # leading comma & kw_only args 

2354 args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}" 

2355 pre_init_kw_only_args = ", ".join( 

2356 [ 

2357 f"{kw_arg_name}={kw_arg_name}" 

2358 # We need to remove the defaults from the kw_only_args. 

2359 for kw_arg_name in (kwa.split("=")[0] for kwa in kw_only_args) 

2360 ] 

2361 ) 

2362 pre_init_args += ", " if pre_init_args else "" 

2363 pre_init_args += pre_init_kw_only_args 

2364 

2365 if call_pre_init and pre_init_has_args: 

2366 # If pre init method has arguments, pass the values given to __init__. 

2367 lines[0] = f"self.__attrs_pre_init__({pre_init_args})" 

2368 

2369 # Python <3.12 doesn't allow backslashes in f-strings. 

2370 NL = "\n " 

2371 return ( 

2372 f"""def {method_name}(self, {args}): 

2373 {NL.join(lines) if lines else "pass"} 

2374""", 

2375 names_for_globals, 

2376 annotations, 

2377 ) 

2378 

2379 

2380def _default_init_alias_for(name: str) -> str: 

2381 """ 

2382 The default __init__ parameter name for a field. 

2383 

2384 This performs private-name adjustment via leading-unscore stripping, 

2385 and is the default value of Attribute.alias if not provided. 

2386 """ 

2387 

2388 return name.lstrip("_") 

2389 

2390 

2391class Attribute: 

2392 """ 

2393 *Read-only* representation of an attribute. 

2394 

2395 .. warning:: 

2396 

2397 You should never instantiate this class yourself. 

2398 

2399 The class has *all* arguments of `attr.ib` (except for ``factory`` which is 

2400 only syntactic sugar for ``default=Factory(...)`` plus the following: 

2401 

2402 - ``name`` (`str`): The name of the attribute. 

2403 - ``alias`` (`str`): The __init__ parameter name of the attribute, after 

2404 any explicit overrides and default private-attribute-name handling. 

2405 - ``inherited`` (`bool`): Whether or not that attribute has been inherited 

2406 from a base class. 

2407 - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The 

2408 callables that are used for comparing and ordering objects by this 

2409 attribute, respectively. These are set by passing a callable to 

2410 `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also 

2411 :ref:`comparison customization <custom-comparison>`. 

2412 

2413 Instances of this class are frequently used for introspection purposes 

2414 like: 

2415 

2416 - `fields` returns a tuple of them. 

2417 - Validators get them passed as the first argument. 

2418 - The :ref:`field transformer <transform-fields>` hook receives a list of 

2419 them. 

2420 - The ``alias`` property exposes the __init__ parameter name of the field, 

2421 with any overrides and default private-attribute handling applied. 

2422 

2423 

2424 .. versionadded:: 20.1.0 *inherited* 

2425 .. versionadded:: 20.1.0 *on_setattr* 

2426 .. versionchanged:: 20.2.0 *inherited* is not taken into account for 

2427 equality checks and hashing anymore. 

2428 .. versionadded:: 21.1.0 *eq_key* and *order_key* 

2429 .. versionadded:: 22.2.0 *alias* 

2430 

2431 For the full version history of the fields, see `attr.ib`. 

2432 """ 

2433 

2434 # These slots must NOT be reordered because we use them later for 

2435 # instantiation. 

2436 __slots__ = ( # noqa: RUF023 

2437 "name", 

2438 "default", 

2439 "validator", 

2440 "repr", 

2441 "eq", 

2442 "eq_key", 

2443 "order", 

2444 "order_key", 

2445 "hash", 

2446 "init", 

2447 "metadata", 

2448 "type", 

2449 "converter", 

2450 "kw_only", 

2451 "inherited", 

2452 "on_setattr", 

2453 "alias", 

2454 ) 

2455 

2456 def __init__( 

2457 self, 

2458 name, 

2459 default, 

2460 validator, 

2461 repr, 

2462 cmp, # XXX: unused, remove along with other cmp code. 

2463 hash, 

2464 init, 

2465 inherited, 

2466 metadata=None, 

2467 type=None, 

2468 converter=None, 

2469 kw_only=False, 

2470 eq=None, 

2471 eq_key=None, 

2472 order=None, 

2473 order_key=None, 

2474 on_setattr=None, 

2475 alias=None, 

2476 ): 

2477 eq, eq_key, order, order_key = _determine_attrib_eq_order( 

2478 cmp, eq_key or eq, order_key or order, True 

2479 ) 

2480 

2481 # Cache this descriptor here to speed things up later. 

2482 bound_setattr = _OBJ_SETATTR.__get__(self) 

2483 

2484 # Despite the big red warning, people *do* instantiate `Attribute` 

2485 # themselves. 

2486 bound_setattr("name", name) 

2487 bound_setattr("default", default) 

2488 bound_setattr("validator", validator) 

2489 bound_setattr("repr", repr) 

2490 bound_setattr("eq", eq) 

2491 bound_setattr("eq_key", eq_key) 

2492 bound_setattr("order", order) 

2493 bound_setattr("order_key", order_key) 

2494 bound_setattr("hash", hash) 

2495 bound_setattr("init", init) 

2496 bound_setattr("converter", converter) 

2497 bound_setattr( 

2498 "metadata", 

2499 ( 

2500 types.MappingProxyType(dict(metadata)) # Shallow copy 

2501 if metadata 

2502 else _EMPTY_METADATA_SINGLETON 

2503 ), 

2504 ) 

2505 bound_setattr("type", type) 

2506 bound_setattr("kw_only", kw_only) 

2507 bound_setattr("inherited", inherited) 

2508 bound_setattr("on_setattr", on_setattr) 

2509 bound_setattr("alias", alias) 

2510 

2511 def __setattr__(self, name, value): 

2512 raise FrozenInstanceError 

2513 

2514 @classmethod 

2515 def from_counting_attr( 

2516 cls, name: str, ca: _CountingAttr, kw_only: bool, type=None 

2517 ): 

2518 # The 'kw_only' argument is the class-level setting, and is used if the 

2519 # attribute itself does not explicitly set 'kw_only'. 

2520 # type holds the annotated value. deal with conflicts: 

2521 if type is None: 

2522 type = ca.type 

2523 elif ca.type is not None: 

2524 msg = f"Type annotation and type argument cannot both be present for '{name}'." 

2525 raise ValueError(msg) 

2526 return cls( 

2527 name, 

2528 ca._default, 

2529 ca._validator, 

2530 ca.repr, 

2531 None, 

2532 ca.hash, 

2533 ca.init, 

2534 False, 

2535 ca.metadata, 

2536 type, 

2537 ca.converter, 

2538 kw_only if ca.kw_only is None else ca.kw_only, 

2539 ca.eq, 

2540 ca.eq_key, 

2541 ca.order, 

2542 ca.order_key, 

2543 ca.on_setattr, 

2544 ca.alias, 

2545 ) 

2546 

2547 # Don't use attrs.evolve since fields(Attribute) doesn't work 

2548 def evolve(self, **changes): 

2549 """ 

2550 Copy *self* and apply *changes*. 

2551 

2552 This works similarly to `attrs.evolve` but that function does not work 

2553 with :class:`attrs.Attribute`. 

2554 

2555 It is mainly meant to be used for `transform-fields`. 

2556 

2557 .. versionadded:: 20.3.0 

2558 """ 

2559 new = copy.copy(self) 

2560 

2561 new._setattrs(changes.items()) 

2562 

2563 return new 

2564 

2565 # Don't use _add_pickle since fields(Attribute) doesn't work 

2566 def __getstate__(self): 

2567 """ 

2568 Play nice with pickle. 

2569 """ 

2570 return tuple( 

2571 getattr(self, name) if name != "metadata" else dict(self.metadata) 

2572 for name in self.__slots__ 

2573 ) 

2574 

2575 def __setstate__(self, state): 

2576 """ 

2577 Play nice with pickle. 

2578 """ 

2579 self._setattrs(zip(self.__slots__, state)) 

2580 

2581 def _setattrs(self, name_values_pairs): 

2582 bound_setattr = _OBJ_SETATTR.__get__(self) 

2583 for name, value in name_values_pairs: 

2584 if name != "metadata": 

2585 bound_setattr(name, value) 

2586 else: 

2587 bound_setattr( 

2588 name, 

2589 ( 

2590 types.MappingProxyType(dict(value)) 

2591 if value 

2592 else _EMPTY_METADATA_SINGLETON 

2593 ), 

2594 ) 

2595 

2596 

2597_a = [ 

2598 Attribute( 

2599 name=name, 

2600 default=NOTHING, 

2601 validator=None, 

2602 repr=True, 

2603 cmp=None, 

2604 eq=True, 

2605 order=False, 

2606 hash=(name != "metadata"), 

2607 init=True, 

2608 inherited=False, 

2609 alias=_default_init_alias_for(name), 

2610 ) 

2611 for name in Attribute.__slots__ 

2612] 

2613 

2614Attribute = _add_hash( 

2615 _add_eq( 

2616 _add_repr(Attribute, attrs=_a), 

2617 attrs=[a for a in _a if a.name != "inherited"], 

2618 ), 

2619 attrs=[a for a in _a if a.hash and a.name != "inherited"], 

2620) 

2621 

2622 

2623class _CountingAttr: 

2624 """ 

2625 Intermediate representation of attributes that uses a counter to preserve 

2626 the order in which the attributes have been defined. 

2627 

2628 *Internal* data structure of the attrs library. Running into is most 

2629 likely the result of a bug like a forgotten `@attr.s` decorator. 

2630 """ 

2631 

2632 __slots__ = ( 

2633 "_default", 

2634 "_validator", 

2635 "alias", 

2636 "converter", 

2637 "counter", 

2638 "eq", 

2639 "eq_key", 

2640 "hash", 

2641 "init", 

2642 "kw_only", 

2643 "metadata", 

2644 "on_setattr", 

2645 "order", 

2646 "order_key", 

2647 "repr", 

2648 "type", 

2649 ) 

2650 __attrs_attrs__ = ( 

2651 *tuple( 

2652 Attribute( 

2653 name=name, 

2654 alias=_default_init_alias_for(name), 

2655 default=NOTHING, 

2656 validator=None, 

2657 repr=True, 

2658 cmp=None, 

2659 hash=True, 

2660 init=True, 

2661 kw_only=False, 

2662 eq=True, 

2663 eq_key=None, 

2664 order=False, 

2665 order_key=None, 

2666 inherited=False, 

2667 on_setattr=None, 

2668 ) 

2669 for name in ( 

2670 "counter", 

2671 "_default", 

2672 "repr", 

2673 "eq", 

2674 "order", 

2675 "hash", 

2676 "init", 

2677 "on_setattr", 

2678 "alias", 

2679 ) 

2680 ), 

2681 Attribute( 

2682 name="metadata", 

2683 alias="metadata", 

2684 default=None, 

2685 validator=None, 

2686 repr=True, 

2687 cmp=None, 

2688 hash=False, 

2689 init=True, 

2690 kw_only=False, 

2691 eq=True, 

2692 eq_key=None, 

2693 order=False, 

2694 order_key=None, 

2695 inherited=False, 

2696 on_setattr=None, 

2697 ), 

2698 ) 

2699 cls_counter = 0 

2700 

2701 def __init__( 

2702 self, 

2703 default, 

2704 validator, 

2705 repr, 

2706 cmp, 

2707 hash, 

2708 init, 

2709 converter, 

2710 metadata, 

2711 type, 

2712 kw_only, 

2713 eq, 

2714 eq_key, 

2715 order, 

2716 order_key, 

2717 on_setattr, 

2718 alias, 

2719 ): 

2720 _CountingAttr.cls_counter += 1 

2721 self.counter = _CountingAttr.cls_counter 

2722 self._default = default 

2723 self._validator = validator 

2724 self.converter = converter 

2725 self.repr = repr 

2726 self.eq = eq 

2727 self.eq_key = eq_key 

2728 self.order = order 

2729 self.order_key = order_key 

2730 self.hash = hash 

2731 self.init = init 

2732 self.metadata = metadata 

2733 self.type = type 

2734 self.kw_only = kw_only 

2735 self.on_setattr = on_setattr 

2736 self.alias = alias 

2737 

2738 def validator(self, meth): 

2739 """ 

2740 Decorator that adds *meth* to the list of validators. 

2741 

2742 Returns *meth* unchanged. 

2743 

2744 .. versionadded:: 17.1.0 

2745 """ 

2746 if self._validator is None: 

2747 self._validator = meth 

2748 else: 

2749 self._validator = and_(self._validator, meth) 

2750 return meth 

2751 

2752 def default(self, meth): 

2753 """ 

2754 Decorator that allows to set the default for an attribute. 

2755 

2756 Returns *meth* unchanged. 

2757 

2758 Raises: 

2759 DefaultAlreadySetError: If default has been set before. 

2760 

2761 .. versionadded:: 17.1.0 

2762 """ 

2763 if self._default is not NOTHING: 

2764 raise DefaultAlreadySetError 

2765 

2766 self._default = Factory(meth, takes_self=True) 

2767 

2768 return meth 

2769 

2770 

2771_CountingAttr = _add_eq(_add_repr(_CountingAttr)) 

2772 

2773 

2774class Factory: 

2775 """ 

2776 Stores a factory callable. 

2777 

2778 If passed as the default value to `attrs.field`, the factory is used to 

2779 generate a new value. 

2780 

2781 Args: 

2782 factory (typing.Callable): 

2783 A callable that takes either none or exactly one mandatory 

2784 positional argument depending on *takes_self*. 

2785 

2786 takes_self (bool): 

2787 Pass the partially initialized instance that is being initialized 

2788 as a positional argument. 

2789 

2790 .. versionadded:: 17.1.0 *takes_self* 

2791 """ 

2792 

2793 __slots__ = ("factory", "takes_self") 

2794 

2795 def __init__(self, factory, takes_self=False): 

2796 self.factory = factory 

2797 self.takes_self = takes_self 

2798 

2799 def __getstate__(self): 

2800 """ 

2801 Play nice with pickle. 

2802 """ 

2803 return tuple(getattr(self, name) for name in self.__slots__) 

2804 

2805 def __setstate__(self, state): 

2806 """ 

2807 Play nice with pickle. 

2808 """ 

2809 for name, value in zip(self.__slots__, state): 

2810 setattr(self, name, value) 

2811 

2812 

2813_f = [ 

2814 Attribute( 

2815 name=name, 

2816 default=NOTHING, 

2817 validator=None, 

2818 repr=True, 

2819 cmp=None, 

2820 eq=True, 

2821 order=False, 

2822 hash=True, 

2823 init=True, 

2824 inherited=False, 

2825 ) 

2826 for name in Factory.__slots__ 

2827] 

2828 

2829Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f) 

2830 

2831 

2832class Converter: 

2833 """ 

2834 Stores a converter callable. 

2835 

2836 Allows for the wrapped converter to take additional arguments. The 

2837 arguments are passed in the order they are documented. 

2838 

2839 Args: 

2840 converter (Callable): A callable that converts the passed value. 

2841 

2842 takes_self (bool): 

2843 Pass the partially initialized instance that is being initialized 

2844 as a positional argument. (default: `False`) 

2845 

2846 takes_field (bool): 

2847 Pass the field definition (an :class:`Attribute`) into the 

2848 converter as a positional argument. (default: `False`) 

2849 

2850 .. versionadded:: 24.1.0 

2851 """ 

2852 

2853 __slots__ = ( 

2854 "__call__", 

2855 "_first_param_type", 

2856 "_global_name", 

2857 "converter", 

2858 "takes_field", 

2859 "takes_self", 

2860 ) 

2861 

2862 def __init__(self, converter, *, takes_self=False, takes_field=False): 

2863 self.converter = converter 

2864 self.takes_self = takes_self 

2865 self.takes_field = takes_field 

2866 

2867 ex = _AnnotationExtractor(converter) 

2868 self._first_param_type = ex.get_first_param_type() 

2869 

2870 if not (self.takes_self or self.takes_field): 

2871 self.__call__ = lambda value, _, __: self.converter(value) 

2872 elif self.takes_self and not self.takes_field: 

2873 self.__call__ = lambda value, instance, __: self.converter( 

2874 value, instance 

2875 ) 

2876 elif not self.takes_self and self.takes_field: 

2877 self.__call__ = lambda value, __, field: self.converter( 

2878 value, field 

2879 ) 

2880 else: 

2881 self.__call__ = lambda value, instance, field: self.converter( 

2882 value, instance, field 

2883 ) 

2884 

2885 rt = ex.get_return_type() 

2886 if rt is not None: 

2887 self.__call__.__annotations__["return"] = rt 

2888 

2889 @staticmethod 

2890 def _get_global_name(attr_name: str) -> str: 

2891 """ 

2892 Return the name that a converter for an attribute name *attr_name* 

2893 would have. 

2894 """ 

2895 return f"__attr_converter_{attr_name}" 

2896 

2897 def _fmt_converter_call(self, attr_name: str, value_var: str) -> str: 

2898 """ 

2899 Return a string that calls the converter for an attribute name 

2900 *attr_name* and the value in variable named *value_var* according to 

2901 `self.takes_self` and `self.takes_field`. 

2902 """ 

2903 if not (self.takes_self or self.takes_field): 

2904 return f"{self._get_global_name(attr_name)}({value_var})" 

2905 

2906 if self.takes_self and self.takes_field: 

2907 return f"{self._get_global_name(attr_name)}({value_var}, self, attr_dict['{attr_name}'])" 

2908 

2909 if self.takes_self: 

2910 return f"{self._get_global_name(attr_name)}({value_var}, self)" 

2911 

2912 return f"{self._get_global_name(attr_name)}({value_var}, attr_dict['{attr_name}'])" 

2913 

2914 def __getstate__(self): 

2915 """ 

2916 Return a dict containing only converter and takes_self -- the rest gets 

2917 computed when loading. 

2918 """ 

2919 return { 

2920 "converter": self.converter, 

2921 "takes_self": self.takes_self, 

2922 "takes_field": self.takes_field, 

2923 } 

2924 

2925 def __setstate__(self, state): 

2926 """ 

2927 Load instance from state. 

2928 """ 

2929 self.__init__(**state) 

2930 

2931 

2932_f = [ 

2933 Attribute( 

2934 name=name, 

2935 default=NOTHING, 

2936 validator=None, 

2937 repr=True, 

2938 cmp=None, 

2939 eq=True, 

2940 order=False, 

2941 hash=True, 

2942 init=True, 

2943 inherited=False, 

2944 ) 

2945 for name in ("converter", "takes_self", "takes_field") 

2946] 

2947 

2948Converter = _add_hash( 

2949 _add_eq(_add_repr(Converter, attrs=_f), attrs=_f), attrs=_f 

2950) 

2951 

2952 

2953def make_class( 

2954 name, attrs, bases=(object,), class_body=None, **attributes_arguments 

2955): 

2956 r""" 

2957 A quick way to create a new class called *name* with *attrs*. 

2958 

2959 .. note:: 

2960 

2961 ``make_class()`` is a thin wrapper around `attr.s`, not `attrs.define` 

2962 which means that it doesn't come with some of the improved defaults. 

2963 

2964 For example, if you want the same ``on_setattr`` behavior as in 

2965 `attrs.define`, you have to pass the hooks yourself: ``make_class(..., 

2966 on_setattr=setters.pipe(setters.convert, setters.validate)`` 

2967 

2968 .. warning:: 

2969 

2970 It is *your* duty to ensure that the class name and the attribute names 

2971 are valid identifiers. ``make_class()`` will *not* validate them for 

2972 you. 

2973 

2974 Args: 

2975 name (str): The name for the new class. 

2976 

2977 attrs (list | dict): 

2978 A list of names or a dictionary of mappings of names to `attr.ib`\ 

2979 s / `attrs.field`\ s. 

2980 

2981 The order is deduced from the order of the names or attributes 

2982 inside *attrs*. Otherwise the order of the definition of the 

2983 attributes is used. 

2984 

2985 bases (tuple[type, ...]): Classes that the new class will subclass. 

2986 

2987 class_body (dict): 

2988 An optional dictionary of class attributes for the new class. 

2989 

2990 attributes_arguments: Passed unmodified to `attr.s`. 

2991 

2992 Returns: 

2993 type: A new class with *attrs*. 

2994 

2995 .. versionadded:: 17.1.0 *bases* 

2996 .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. 

2997 .. versionchanged:: 23.2.0 *class_body* 

2998 .. versionchanged:: 25.2.0 Class names can now be unicode. 

2999 """ 

3000 # Class identifiers are converted into the normal form NFKC while parsing 

3001 name = unicodedata.normalize("NFKC", name) 

3002 

3003 if isinstance(attrs, dict): 

3004 cls_dict = attrs 

3005 elif isinstance(attrs, (list, tuple)): 

3006 cls_dict = {a: attrib() for a in attrs} 

3007 else: 

3008 msg = "attrs argument must be a dict or a list." 

3009 raise TypeError(msg) 

3010 

3011 pre_init = cls_dict.pop("__attrs_pre_init__", None) 

3012 post_init = cls_dict.pop("__attrs_post_init__", None) 

3013 user_init = cls_dict.pop("__init__", None) 

3014 

3015 body = {} 

3016 if class_body is not None: 

3017 body.update(class_body) 

3018 if pre_init is not None: 

3019 body["__attrs_pre_init__"] = pre_init 

3020 if post_init is not None: 

3021 body["__attrs_post_init__"] = post_init 

3022 if user_init is not None: 

3023 body["__init__"] = user_init 

3024 

3025 type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body)) 

3026 

3027 # For pickling to work, the __module__ variable needs to be set to the 

3028 # frame where the class is created. Bypass this step in environments where 

3029 # sys._getframe is not defined (Jython for example) or sys._getframe is not 

3030 # defined for arguments greater than 0 (IronPython). 

3031 with contextlib.suppress(AttributeError, ValueError): 

3032 type_.__module__ = sys._getframe(1).f_globals.get( 

3033 "__name__", "__main__" 

3034 ) 

3035 

3036 # We do it here for proper warnings with meaningful stacklevel. 

3037 cmp = attributes_arguments.pop("cmp", None) 

3038 ( 

3039 attributes_arguments["eq"], 

3040 attributes_arguments["order"], 

3041 ) = _determine_attrs_eq_order( 

3042 cmp, 

3043 attributes_arguments.get("eq"), 

3044 attributes_arguments.get("order"), 

3045 True, 

3046 ) 

3047 

3048 cls = _attrs(these=cls_dict, **attributes_arguments)(type_) 

3049 # Only add type annotations now or "_attrs()" will complain: 

3050 cls.__annotations__ = { 

3051 k: v.type for k, v in cls_dict.items() if v.type is not None 

3052 } 

3053 return cls 

3054 

3055 

3056# These are required by within this module so we define them here and merely 

3057# import into .validators / .converters. 

3058 

3059 

3060@attrs(slots=True, unsafe_hash=True) 

3061class _AndValidator: 

3062 """ 

3063 Compose many validators to a single one. 

3064 """ 

3065 

3066 _validators = attrib() 

3067 

3068 def __call__(self, inst, attr, value): 

3069 for v in self._validators: 

3070 v(inst, attr, value) 

3071 

3072 

3073def and_(*validators): 

3074 """ 

3075 A validator that composes multiple validators into one. 

3076 

3077 When called on a value, it runs all wrapped validators. 

3078 

3079 Args: 

3080 validators (~collections.abc.Iterable[typing.Callable]): 

3081 Arbitrary number of validators. 

3082 

3083 .. versionadded:: 17.1.0 

3084 """ 

3085 vals = [] 

3086 for validator in validators: 

3087 vals.extend( 

3088 validator._validators 

3089 if isinstance(validator, _AndValidator) 

3090 else [validator] 

3091 ) 

3092 

3093 return _AndValidator(tuple(vals)) 

3094 

3095 

3096def pipe(*converters): 

3097 """ 

3098 A converter that composes multiple converters into one. 

3099 

3100 When called on a value, it runs all wrapped converters, returning the 

3101 *last* value. 

3102 

3103 Type annotations will be inferred from the wrapped converters', if they 

3104 have any. 

3105 

3106 converters (~collections.abc.Iterable[typing.Callable]): 

3107 Arbitrary number of converters. 

3108 

3109 .. versionadded:: 20.1.0 

3110 """ 

3111 

3112 return_instance = any(isinstance(c, Converter) for c in converters) 

3113 

3114 if return_instance: 

3115 

3116 def pipe_converter(val, inst, field): 

3117 for c in converters: 

3118 val = ( 

3119 c(val, inst, field) if isinstance(c, Converter) else c(val) 

3120 ) 

3121 

3122 return val 

3123 

3124 else: 

3125 

3126 def pipe_converter(val): 

3127 for c in converters: 

3128 val = c(val) 

3129 

3130 return val 

3131 

3132 if not converters: 

3133 # If the converter list is empty, pipe_converter is the identity. 

3134 A = TypeVar("A") 

3135 pipe_converter.__annotations__.update({"val": A, "return": A}) 

3136 else: 

3137 # Get parameter type from first converter. 

3138 t = _AnnotationExtractor(converters[0]).get_first_param_type() 

3139 if t: 

3140 pipe_converter.__annotations__["val"] = t 

3141 

3142 last = converters[-1] 

3143 if not PY_3_11_PLUS and isinstance(last, Converter): 

3144 last = last.__call__ 

3145 

3146 # Get return type from last converter. 

3147 rt = _AnnotationExtractor(last).get_return_type() 

3148 if rt: 

3149 pipe_converter.__annotations__["return"] = rt 

3150 

3151 if return_instance: 

3152 return Converter(pipe_converter, takes_self=True, takes_field=True) 

3153 return pipe_converter