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

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

1126 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 

15import weakref 

16 

17from collections.abc import Callable, Mapping 

18from functools import cached_property 

19from typing import Any, NamedTuple, TypeVar 

20 

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

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

23from . import _compat, _config, setters 

24from ._compat import ( 

25 PY_3_10_PLUS, 

26 PY_3_11_PLUS, 

27 PY_3_13_PLUS, 

28 _AnnotationExtractor, 

29 _get_annotations, 

30 get_generic_base, 

31) 

32from .exceptions import ( 

33 DefaultAlreadySetError, 

34 FrozenInstanceError, 

35 NotAnAttrsClassError, 

36 UnannotatedAttributeError, 

37) 

38 

39 

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

41_OBJ_SETATTR = object.__setattr__ 

42_INIT_FACTORY_PAT = "__attr_factory_%s" 

43_CLASSVAR_PREFIXES = ( 

44 "typing.ClassVar", 

45 "t.ClassVar", 

46 "ClassVar", 

47 "typing_extensions.ClassVar", 

48) 

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

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

51# (when slots=True) 

52_HASH_CACHE_FIELD = "_attrs_cached_hash" 

53 

54_EMPTY_METADATA_SINGLETON = types.MappingProxyType({}) 

55 

56# Unique object for unequivocal getattr() defaults. 

57_SENTINEL = object() 

58 

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

60 

61 

62class _Nothing(enum.Enum): 

63 """ 

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

65 

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

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

68 

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

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

71 """ 

72 

73 NOTHING = enum.auto() 

74 

75 def __repr__(self): 

76 return "NOTHING" 

77 

78 def __bool__(self): 

79 return False 

80 

81 

82NOTHING = _Nothing.NOTHING 

83""" 

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

85 

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

87""" 

88 

89 

90class _CacheHashWrapper(int): 

91 """ 

92 An integer subclass that pickles / copies as None 

93 

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

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

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

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

98 

99 See GH #613 for more details. 

100 """ 

101 

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

103 return _none_constructor, _args 

104 

105 

106def attrib( 

107 default=NOTHING, 

108 validator=None, 

109 repr=True, 

110 cmp=None, 

111 hash=None, 

112 init=True, 

113 metadata=None, 

114 type=None, 

115 converter=None, 

116 factory=None, 

117 kw_only=None, 

118 eq=None, 

119 order=None, 

120 on_setattr=None, 

121 alias=None, 

122): 

123 """ 

124 Create a new field / attribute on a class. 

125 

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

127 

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

129 though). 

130 

131 .. warning:: 

132 

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

134 `attr.s` (or similar)! 

135 

136 

137 .. versionadded:: 15.2.0 *convert* 

138 .. versionadded:: 16.3.0 *metadata* 

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

140 .. versionchanged:: 17.1.0 

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

142 .. versionadded:: 17.3.0 *type* 

143 .. deprecated:: 17.4.0 *convert* 

144 .. versionadded:: 17.4.0 

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

146 consistency with other noun-based arguments. 

147 .. versionadded:: 18.1.0 

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

149 .. versionadded:: 18.2.0 *kw_only* 

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

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

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

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

154 .. versionadded:: 20.1.0 *on_setattr* 

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

156 .. versionchanged:: 21.1.0 

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

158 .. versionchanged:: 21.1.0 *cmp* undeprecated 

159 .. versionadded:: 22.2.0 *alias* 

160 .. versionchanged:: 25.4.0 

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

162 None. 

163 """ 

164 eq, eq_key, order, order_key = _determine_attrib_eq_order( 

165 cmp, eq, order, True 

166 ) 

167 

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

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

170 raise TypeError(msg) 

171 

172 if factory is not None: 

173 if default is not NOTHING: 

174 msg = ( 

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

176 ) 

177 raise ValueError(msg) 

178 if not callable(factory): 

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

180 raise ValueError(msg) 

181 default = Factory(factory) 

182 

183 if metadata is None: 

184 metadata = {} 

185 

186 # Apply syntactic sugar by auto-wrapping. 

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

188 on_setattr = setters.pipe(*on_setattr) 

189 

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

191 validator = and_(*validator) 

192 

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

194 converter = pipe(*converter) 

195 

196 return _CountingAttr( 

197 default=default, 

198 validator=validator, 

199 repr=repr, 

200 cmp=None, 

201 hash=hash, 

202 init=init, 

203 converter=converter, 

204 metadata=metadata, 

205 type=type, 

206 kw_only=kw_only, 

207 eq=eq, 

208 eq_key=eq_key, 

209 order=order, 

210 order_key=order_key, 

211 on_setattr=on_setattr, 

212 alias=alias, 

213 ) 

214 

215 

216def _compile_and_eval( 

217 script: str, 

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

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

220 filename: str = "", 

221) -> None: 

222 """ 

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

224 variables. 

225 """ 

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

227 eval(bytecode, globs, locs) 

228 

229 

230def _linecache_and_compile( 

231 script: str, 

232 filename: str, 

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

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

235) -> dict[str, Any]: 

236 """ 

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

238 """ 

239 

240 locs = {} if locals is None else locals 

241 

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

243 # we add a fake linecache entry. 

244 count = 1 

245 base_filename = filename 

246 while True: 

247 linecache_tuple = ( 

248 len(script), 

249 None, 

250 script.splitlines(True), 

251 filename, 

252 ) 

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

254 if old_val == linecache_tuple: 

255 break 

256 

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

258 count += 1 

259 

260 _compile_and_eval(script, globs, locs, filename) 

261 

262 return locs 

263 

264 

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

266 """ 

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

268 

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

270 

271 class MyClassAttributes(tuple): 

272 __slots__ = () 

273 x = property(itemgetter(0)) 

274 """ 

275 attr_class_name = f"{cls_name}Attributes" 

276 body = {} 

277 for i, attr_name in enumerate(attr_names): 

278 

279 def getter(self, i=i): 

280 return self[i] 

281 

282 body[attr_name] = property(getter) 

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

284 

285 

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

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

288class _Attributes(NamedTuple): 

289 attrs: type 

290 base_attrs: list[Attribute] 

291 base_attrs_map: dict[str, type] 

292 

293 

294def _is_class_var(annot): 

295 """ 

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

297 

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

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

300 disadvantage compared to plain old classes. 

301 """ 

302 annot = str(annot) 

303 

304 # Annotation can be quoted. 

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

306 annot = annot[1:-1] 

307 

308 return annot.startswith(_CLASSVAR_PREFIXES) 

309 

310 

311def _has_own_attribute(cls, attrib_name): 

312 """ 

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

314 """ 

315 return attrib_name in cls.__dict__ 

316 

317 

318def _collect_base_attrs( 

319 cls, taken_attr_names 

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

321 """ 

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

323 """ 

324 base_attrs = [] 

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

326 

327 # Traverse the MRO and collect attributes. 

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

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

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

331 continue 

332 

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

334 base_attrs.append(a) 

335 base_attr_map[a.name] = base_cls 

336 

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

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

339 # instance. 

340 filtered = [] 

341 seen = set() 

342 for a in reversed(base_attrs): 

343 if a.name in seen: 

344 continue 

345 filtered.insert(0, a) 

346 seen.add(a.name) 

347 

348 return filtered, base_attr_map 

349 

350 

351def _collect_base_attrs_broken(cls, taken_attr_names): 

352 """ 

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

354 

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

356 

357 Adhere to the old incorrect behavior. 

358 

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

360 leads to the buggy behavior reported in #428. 

361 """ 

362 base_attrs = [] 

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

364 

365 # Traverse the MRO and collect attributes. 

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

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

368 if a.name in taken_attr_names: 

369 continue 

370 

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

372 taken_attr_names.add(a.name) 

373 base_attrs.append(a) 

374 base_attr_map[a.name] = base_cls 

375 

376 return base_attrs, base_attr_map 

377 

378 

379def _transform_attrs( 

380 cls, 

381 these, 

382 auto_attribs, 

383 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 no = ClassProps.KeywordOnly.NO 

441 own_attrs = [ 

442 fca( 

443 attr_name, 

444 ca, 

445 kw_only is not no, 

446 anns.get(attr_name), 

447 ) 

448 for attr_name, ca in ca_list 

449 ] 

450 

451 if collect_by_mro: 

452 base_attrs, base_attr_map = _collect_base_attrs( 

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

454 ) 

455 else: 

456 base_attrs, base_attr_map = _collect_base_attrs_broken( 

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

458 ) 

459 

460 if kw_only is ClassProps.KeywordOnly.FORCE: 

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

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

463 

464 attrs = base_attrs + own_attrs 

465 

466 if field_transformer is not None: 

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

468 

469 # Check attr order after executing the field_transformer. 

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

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

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

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

474 had_default = False 

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

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

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

478 raise ValueError(msg) 

479 

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

481 had_default = True 

482 

483 # Resolve default field alias after executing field_transformer. 

484 # This allows field_transformer to differentiate between explicit vs 

485 # default aliases and supply their own defaults. 

486 for a in attrs: 

487 if not a.alias: 

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

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

490 

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

492 # add or remove attributes! 

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

494 AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) 

495 

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

497 

498 

499def _make_cached_property_getattr(cached_properties, original_getattr, cls): 

500 lines = [ 

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

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

503 "def wrapper(_cls):", 

504 " __class__ = _cls", 

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

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

507 " if func is not None:", 

508 " result = func(self)", 

509 " _setter = _cached_setattr_get(self)", 

510 " _setter(item, result)", 

511 " return result", 

512 ] 

513 if original_getattr is not None: 

514 lines.append( 

515 " return original_getattr(self, item)", 

516 ) 

517 else: 

518 lines.extend( 

519 [ 

520 " try:", 

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

522 " except AttributeError:", 

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

524 " raise", 

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

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

527 " raise AttributeError(original_error)", 

528 ] 

529 ) 

530 

531 lines.extend( 

532 [ 

533 " return __getattr__", 

534 "__getattr__ = wrapper(_cls)", 

535 ] 

536 ) 

537 

538 unique_filename = _generate_unique_filename(cls, "getattr") 

539 

540 glob = { 

541 "cached_properties": cached_properties, 

542 "_cached_setattr_get": _OBJ_SETATTR.__get__, 

543 "original_getattr": original_getattr, 

544 } 

545 

546 return _linecache_and_compile( 

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

548 )["__getattr__"] 

549 

550 

551def _frozen_setattrs(self, name, value): 

552 """ 

553 Attached to frozen classes as __setattr__. 

554 """ 

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

556 "__cause__", 

557 "__context__", 

558 "__traceback__", 

559 "__suppress_context__", 

560 "__notes__", 

561 ): 

562 BaseException.__setattr__(self, name, value) 

563 return 

564 

565 raise FrozenInstanceError 

566 

567 

568def _frozen_delattrs(self, name): 

569 """ 

570 Attached to frozen classes as __delattr__. 

571 """ 

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

573 BaseException.__delattr__(self, name) 

574 return 

575 

576 raise FrozenInstanceError 

577 

578 

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

580 """ 

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

582 *changes* applied. 

583 

584 .. tip:: 

585 

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

587 

588 Args: 

589 

590 inst: 

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

592 as a positional argument. 

593 

594 changes: 

595 Keyword changes in the new copy. 

596 

597 Returns: 

598 A copy of inst with *changes* incorporated. 

599 

600 Raises: 

601 TypeError: 

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

603 

604 attrs.exceptions.NotAnAttrsClassError: 

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

606 

607 .. versionadded:: 17.1.0 

608 .. deprecated:: 23.1.0 

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

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

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

612 argument. 

613 .. versionchanged:: 24.1.0 

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

615 """ 

616 try: 

617 (inst,) = args 

618 except ValueError: 

619 msg = ( 

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

621 ) 

622 raise TypeError(msg) from None 

623 

624 cls = inst.__class__ 

625 attrs = fields(cls) 

626 for a in attrs: 

627 if not a.init: 

628 continue 

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

630 init_name = a.alias 

631 if init_name not in changes: 

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

633 

634 return cls(**changes) 

635 

636 

637class _ClassBuilder: 

638 """ 

639 Iteratively build *one* class. 

640 """ 

641 

642 __slots__ = ( 

643 "_add_method_dunders", 

644 "_attr_names", 

645 "_attrs", 

646 "_base_attr_map", 

647 "_base_names", 

648 "_cache_hash", 

649 "_cls", 

650 "_cls_dict", 

651 "_delete_attribs", 

652 "_frozen", 

653 "_has_custom_setattr", 

654 "_has_post_init", 

655 "_has_pre_init", 

656 "_is_exc", 

657 "_on_setattr", 

658 "_pre_init_has_args", 

659 "_repr_added", 

660 "_script_snippets", 

661 "_slots", 

662 "_weakref_slot", 

663 "_wrote_own_setattr", 

664 ) 

665 

666 def __init__( 

667 self, 

668 cls: type, 

669 these, 

670 auto_attribs: bool, 

671 props: ClassProps, 

672 has_custom_setattr: bool, 

673 ): 

674 attrs, base_attrs, base_map = _transform_attrs( 

675 cls, 

676 these, 

677 auto_attribs, 

678 props.kw_only, 

679 props.collected_fields_by_mro, 

680 props.field_transformer, 

681 ) 

682 

683 self._cls = cls 

684 self._cls_dict = dict(cls.__dict__) if props.is_slotted else {} 

685 self._attrs = attrs 

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

687 self._base_attr_map = base_map 

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

689 self._slots = props.is_slotted 

690 self._frozen = props.is_frozen 

691 self._weakref_slot = props.has_weakref_slot 

692 self._cache_hash = ( 

693 props.hashability is ClassProps.Hashability.HASHABLE_CACHED 

694 ) 

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

696 self._pre_init_has_args = False 

697 if self._has_pre_init: 

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

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

700 pre_init_func = cls.__attrs_pre_init__ 

701 pre_init_signature = inspect.signature(pre_init_func) 

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

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

704 self._delete_attribs = not bool(these) 

705 self._is_exc = props.is_exception 

706 self._on_setattr = props.on_setattr_hook 

707 

708 self._has_custom_setattr = has_custom_setattr 

709 self._wrote_own_setattr = False 

710 

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

712 self._cls_dict["__attrs_props__"] = props 

713 

714 if props.is_frozen: 

715 self._cls_dict["__setattr__"] = _frozen_setattrs 

716 self._cls_dict["__delattr__"] = _frozen_delattrs 

717 

718 self._wrote_own_setattr = True 

719 elif self._on_setattr in ( 

720 _DEFAULT_ON_SETATTR, 

721 setters.validate, 

722 setters.convert, 

723 ): 

724 has_validator = has_converter = False 

725 for a in attrs: 

726 if a.validator is not None: 

727 has_validator = True 

728 if a.converter is not None: 

729 has_converter = True 

730 

731 if has_validator and has_converter: 

732 break 

733 if ( 

734 ( 

735 self._on_setattr == _DEFAULT_ON_SETATTR 

736 and not (has_validator or has_converter) 

737 ) 

738 or (self._on_setattr == setters.validate and not has_validator) 

739 or (self._on_setattr == setters.convert and not has_converter) 

740 ): 

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

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

743 # no on_setattr. 

744 self._on_setattr = None 

745 

746 if props.added_pickling: 

747 ( 

748 self._cls_dict["__getstate__"], 

749 self._cls_dict["__setstate__"], 

750 ) = self._make_getstate_setstate() 

751 

752 # tuples of script, globs, hook 

753 self._script_snippets: list[ 

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

755 ] = [] 

756 self._repr_added = False 

757 

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

759 # exist. 

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

761 self._cls, "__qualname__" 

762 ): 

763 self._add_method_dunders = self._add_method_dunders_safe 

764 else: 

765 self._add_method_dunders = self._add_method_dunders_unsafe 

766 

767 def __repr__(self): 

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

769 

770 def _eval_snippets(self) -> None: 

771 """ 

772 Evaluate any registered snippets in one go. 

773 """ 

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

775 globs = {} 

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

777 globs.update(snippet_globs) 

778 

779 locs = _linecache_and_compile( 

780 script, 

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

782 globs, 

783 ) 

784 

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

786 hook(self._cls_dict, locs) 

787 

788 def build_class(self): 

789 """ 

790 Finalize class based on the accumulated configuration. 

791 

792 Builder cannot be used after calling this method. 

793 """ 

794 self._eval_snippets() 

795 if self._slots is True: 

796 cls = self._create_slots_class() 

797 self._cls.__attrs_base_of_slotted__ = weakref.ref(cls) 

798 else: 

799 cls = self._patch_original_class() 

800 if PY_3_10_PLUS: 

801 cls = abc.update_abstractmethods(cls) 

802 

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

804 # _has_own_attribute does NOT work properly for classmethods. 

805 if ( 

806 getattr(cls, "__attrs_init_subclass__", None) 

807 and "__attrs_init_subclass__" not in cls.__dict__ 

808 ): 

809 cls.__attrs_init_subclass__() 

810 

811 return cls 

812 

813 def _patch_original_class(self): 

814 """ 

815 Apply accumulated methods and return the class. 

816 """ 

817 cls = self._cls 

818 base_names = self._base_names 

819 

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

821 if self._delete_attribs: 

822 for name in self._attr_names: 

823 if ( 

824 name not in base_names 

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

826 ): 

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

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

829 # same name by using only a type annotation. 

830 with contextlib.suppress(AttributeError): 

831 delattr(cls, name) 

832 

833 # Attach our dunder methods. 

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

835 setattr(cls, name, value) 

836 

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

838 # reset it to object's. 

839 if not self._wrote_own_setattr and getattr( 

840 cls, "__attrs_own_setattr__", False 

841 ): 

842 cls.__attrs_own_setattr__ = False 

843 

844 if not self._has_custom_setattr: 

845 cls.__setattr__ = _OBJ_SETATTR 

846 

847 return cls 

848 

849 def _create_slots_class(self): 

850 """ 

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

852 """ 

853 cd = { 

854 k: v 

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

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

857 } 

858 

859 # 3.14.0rc2+ 

860 if hasattr(sys, "_clear_type_descriptors"): 

861 sys._clear_type_descriptors(self._cls) 

862 

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

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

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

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

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

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

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

870 # XXX: OK with us. 

871 if not self._wrote_own_setattr: 

872 cd["__attrs_own_setattr__"] = False 

873 

874 if not self._has_custom_setattr: 

875 for base_cls in self._cls.__bases__: 

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

877 cd["__setattr__"] = _OBJ_SETATTR 

878 break 

879 

880 # Traverse the MRO to collect existing slots 

881 # and check for an existing __weakref__. 

882 existing_slots = {} 

883 weakref_inherited = False 

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

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

886 weakref_inherited = True 

887 existing_slots.update( 

888 { 

889 name: getattr(base_cls, name) 

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

891 } 

892 ) 

893 

894 base_names = set(self._base_names) 

895 

896 names = self._attr_names 

897 if ( 

898 self._weakref_slot 

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

900 and "__weakref__" not in names 

901 and not weakref_inherited 

902 ): 

903 names += ("__weakref__",) 

904 

905 cached_properties = { 

906 name: cached_prop.func 

907 for name, cached_prop in cd.items() 

908 if isinstance(cached_prop, cached_property) 

909 } 

910 

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

912 # To know to update them. 

913 additional_closure_functions_to_update = [] 

914 if cached_properties: 

915 class_annotations = _get_annotations(self._cls) 

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

917 # Add cached properties to names for slotting. 

918 names += (name,) 

919 # Clear out function from class to avoid clashing. 

920 del cd[name] 

921 additional_closure_functions_to_update.append(func) 

922 annotation = inspect.signature(func).return_annotation 

923 if annotation is not inspect.Parameter.empty: 

924 class_annotations[name] = annotation 

925 

926 original_getattr = cd.get("__getattr__") 

927 if original_getattr is not None: 

928 additional_closure_functions_to_update.append(original_getattr) 

929 

930 cd["__getattr__"] = _make_cached_property_getattr( 

931 cached_properties, original_getattr, self._cls 

932 ) 

933 

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

935 # Setting __slots__ to inherited attributes wastes memory. 

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

937 

938 # There are slots for attributes from current class 

939 # that are defined in parent classes. 

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

941 # we collect them here and update the class dict 

942 reused_slots = { 

943 slot: slot_descriptor 

944 for slot, slot_descriptor in existing_slots.items() 

945 if slot in slot_names 

946 } 

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

948 cd.update(reused_slots) 

949 if self._cache_hash: 

950 slot_names.append(_HASH_CACHE_FIELD) 

951 

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

953 

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

955 

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

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

958 

959 # The following is a fix for 

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

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

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

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

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

965 for item in itertools.chain( 

966 cls.__dict__.values(), additional_closure_functions_to_update 

967 ): 

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

969 # Class- and staticmethods hide their functions inside. 

970 # These might need to be rewritten as well. 

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

972 elif isinstance(item, property): 

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

974 # There is no universal way for other descriptors. 

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

976 else: 

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

978 

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

980 continue 

981 for cell in closure_cells: 

982 try: 

983 match = cell.cell_contents is self._cls 

984 except ValueError: # noqa: PERF203 

985 # ValueError: Cell is empty 

986 pass 

987 else: 

988 if match: 

989 cell.cell_contents = cls 

990 return cls 

991 

992 def add_repr(self, ns): 

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

994 

995 def _attach_repr(cls_dict, globs): 

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

997 

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

999 self._repr_added = True 

1000 return self 

1001 

1002 def add_str(self): 

1003 if not self._repr_added: 

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

1005 raise ValueError(msg) 

1006 

1007 def __str__(self): 

1008 return self.__repr__() 

1009 

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

1011 return self 

1012 

1013 def _make_getstate_setstate(self): 

1014 """ 

1015 Create custom __setstate__ and __getstate__ methods. 

1016 """ 

1017 # __weakref__ is not writable. 

1018 state_attr_names = tuple( 

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

1020 ) 

1021 

1022 def slots_getstate(self): 

1023 """ 

1024 Automatically created by attrs. 

1025 """ 

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

1027 

1028 hash_caching_enabled = self._cache_hash 

1029 

1030 def slots_setstate(self, state): 

1031 """ 

1032 Automatically created by attrs. 

1033 """ 

1034 __bound_setattr = _OBJ_SETATTR.__get__(self) 

1035 if isinstance(state, tuple): 

1036 # Backward compatibility with attrs instances pickled with 

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

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

1039 __bound_setattr(name, value) 

1040 else: 

1041 for name in state_attr_names: 

1042 if name in state: 

1043 __bound_setattr(name, state[name]) 

1044 

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

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

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

1048 # miss. 

1049 if hash_caching_enabled: 

1050 __bound_setattr(_HASH_CACHE_FIELD, None) 

1051 

1052 return slots_getstate, slots_setstate 

1053 

1054 def make_unhashable(self): 

1055 self._cls_dict["__hash__"] = None 

1056 return self 

1057 

1058 def add_hash(self): 

1059 script, globs = _make_hash_script( 

1060 self._cls, 

1061 self._attrs, 

1062 frozen=self._frozen, 

1063 cache_hash=self._cache_hash, 

1064 ) 

1065 

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

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

1068 

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

1070 

1071 return self 

1072 

1073 def add_init(self): 

1074 script, globs, annotations = _make_init_script( 

1075 self._cls, 

1076 self._attrs, 

1077 self._has_pre_init, 

1078 self._pre_init_has_args, 

1079 self._has_post_init, 

1080 self._frozen, 

1081 self._slots, 

1082 self._cache_hash, 

1083 self._base_attr_map, 

1084 self._is_exc, 

1085 self._on_setattr, 

1086 attrs_init=False, 

1087 ) 

1088 

1089 def _attach_init(cls_dict, globs): 

1090 init = globs["__init__"] 

1091 init.__annotations__ = annotations 

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

1093 

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

1095 

1096 return self 

1097 

1098 def add_replace(self): 

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

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

1101 ) 

1102 return self 

1103 

1104 def add_match_args(self): 

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

1106 field.name 

1107 for field in self._attrs 

1108 if field.init and not field.kw_only 

1109 ) 

1110 

1111 def add_attrs_init(self): 

1112 script, globs, annotations = _make_init_script( 

1113 self._cls, 

1114 self._attrs, 

1115 self._has_pre_init, 

1116 self._pre_init_has_args, 

1117 self._has_post_init, 

1118 self._frozen, 

1119 self._slots, 

1120 self._cache_hash, 

1121 self._base_attr_map, 

1122 self._is_exc, 

1123 self._on_setattr, 

1124 attrs_init=True, 

1125 ) 

1126 

1127 def _attach_attrs_init(cls_dict, globs): 

1128 init = globs["__attrs_init__"] 

1129 init.__annotations__ = annotations 

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

1131 

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

1133 

1134 return self 

1135 

1136 def add_eq(self): 

1137 cd = self._cls_dict 

1138 

1139 script, globs = _make_eq_script(self._attrs) 

1140 

1141 def _attach_eq(cls_dict, globs): 

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

1143 

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

1145 

1146 cd["__ne__"] = __ne__ 

1147 

1148 return self 

1149 

1150 def add_order(self): 

1151 cd = self._cls_dict 

1152 

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

1154 self._add_method_dunders(meth) 

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

1156 ) 

1157 

1158 return self 

1159 

1160 def add_setattr(self): 

1161 sa_attrs = {} 

1162 for a in self._attrs: 

1163 on_setattr = a.on_setattr or self._on_setattr 

1164 if on_setattr and on_setattr is not setters.NO_OP: 

1165 sa_attrs[a.name] = a, on_setattr 

1166 

1167 if not sa_attrs: 

1168 return self 

1169 

1170 if self._has_custom_setattr: 

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

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

1173 raise ValueError(msg) 

1174 

1175 # docstring comes from _add_method_dunders 

1176 def __setattr__(self, name, val): 

1177 try: 

1178 a, hook = sa_attrs[name] 

1179 except KeyError: 

1180 nval = val 

1181 else: 

1182 nval = hook(self, a, val) 

1183 

1184 _OBJ_SETATTR(self, name, nval) 

1185 

1186 self._cls_dict["__attrs_own_setattr__"] = True 

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

1188 self._wrote_own_setattr = True 

1189 

1190 return self 

1191 

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

1193 """ 

1194 Add __module__ and __qualname__ to a *method*. 

1195 """ 

1196 method.__module__ = self._cls.__module__ 

1197 

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

1199 

1200 method.__doc__ = ( 

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

1202 ) 

1203 

1204 return method 

1205 

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

1207 """ 

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

1209 """ 

1210 with contextlib.suppress(AttributeError): 

1211 method.__module__ = self._cls.__module__ 

1212 

1213 with contextlib.suppress(AttributeError): 

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

1215 

1216 with contextlib.suppress(AttributeError): 

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

1218 

1219 return method 

1220 

1221 

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

1223 """ 

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

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

1226 """ 

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

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

1229 raise ValueError(msg) 

1230 

1231 # cmp takes precedence due to bw-compatibility. 

1232 if cmp is not None: 

1233 return cmp, cmp 

1234 

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

1236 # mirrors equality. 

1237 if eq is None: 

1238 eq = default_eq 

1239 

1240 if order is None: 

1241 order = eq 

1242 

1243 if eq is False and order is True: 

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

1245 raise ValueError(msg) 

1246 

1247 return eq, order 

1248 

1249 

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

1251 """ 

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

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

1254 """ 

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

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

1257 raise ValueError(msg) 

1258 

1259 def decide_callable_or_boolean(value): 

1260 """ 

1261 Decide whether a key function is used. 

1262 """ 

1263 if callable(value): 

1264 value, key = True, value 

1265 else: 

1266 key = None 

1267 return value, key 

1268 

1269 # cmp takes precedence due to bw-compatibility. 

1270 if cmp is not None: 

1271 cmp, cmp_key = decide_callable_or_boolean(cmp) 

1272 return cmp, cmp_key, cmp, cmp_key 

1273 

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

1275 # mirrors equality. 

1276 if eq is None: 

1277 eq, eq_key = default_eq, None 

1278 else: 

1279 eq, eq_key = decide_callable_or_boolean(eq) 

1280 

1281 if order is None: 

1282 order, order_key = eq, eq_key 

1283 else: 

1284 order, order_key = decide_callable_or_boolean(order) 

1285 

1286 if eq is False and order is True: 

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

1288 raise ValueError(msg) 

1289 

1290 return eq, eq_key, order, order_key 

1291 

1292 

1293def _determine_whether_to_implement( 

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

1295): 

1296 """ 

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

1298 

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

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

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

1302 

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

1304 """ 

1305 if flag is True or flag is False: 

1306 return flag 

1307 

1308 if flag is None and auto_detect is False: 

1309 return default 

1310 

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

1312 for dunder in dunders: 

1313 if _has_own_attribute(cls, dunder): 

1314 return False 

1315 

1316 return default 

1317 

1318 

1319def attrs( 

1320 maybe_cls=None, 

1321 these=None, 

1322 repr_ns=None, 

1323 repr=None, 

1324 cmp=None, 

1325 hash=None, 

1326 init=None, 

1327 slots=False, 

1328 frozen=False, 

1329 weakref_slot=True, 

1330 str=False, 

1331 auto_attribs=False, 

1332 kw_only=False, 

1333 cache_hash=False, 

1334 auto_exc=False, 

1335 eq=None, 

1336 order=None, 

1337 auto_detect=False, 

1338 collect_by_mro=False, 

1339 getstate_setstate=None, 

1340 on_setattr=None, 

1341 field_transformer=None, 

1342 match_args=True, 

1343 unsafe_hash=None, 

1344 force_kw_only=True, 

1345): 

1346 r""" 

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

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

1349 

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

1351 *never* go away, though). 

1352 

1353 Args: 

1354 repr_ns (str): 

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

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

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

1358 pointless in Python 3 and is therefore deprecated. 

1359 

1360 .. caution:: 

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

1362 can have different defaults. 

1363 

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

1365 

1366 .. versionadded:: 16.0.0 *slots* 

1367 .. versionadded:: 16.1.0 *frozen* 

1368 .. versionadded:: 16.3.0 *str* 

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

1370 .. versionchanged:: 17.1.0 

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

1372 .. versionadded:: 17.3.0 *auto_attribs* 

1373 .. versionchanged:: 18.1.0 

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

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

1376 .. versionadded:: 18.2.0 *weakref_slot* 

1377 .. deprecated:: 18.2.0 

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

1379 `DeprecationWarning` if the classes compared are subclasses of 

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

1381 to each other. 

1382 .. versionchanged:: 19.2.0 

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

1384 subclasses comparable anymore. 

1385 .. versionadded:: 18.2.0 *kw_only* 

1386 .. versionadded:: 18.2.0 *cache_hash* 

1387 .. versionadded:: 19.1.0 *auto_exc* 

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

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

1390 .. versionadded:: 20.1.0 *auto_detect* 

1391 .. versionadded:: 20.1.0 *collect_by_mro* 

1392 .. versionadded:: 20.1.0 *getstate_setstate* 

1393 .. versionadded:: 20.1.0 *on_setattr* 

1394 .. versionadded:: 20.3.0 *field_transformer* 

1395 .. versionchanged:: 21.1.0 

1396 ``init=False`` injects ``__attrs_init__`` 

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

1398 .. versionchanged:: 21.1.0 *cmp* undeprecated 

1399 .. versionadded:: 21.3.0 *match_args* 

1400 .. versionadded:: 22.2.0 

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

1402 .. deprecated:: 24.1.0 *repr_ns* 

1403 .. versionchanged:: 24.1.0 

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

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

1406 uncomparable values like `math.nan`. 

1407 .. versionadded:: 24.1.0 

1408 If a class has an *inherited* classmethod called 

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

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

1411 .. versionchanged:: 25.4.0 

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

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

1414 .. versionadded:: 25.4.0 *force_kw_only* 

1415 """ 

1416 if repr_ns is not None: 

1417 import warnings 

1418 

1419 warnings.warn( 

1420 DeprecationWarning( 

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

1422 ), 

1423 stacklevel=2, 

1424 ) 

1425 

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

1427 

1428 # unsafe_hash takes precedence due to PEP 681. 

1429 if unsafe_hash is not None: 

1430 hash = unsafe_hash 

1431 

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

1433 on_setattr = setters.pipe(*on_setattr) 

1434 

1435 def wrap(cls): 

1436 nonlocal hash 

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 eq = not is_exc and _determine_whether_to_implement( 

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

1449 ) 

1450 

1451 Hashability = ClassProps.Hashability 

1452 

1453 if is_exc: 

1454 hashability = Hashability.LEAVE_ALONE 

1455 elif hash is True: 

1456 hashability = ( 

1457 Hashability.HASHABLE_CACHED 

1458 if cache_hash 

1459 else Hashability.HASHABLE 

1460 ) 

1461 elif hash is False: 

1462 hashability = Hashability.LEAVE_ALONE 

1463 elif hash is None: 

1464 if auto_detect is True and _has_own_attribute(cls, "__hash__"): 

1465 hashability = Hashability.LEAVE_ALONE 

1466 elif eq is True and is_frozen is True: 

1467 hashability = ( 

1468 Hashability.HASHABLE_CACHED 

1469 if cache_hash 

1470 else Hashability.HASHABLE 

1471 ) 

1472 elif eq is False: 

1473 hashability = Hashability.LEAVE_ALONE 

1474 else: 

1475 hashability = Hashability.UNHASHABLE 

1476 else: 

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

1478 raise TypeError(msg) 

1479 

1480 KeywordOnly = ClassProps.KeywordOnly 

1481 if kw_only: 

1482 kwo = KeywordOnly.FORCE if force_kw_only else KeywordOnly.YES 

1483 else: 

1484 kwo = KeywordOnly.NO 

1485 

1486 props = ClassProps( 

1487 is_exception=is_exc, 

1488 is_frozen=is_frozen, 

1489 is_slotted=slots, 

1490 collected_fields_by_mro=collect_by_mro, 

1491 added_init=_determine_whether_to_implement( 

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

1493 ), 

1494 added_repr=_determine_whether_to_implement( 

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

1496 ), 

1497 added_eq=eq, 

1498 added_ordering=not is_exc 

1499 and _determine_whether_to_implement( 

1500 cls, 

1501 order_, 

1502 auto_detect, 

1503 ("__lt__", "__le__", "__gt__", "__ge__"), 

1504 ), 

1505 hashability=hashability, 

1506 added_match_args=match_args, 

1507 kw_only=kwo, 

1508 has_weakref_slot=weakref_slot, 

1509 added_str=str, 

1510 added_pickling=_determine_whether_to_implement( 

1511 cls, 

1512 getstate_setstate, 

1513 auto_detect, 

1514 ("__getstate__", "__setstate__"), 

1515 default=slots, 

1516 ), 

1517 on_setattr_hook=on_setattr, 

1518 field_transformer=field_transformer, 

1519 ) 

1520 

1521 if not props.is_hashable and cache_hash: 

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

1523 raise TypeError(msg) 

1524 

1525 builder = _ClassBuilder( 

1526 cls, 

1527 these, 

1528 auto_attribs=auto_attribs, 

1529 props=props, 

1530 has_custom_setattr=has_own_setattr, 

1531 ) 

1532 

1533 if props.added_repr: 

1534 builder.add_repr(repr_ns) 

1535 

1536 if props.added_str: 

1537 builder.add_str() 

1538 

1539 if props.added_eq: 

1540 builder.add_eq() 

1541 if props.added_ordering: 

1542 builder.add_order() 

1543 

1544 if not frozen: 

1545 builder.add_setattr() 

1546 

1547 if props.is_hashable: 

1548 builder.add_hash() 

1549 elif props.hashability is Hashability.UNHASHABLE: 

1550 builder.make_unhashable() 

1551 

1552 if props.added_init: 

1553 builder.add_init() 

1554 else: 

1555 builder.add_attrs_init() 

1556 if cache_hash: 

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

1558 raise TypeError(msg) 

1559 

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

1561 builder.add_replace() 

1562 

1563 if ( 

1564 PY_3_10_PLUS 

1565 and match_args 

1566 and not _has_own_attribute(cls, "__match_args__") 

1567 ): 

1568 builder.add_match_args() 

1569 

1570 return builder.build_class() 

1571 

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

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

1574 if maybe_cls is None: 

1575 return wrap 

1576 

1577 return wrap(maybe_cls) 

1578 

1579 

1580_attrs = attrs 

1581""" 

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

1583*attrs*. 

1584""" 

1585 

1586 

1587def _has_frozen_base_class(cls): 

1588 """ 

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

1590 __setattr__. 

1591 """ 

1592 return cls.__setattr__ is _frozen_setattrs 

1593 

1594 

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

1596 """ 

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

1598 """ 

1599 return ( 

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

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

1602 ) 

1603 

1604 

1605def _make_hash_script( 

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

1607) -> tuple[str, dict]: 

1608 attrs = tuple( 

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

1610 ) 

1611 

1612 tab = " " 

1613 

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

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

1616 globs = {} 

1617 

1618 hash_def = "def __hash__(self" 

1619 hash_func = "hash((" 

1620 closing_braces = "))" 

1621 if not cache_hash: 

1622 hash_def += "):" 

1623 else: 

1624 hash_def += ", *" 

1625 

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

1627 hash_func = "_cache_wrapper(" + hash_func 

1628 closing_braces += ")" 

1629 

1630 method_lines = [hash_def] 

1631 

1632 def append_hash_computation_lines(prefix, indent): 

1633 """ 

1634 Generate the code for actually computing the hash code. 

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

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

1637 """ 

1638 

1639 method_lines.extend( 

1640 [ 

1641 indent + prefix + hash_func, 

1642 indent + f" {type_hash},", 

1643 ] 

1644 ) 

1645 

1646 for a in attrs: 

1647 if a.eq_key: 

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

1649 globs[cmp_name] = a.eq_key 

1650 method_lines.append( 

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

1652 ) 

1653 else: 

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

1655 

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

1657 

1658 if cache_hash: 

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

1660 if frozen: 

1661 append_hash_computation_lines( 

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

1663 ) 

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

1665 else: 

1666 append_hash_computation_lines( 

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

1668 ) 

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

1670 else: 

1671 append_hash_computation_lines("return ", tab) 

1672 

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

1674 return script, globs 

1675 

1676 

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

1678 """ 

1679 Add a hash method to *cls*. 

1680 """ 

1681 script, globs = _make_hash_script( 

1682 cls, attrs, frozen=False, cache_hash=False 

1683 ) 

1684 _compile_and_eval( 

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

1686 ) 

1687 cls.__hash__ = globs["__hash__"] 

1688 return cls 

1689 

1690 

1691def __ne__(self, other): 

1692 """ 

1693 Check equality and either forward a NotImplemented or 

1694 return the result negated. 

1695 """ 

1696 result = self.__eq__(other) 

1697 if result is NotImplemented: 

1698 return NotImplemented 

1699 

1700 return not result 

1701 

1702 

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

1704 """ 

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

1706 """ 

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

1708 

1709 lines = [ 

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

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

1712 " return NotImplemented", 

1713 ] 

1714 

1715 globs = {} 

1716 if attrs: 

1717 lines.append(" return (") 

1718 for a in attrs: 

1719 if a.eq_key: 

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

1721 # Add the key function to the global namespace 

1722 # of the evaluated function. 

1723 globs[cmp_name] = a.eq_key 

1724 lines.append( 

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

1726 ) 

1727 else: 

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

1729 if a is not attrs[-1]: 

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

1731 lines.append(" )") 

1732 else: 

1733 lines.append(" return True") 

1734 

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

1736 

1737 return script, globs 

1738 

1739 

1740def _make_order(cls, attrs): 

1741 """ 

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

1743 """ 

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

1745 

1746 def attrs_to_tuple(obj): 

1747 """ 

1748 Save us some typing. 

1749 """ 

1750 return tuple( 

1751 key(value) if key else value 

1752 for value, key in ( 

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

1754 ) 

1755 ) 

1756 

1757 def __lt__(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 def __le__(self, other): 

1767 """ 

1768 Automatically created by attrs. 

1769 """ 

1770 if other.__class__ is self.__class__: 

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

1772 

1773 return NotImplemented 

1774 

1775 def __gt__(self, other): 

1776 """ 

1777 Automatically created by attrs. 

1778 """ 

1779 if other.__class__ is self.__class__: 

1780 return attrs_to_tuple(self) > attrs_to_tuple(other) 

1781 

1782 return NotImplemented 

1783 

1784 def __ge__(self, other): 

1785 """ 

1786 Automatically created by attrs. 

1787 """ 

1788 if other.__class__ is self.__class__: 

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

1790 

1791 return NotImplemented 

1792 

1793 return __lt__, __le__, __gt__, __ge__ 

1794 

1795 

1796def _add_eq(cls, attrs=None): 

1797 """ 

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

1799 """ 

1800 if attrs is None: 

1801 attrs = cls.__attrs_attrs__ 

1802 

1803 script, globs = _make_eq_script(attrs) 

1804 _compile_and_eval( 

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

1806 ) 

1807 cls.__eq__ = globs["__eq__"] 

1808 cls.__ne__ = __ne__ 

1809 

1810 return cls 

1811 

1812 

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

1814 """ 

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

1816 """ 

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

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

1819 # callable. 

1820 attr_names_with_reprs = tuple( 

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

1822 for a in attrs 

1823 if a.repr is not False 

1824 ) 

1825 globs = { 

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

1827 } 

1828 globs["_compat"] = _compat 

1829 globs["AttributeError"] = AttributeError 

1830 globs["NOTHING"] = NOTHING 

1831 attribute_fragments = [] 

1832 for name, r, i in attr_names_with_reprs: 

1833 accessor = ( 

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

1835 ) 

1836 fragment = ( 

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

1838 if r == repr 

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

1840 ) 

1841 attribute_fragments.append(fragment) 

1842 repr_fragment = ", ".join(attribute_fragments) 

1843 

1844 if ns is None: 

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

1846 else: 

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

1848 

1849 lines = [ 

1850 "def __repr__(self):", 

1851 " try:", 

1852 " already_repring = _compat.repr_context.already_repring", 

1853 " except AttributeError:", 

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

1855 " _compat.repr_context.already_repring = already_repring", 

1856 " else:", 

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

1858 " return '...'", 

1859 " else:", 

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

1861 " try:", 

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

1863 " finally:", 

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

1865 ] 

1866 

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

1868 

1869 

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

1871 """ 

1872 Add a repr method to *cls*. 

1873 """ 

1874 if attrs is None: 

1875 attrs = cls.__attrs_attrs__ 

1876 

1877 script, globs = _make_repr_script(attrs, ns) 

1878 _compile_and_eval( 

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

1880 ) 

1881 cls.__repr__ = globs["__repr__"] 

1882 return cls 

1883 

1884 

1885def fields(cls): 

1886 """ 

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

1888 

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

1890 examples). 

1891 

1892 Args: 

1893 cls (type): Class to introspect. 

1894 

1895 Raises: 

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

1897 

1898 attrs.exceptions.NotAnAttrsClassError: 

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

1900 

1901 Returns: 

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

1903 

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

1905 by name. 

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

1907 """ 

1908 generic_base = get_generic_base(cls) 

1909 

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

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

1912 raise TypeError(msg) 

1913 

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

1915 

1916 if attrs is None: 

1917 if generic_base is not None: 

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

1919 if attrs is not None: 

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

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

1922 # efficient. 

1923 cls.__attrs_attrs__ = attrs 

1924 return attrs 

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

1926 raise NotAnAttrsClassError(msg) 

1927 

1928 return attrs 

1929 

1930 

1931def fields_dict(cls): 

1932 """ 

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

1934 are the attribute names. 

1935 

1936 Args: 

1937 cls (type): Class to introspect. 

1938 

1939 Raises: 

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

1941 

1942 attrs.exceptions.NotAnAttrsClassError: 

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

1944 

1945 Returns: 

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

1947 

1948 .. versionadded:: 18.1.0 

1949 """ 

1950 if not isinstance(cls, type): 

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

1952 raise TypeError(msg) 

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

1954 if attrs is None: 

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

1956 raise NotAnAttrsClassError(msg) 

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

1958 

1959 

1960def validate(inst): 

1961 """ 

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

1963 

1964 Leaves all exceptions through. 

1965 

1966 Args: 

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

1968 """ 

1969 if _config._run_validators is False: 

1970 return 

1971 

1972 for a in fields(inst.__class__): 

1973 v = a.validator 

1974 if v is not None: 

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

1976 

1977 

1978def _is_slot_attr(a_name, base_attr_map): 

1979 """ 

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

1981 """ 

1982 cls = base_attr_map.get(a_name) 

1983 return cls and "__slots__" in cls.__dict__ 

1984 

1985 

1986def _make_init_script( 

1987 cls, 

1988 attrs, 

1989 pre_init, 

1990 pre_init_has_args, 

1991 post_init, 

1992 frozen, 

1993 slots, 

1994 cache_hash, 

1995 base_attr_map, 

1996 is_exc, 

1997 cls_on_setattr, 

1998 attrs_init, 

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

2000 has_cls_on_setattr = ( 

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

2002 ) 

2003 

2004 if frozen and has_cls_on_setattr: 

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

2006 raise ValueError(msg) 

2007 

2008 needs_cached_setattr = cache_hash or frozen 

2009 filtered_attrs = [] 

2010 attr_dict = {} 

2011 for a in attrs: 

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

2013 continue 

2014 

2015 filtered_attrs.append(a) 

2016 attr_dict[a.name] = a 

2017 

2018 if a.on_setattr is not None: 

2019 if frozen is True: 

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

2021 raise ValueError(msg) 

2022 

2023 needs_cached_setattr = True 

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

2025 needs_cached_setattr = True 

2026 

2027 script, globs, annotations = _attrs_to_init_script( 

2028 filtered_attrs, 

2029 frozen, 

2030 slots, 

2031 pre_init, 

2032 pre_init_has_args, 

2033 post_init, 

2034 cache_hash, 

2035 base_attr_map, 

2036 is_exc, 

2037 needs_cached_setattr, 

2038 has_cls_on_setattr, 

2039 "__attrs_init__" if attrs_init else "__init__", 

2040 ) 

2041 if cls.__module__ in sys.modules: 

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

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

2044 

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

2046 

2047 if needs_cached_setattr: 

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

2049 # setattr hooks. 

2050 globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__ 

2051 

2052 return script, globs, annotations 

2053 

2054 

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

2056 """ 

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

2058 """ 

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

2060 

2061 

2062def _setattr_with_converter( 

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

2064) -> str: 

2065 """ 

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

2067 its converter first. 

2068 """ 

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

2070 

2071 

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

2073 """ 

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

2075 relegate to _setattr. 

2076 """ 

2077 if has_on_setattr: 

2078 return _setattr(attr_name, value, True) 

2079 

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

2081 

2082 

2083def _assign_with_converter( 

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

2085) -> str: 

2086 """ 

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

2088 conversion. Otherwise relegate to _setattr_with_converter. 

2089 """ 

2090 if has_on_setattr: 

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

2092 

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

2094 

2095 

2096def _determine_setters( 

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

2098): 

2099 """ 

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

2101 and/or slotted. 

2102 """ 

2103 if frozen is True: 

2104 if slots is True: 

2105 return (), _setattr, _setattr_with_converter 

2106 

2107 # Dict frozen classes assign directly to __dict__. 

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

2109 # class. 

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

2111 

2112 def fmt_setter( 

2113 attr_name: str, value_var: str, has_on_setattr: bool 

2114 ) -> str: 

2115 if _is_slot_attr(attr_name, base_attr_map): 

2116 return _setattr(attr_name, value_var, has_on_setattr) 

2117 

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

2119 

2120 def fmt_setter_with_converter( 

2121 attr_name: str, 

2122 value_var: str, 

2123 has_on_setattr: bool, 

2124 converter: Converter, 

2125 ) -> str: 

2126 if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): 

2127 return _setattr_with_converter( 

2128 attr_name, value_var, has_on_setattr, converter 

2129 ) 

2130 

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

2132 

2133 return ( 

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

2135 fmt_setter, 

2136 fmt_setter_with_converter, 

2137 ) 

2138 

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

2140 return (), _assign, _assign_with_converter 

2141 

2142 

2143def _attrs_to_init_script( 

2144 attrs: list[Attribute], 

2145 is_frozen: bool, 

2146 is_slotted: bool, 

2147 call_pre_init: bool, 

2148 pre_init_has_args: bool, 

2149 call_post_init: bool, 

2150 does_cache_hash: bool, 

2151 base_attr_map: dict[str, type], 

2152 is_exc: bool, 

2153 needs_cached_setattr: bool, 

2154 has_cls_on_setattr: bool, 

2155 method_name: str, 

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

2157 """ 

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

2159 annotations for the initializer. 

2160 

2161 The globals are required by the generated script. 

2162 """ 

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

2164 

2165 if needs_cached_setattr: 

2166 lines.append( 

2167 # Circumvent the __setattr__ descriptor to save one lookup per 

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

2169 # does_cache_hash is True. 

2170 "_setattr = _cached_setattr_get(self)" 

2171 ) 

2172 

2173 extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters( 

2174 is_frozen, is_slotted, base_attr_map 

2175 ) 

2176 lines.extend(extra_lines) 

2177 

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

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

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

2181 attrs_to_validate = [] 

2182 

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

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

2185 names_for_globals = {} 

2186 annotations = {"return": None} 

2187 

2188 for a in attrs: 

2189 if a.validator: 

2190 attrs_to_validate.append(a) 

2191 

2192 attr_name = a.name 

2193 has_on_setattr = a.on_setattr is not None or ( 

2194 a.on_setattr is not setters.NO_OP and has_cls_on_setattr 

2195 ) 

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

2197 # explicitly provided 

2198 arg_name = a.alias 

2199 

2200 has_factory = isinstance(a.default, Factory) 

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

2202 

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

2204 converter = Converter(a.converter) 

2205 else: 

2206 converter = a.converter 

2207 

2208 if a.init is False: 

2209 if has_factory: 

2210 init_factory_name = _INIT_FACTORY_PAT % (a.name,) 

2211 if converter is not None: 

2212 lines.append( 

2213 fmt_setter_with_converter( 

2214 attr_name, 

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

2216 has_on_setattr, 

2217 converter, 

2218 ) 

2219 ) 

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

2221 converter.converter 

2222 ) 

2223 else: 

2224 lines.append( 

2225 fmt_setter( 

2226 attr_name, 

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

2228 has_on_setattr, 

2229 ) 

2230 ) 

2231 names_for_globals[init_factory_name] = a.default.factory 

2232 elif converter is not None: 

2233 lines.append( 

2234 fmt_setter_with_converter( 

2235 attr_name, 

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

2237 has_on_setattr, 

2238 converter, 

2239 ) 

2240 ) 

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

2242 converter.converter 

2243 ) 

2244 else: 

2245 lines.append( 

2246 fmt_setter( 

2247 attr_name, 

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

2249 has_on_setattr, 

2250 ) 

2251 ) 

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

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

2254 if a.kw_only: 

2255 kw_only_args.append(arg) 

2256 else: 

2257 args.append(arg) 

2258 pre_init_args.append(arg_name) 

2259 

2260 if converter is not None: 

2261 lines.append( 

2262 fmt_setter_with_converter( 

2263 attr_name, arg_name, has_on_setattr, converter 

2264 ) 

2265 ) 

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

2267 converter.converter 

2268 ) 

2269 else: 

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

2271 

2272 elif has_factory: 

2273 arg = f"{arg_name}=NOTHING" 

2274 if a.kw_only: 

2275 kw_only_args.append(arg) 

2276 else: 

2277 args.append(arg) 

2278 pre_init_args.append(arg_name) 

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

2280 

2281 init_factory_name = _INIT_FACTORY_PAT % (a.name,) 

2282 if converter is not None: 

2283 lines.append( 

2284 " " 

2285 + fmt_setter_with_converter( 

2286 attr_name, arg_name, has_on_setattr, converter 

2287 ) 

2288 ) 

2289 lines.append("else:") 

2290 lines.append( 

2291 " " 

2292 + fmt_setter_with_converter( 

2293 attr_name, 

2294 init_factory_name + "(" + maybe_self + ")", 

2295 has_on_setattr, 

2296 converter, 

2297 ) 

2298 ) 

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

2300 converter.converter 

2301 ) 

2302 else: 

2303 lines.append( 

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

2305 ) 

2306 lines.append("else:") 

2307 lines.append( 

2308 " " 

2309 + fmt_setter( 

2310 attr_name, 

2311 init_factory_name + "(" + maybe_self + ")", 

2312 has_on_setattr, 

2313 ) 

2314 ) 

2315 names_for_globals[init_factory_name] = a.default.factory 

2316 else: 

2317 if a.kw_only: 

2318 kw_only_args.append(arg_name) 

2319 else: 

2320 args.append(arg_name) 

2321 pre_init_args.append(arg_name) 

2322 

2323 if converter is not None: 

2324 lines.append( 

2325 fmt_setter_with_converter( 

2326 attr_name, arg_name, has_on_setattr, converter 

2327 ) 

2328 ) 

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

2330 converter.converter 

2331 ) 

2332 else: 

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

2334 

2335 if a.init is True: 

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

2337 annotations[arg_name] = a.type 

2338 elif converter is not None and converter._first_param_type: 

2339 # Use the type from the converter if present. 

2340 annotations[arg_name] = converter._first_param_type 

2341 

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

2343 names_for_globals["_config"] = _config 

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

2345 for a in attrs_to_validate: 

2346 val_name = "__attr_validator_" + a.name 

2347 attr_name = "__attr_" + a.name 

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

2349 names_for_globals[val_name] = a.validator 

2350 names_for_globals[attr_name] = a 

2351 

2352 if call_post_init: 

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

2354 

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

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

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

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

2359 # would result in silent bugs. 

2360 if does_cache_hash: 

2361 if is_frozen: 

2362 if is_slotted: 

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

2364 else: 

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

2366 else: 

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

2368 lines.append(init_hash_cache) 

2369 

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

2371 # initialization. 

2372 if is_exc: 

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

2374 

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

2376 

2377 args = ", ".join(args) 

2378 pre_init_args = ", ".join(pre_init_args) 

2379 if kw_only_args: 

2380 # leading comma & kw_only args 

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

2382 pre_init_kw_only_args = ", ".join( 

2383 [ 

2384 f"{kw_arg_name}={kw_arg_name}" 

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

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

2387 ] 

2388 ) 

2389 pre_init_args += ", " if pre_init_args else "" 

2390 pre_init_args += pre_init_kw_only_args 

2391 

2392 if call_pre_init and pre_init_has_args: 

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

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

2395 

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

2397 NL = "\n " 

2398 return ( 

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

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

2401""", 

2402 names_for_globals, 

2403 annotations, 

2404 ) 

2405 

2406 

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

2408 """ 

2409 The default __init__ parameter name for a field. 

2410 

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

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

2413 """ 

2414 

2415 return name.lstrip("_") 

2416 

2417 

2418class Attribute: 

2419 """ 

2420 *Read-only* representation of an attribute. 

2421 

2422 .. warning:: 

2423 

2424 You should never instantiate this class yourself. 

2425 

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

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

2428 

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

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

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

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

2433 from a base class. 

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

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

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

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

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

2439 

2440 Instances of this class are frequently used for introspection purposes 

2441 like: 

2442 

2443 - `fields` returns a tuple of them. 

2444 - Validators get them passed as the first argument. 

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

2446 them. 

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

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

2449 

2450 

2451 .. versionadded:: 20.1.0 *inherited* 

2452 .. versionadded:: 20.1.0 *on_setattr* 

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

2454 equality checks and hashing anymore. 

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

2456 .. versionadded:: 22.2.0 *alias* 

2457 

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

2459 """ 

2460 

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

2462 # instantiation. 

2463 __slots__ = ( # noqa: RUF023 

2464 "name", 

2465 "default", 

2466 "validator", 

2467 "repr", 

2468 "eq", 

2469 "eq_key", 

2470 "order", 

2471 "order_key", 

2472 "hash", 

2473 "init", 

2474 "metadata", 

2475 "type", 

2476 "converter", 

2477 "kw_only", 

2478 "inherited", 

2479 "on_setattr", 

2480 "alias", 

2481 ) 

2482 

2483 def __init__( 

2484 self, 

2485 name, 

2486 default, 

2487 validator, 

2488 repr, 

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

2490 hash, 

2491 init, 

2492 inherited, 

2493 metadata=None, 

2494 type=None, 

2495 converter=None, 

2496 kw_only=False, 

2497 eq=None, 

2498 eq_key=None, 

2499 order=None, 

2500 order_key=None, 

2501 on_setattr=None, 

2502 alias=None, 

2503 ): 

2504 eq, eq_key, order, order_key = _determine_attrib_eq_order( 

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

2506 ) 

2507 

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

2509 bound_setattr = _OBJ_SETATTR.__get__(self) 

2510 

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

2512 # themselves. 

2513 bound_setattr("name", name) 

2514 bound_setattr("default", default) 

2515 bound_setattr("validator", validator) 

2516 bound_setattr("repr", repr) 

2517 bound_setattr("eq", eq) 

2518 bound_setattr("eq_key", eq_key) 

2519 bound_setattr("order", order) 

2520 bound_setattr("order_key", order_key) 

2521 bound_setattr("hash", hash) 

2522 bound_setattr("init", init) 

2523 bound_setattr("converter", converter) 

2524 bound_setattr( 

2525 "metadata", 

2526 ( 

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

2528 if metadata 

2529 else _EMPTY_METADATA_SINGLETON 

2530 ), 

2531 ) 

2532 bound_setattr("type", type) 

2533 bound_setattr("kw_only", kw_only) 

2534 bound_setattr("inherited", inherited) 

2535 bound_setattr("on_setattr", on_setattr) 

2536 bound_setattr("alias", alias) 

2537 

2538 def __setattr__(self, name, value): 

2539 raise FrozenInstanceError 

2540 

2541 @classmethod 

2542 def from_counting_attr( 

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

2544 ): 

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

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

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

2548 if type is None: 

2549 type = ca.type 

2550 elif ca.type is not None: 

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

2552 raise ValueError(msg) 

2553 return cls( 

2554 name, 

2555 ca._default, 

2556 ca._validator, 

2557 ca.repr, 

2558 None, 

2559 ca.hash, 

2560 ca.init, 

2561 False, 

2562 ca.metadata, 

2563 type, 

2564 ca.converter, 

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

2566 ca.eq, 

2567 ca.eq_key, 

2568 ca.order, 

2569 ca.order_key, 

2570 ca.on_setattr, 

2571 ca.alias, 

2572 ) 

2573 

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

2575 def evolve(self, **changes): 

2576 """ 

2577 Copy *self* and apply *changes*. 

2578 

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

2580 with :class:`attrs.Attribute`. 

2581 

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

2583 

2584 .. versionadded:: 20.3.0 

2585 """ 

2586 new = copy.copy(self) 

2587 

2588 new._setattrs(changes.items()) 

2589 

2590 return new 

2591 

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

2593 def __getstate__(self): 

2594 """ 

2595 Play nice with pickle. 

2596 """ 

2597 return tuple( 

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

2599 for name in self.__slots__ 

2600 ) 

2601 

2602 def __setstate__(self, state): 

2603 """ 

2604 Play nice with pickle. 

2605 """ 

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

2607 

2608 def _setattrs(self, name_values_pairs): 

2609 bound_setattr = _OBJ_SETATTR.__get__(self) 

2610 for name, value in name_values_pairs: 

2611 if name != "metadata": 

2612 bound_setattr(name, value) 

2613 else: 

2614 bound_setattr( 

2615 name, 

2616 ( 

2617 types.MappingProxyType(dict(value)) 

2618 if value 

2619 else _EMPTY_METADATA_SINGLETON 

2620 ), 

2621 ) 

2622 

2623 

2624_a = [ 

2625 Attribute( 

2626 name=name, 

2627 default=NOTHING, 

2628 validator=None, 

2629 repr=True, 

2630 cmp=None, 

2631 eq=True, 

2632 order=False, 

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

2634 init=True, 

2635 inherited=False, 

2636 alias=_default_init_alias_for(name), 

2637 ) 

2638 for name in Attribute.__slots__ 

2639] 

2640 

2641Attribute = _add_hash( 

2642 _add_eq( 

2643 _add_repr(Attribute, attrs=_a), 

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

2645 ), 

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

2647) 

2648 

2649 

2650class _CountingAttr: 

2651 """ 

2652 Intermediate representation of attributes that uses a counter to preserve 

2653 the order in which the attributes have been defined. 

2654 

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

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

2657 """ 

2658 

2659 __slots__ = ( 

2660 "_default", 

2661 "_validator", 

2662 "alias", 

2663 "converter", 

2664 "counter", 

2665 "eq", 

2666 "eq_key", 

2667 "hash", 

2668 "init", 

2669 "kw_only", 

2670 "metadata", 

2671 "on_setattr", 

2672 "order", 

2673 "order_key", 

2674 "repr", 

2675 "type", 

2676 ) 

2677 __attrs_attrs__ = ( 

2678 *tuple( 

2679 Attribute( 

2680 name=name, 

2681 alias=_default_init_alias_for(name), 

2682 default=NOTHING, 

2683 validator=None, 

2684 repr=True, 

2685 cmp=None, 

2686 hash=True, 

2687 init=True, 

2688 kw_only=False, 

2689 eq=True, 

2690 eq_key=None, 

2691 order=False, 

2692 order_key=None, 

2693 inherited=False, 

2694 on_setattr=None, 

2695 ) 

2696 for name in ( 

2697 "counter", 

2698 "_default", 

2699 "repr", 

2700 "eq", 

2701 "order", 

2702 "hash", 

2703 "init", 

2704 "on_setattr", 

2705 "alias", 

2706 ) 

2707 ), 

2708 Attribute( 

2709 name="metadata", 

2710 alias="metadata", 

2711 default=None, 

2712 validator=None, 

2713 repr=True, 

2714 cmp=None, 

2715 hash=False, 

2716 init=True, 

2717 kw_only=False, 

2718 eq=True, 

2719 eq_key=None, 

2720 order=False, 

2721 order_key=None, 

2722 inherited=False, 

2723 on_setattr=None, 

2724 ), 

2725 ) 

2726 cls_counter = 0 

2727 

2728 def __init__( 

2729 self, 

2730 default, 

2731 validator, 

2732 repr, 

2733 cmp, 

2734 hash, 

2735 init, 

2736 converter, 

2737 metadata, 

2738 type, 

2739 kw_only, 

2740 eq, 

2741 eq_key, 

2742 order, 

2743 order_key, 

2744 on_setattr, 

2745 alias, 

2746 ): 

2747 _CountingAttr.cls_counter += 1 

2748 self.counter = _CountingAttr.cls_counter 

2749 self._default = default 

2750 self._validator = validator 

2751 self.converter = converter 

2752 self.repr = repr 

2753 self.eq = eq 

2754 self.eq_key = eq_key 

2755 self.order = order 

2756 self.order_key = order_key 

2757 self.hash = hash 

2758 self.init = init 

2759 self.metadata = metadata 

2760 self.type = type 

2761 self.kw_only = kw_only 

2762 self.on_setattr = on_setattr 

2763 self.alias = alias 

2764 

2765 def validator(self, meth): 

2766 """ 

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

2768 

2769 Returns *meth* unchanged. 

2770 

2771 .. versionadded:: 17.1.0 

2772 """ 

2773 if self._validator is None: 

2774 self._validator = meth 

2775 else: 

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

2777 return meth 

2778 

2779 def default(self, meth): 

2780 """ 

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

2782 

2783 Returns *meth* unchanged. 

2784 

2785 Raises: 

2786 DefaultAlreadySetError: If default has been set before. 

2787 

2788 .. versionadded:: 17.1.0 

2789 """ 

2790 if self._default is not NOTHING: 

2791 raise DefaultAlreadySetError 

2792 

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

2794 

2795 return meth 

2796 

2797 

2798_CountingAttr = _add_eq(_add_repr(_CountingAttr)) 

2799 

2800 

2801class ClassProps: 

2802 """ 

2803 Effective class properties as derived from parameters to `attr.s()` or 

2804 `define()` decorators. 

2805 

2806 This is the same data structure that *attrs* uses internally to decide how 

2807 to construct the final class. 

2808 

2809 Warning: 

2810 

2811 This feature is currently **experimental** and is not covered by our 

2812 strict backwards-compatibility guarantees. 

2813 

2814 

2815 Attributes: 

2816 is_exception (bool): 

2817 Whether the class is treated as an exception class. 

2818 

2819 is_slotted (bool): 

2820 Whether the class is `slotted <slotted classes>`. 

2821 

2822 has_weakref_slot (bool): 

2823 Whether the class has a slot for weak references. 

2824 

2825 is_frozen (bool): 

2826 Whether the class is frozen. 

2827 

2828 kw_only (KeywordOnly): 

2829 Whether / how the class enforces keyword-only arguments on the 

2830 ``__init__`` method. 

2831 

2832 collected_fields_by_mro (bool): 

2833 Whether the class fields were collected by method resolution order. 

2834 That is, correctly but unlike `dataclasses`. 

2835 

2836 added_init (bool): 

2837 Whether the class has an *attrs*-generated ``__init__`` method. 

2838 

2839 added_repr (bool): 

2840 Whether the class has an *attrs*-generated ``__repr__`` method. 

2841 

2842 added_eq (bool): 

2843 Whether the class has *attrs*-generated equality methods. 

2844 

2845 added_ordering (bool): 

2846 Whether the class has *attrs*-generated ordering methods. 

2847 

2848 hashability (Hashability): How `hashable <hashing>` the class is. 

2849 

2850 added_match_args (bool): 

2851 Whether the class supports positional `match <match>` over its 

2852 fields. 

2853 

2854 added_str (bool): 

2855 Whether the class has an *attrs*-generated ``__str__`` method. 

2856 

2857 added_pickling (bool): 

2858 Whether the class has *attrs*-generated ``__getstate__`` and 

2859 ``__setstate__`` methods for `pickle`. 

2860 

2861 on_setattr_hook (Callable[[Any, Attribute[Any], Any], Any] | None): 

2862 The class's ``__setattr__`` hook. 

2863 

2864 field_transformer (Callable[[Attribute[Any]], Attribute[Any]] | None): 

2865 The class's `field transformers <transform-fields>`. 

2866 

2867 .. versionadded:: 25.4.0 

2868 """ 

2869 

2870 class Hashability(enum.Enum): 

2871 """ 

2872 The hashability of a class. 

2873 

2874 .. versionadded:: 25.4.0 

2875 """ 

2876 

2877 HASHABLE = "hashable" 

2878 """Write a ``__hash__``.""" 

2879 HASHABLE_CACHED = "hashable_cache" 

2880 """Write a ``__hash__`` and cache the hash.""" 

2881 UNHASHABLE = "unhashable" 

2882 """Set ``__hash__`` to ``None``.""" 

2883 LEAVE_ALONE = "leave_alone" 

2884 """Don't touch ``__hash__``.""" 

2885 

2886 class KeywordOnly(enum.Enum): 

2887 """ 

2888 How attributes should be treated regarding keyword-only parameters. 

2889 

2890 .. versionadded:: 25.4.0 

2891 """ 

2892 

2893 NO = "no" 

2894 """Attributes are not keyword-only.""" 

2895 YES = "yes" 

2896 """Attributes in current class without kw_only=False are keyword-only.""" 

2897 FORCE = "force" 

2898 """All attributes are keyword-only.""" 

2899 

2900 __slots__ = ( # noqa: RUF023 -- order matters for __init__ 

2901 "is_exception", 

2902 "is_slotted", 

2903 "has_weakref_slot", 

2904 "is_frozen", 

2905 "kw_only", 

2906 "collected_fields_by_mro", 

2907 "added_init", 

2908 "added_repr", 

2909 "added_eq", 

2910 "added_ordering", 

2911 "hashability", 

2912 "added_match_args", 

2913 "added_str", 

2914 "added_pickling", 

2915 "on_setattr_hook", 

2916 "field_transformer", 

2917 ) 

2918 

2919 def __init__( 

2920 self, 

2921 is_exception, 

2922 is_slotted, 

2923 has_weakref_slot, 

2924 is_frozen, 

2925 kw_only, 

2926 collected_fields_by_mro, 

2927 added_init, 

2928 added_repr, 

2929 added_eq, 

2930 added_ordering, 

2931 hashability, 

2932 added_match_args, 

2933 added_str, 

2934 added_pickling, 

2935 on_setattr_hook, 

2936 field_transformer, 

2937 ): 

2938 self.is_exception = is_exception 

2939 self.is_slotted = is_slotted 

2940 self.has_weakref_slot = has_weakref_slot 

2941 self.is_frozen = is_frozen 

2942 self.kw_only = kw_only 

2943 self.collected_fields_by_mro = collected_fields_by_mro 

2944 self.added_init = added_init 

2945 self.added_repr = added_repr 

2946 self.added_eq = added_eq 

2947 self.added_ordering = added_ordering 

2948 self.hashability = hashability 

2949 self.added_match_args = added_match_args 

2950 self.added_str = added_str 

2951 self.added_pickling = added_pickling 

2952 self.on_setattr_hook = on_setattr_hook 

2953 self.field_transformer = field_transformer 

2954 

2955 @property 

2956 def is_hashable(self): 

2957 return ( 

2958 self.hashability is ClassProps.Hashability.HASHABLE 

2959 or self.hashability is ClassProps.Hashability.HASHABLE_CACHED 

2960 ) 

2961 

2962 

2963_cas = [ 

2964 Attribute( 

2965 name=name, 

2966 default=NOTHING, 

2967 validator=None, 

2968 repr=True, 

2969 cmp=None, 

2970 eq=True, 

2971 order=False, 

2972 hash=True, 

2973 init=True, 

2974 inherited=False, 

2975 alias=_default_init_alias_for(name), 

2976 ) 

2977 for name in ClassProps.__slots__ 

2978] 

2979 

2980ClassProps = _add_eq(_add_repr(ClassProps, attrs=_cas), attrs=_cas) 

2981 

2982 

2983class Factory: 

2984 """ 

2985 Stores a factory callable. 

2986 

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

2988 generate a new value. 

2989 

2990 Args: 

2991 factory (typing.Callable): 

2992 A callable that takes either none or exactly one mandatory 

2993 positional argument depending on *takes_self*. 

2994 

2995 takes_self (bool): 

2996 Pass the partially initialized instance that is being initialized 

2997 as a positional argument. 

2998 

2999 .. versionadded:: 17.1.0 *takes_self* 

3000 """ 

3001 

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

3003 

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

3005 self.factory = factory 

3006 self.takes_self = takes_self 

3007 

3008 def __getstate__(self): 

3009 """ 

3010 Play nice with pickle. 

3011 """ 

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

3013 

3014 def __setstate__(self, state): 

3015 """ 

3016 Play nice with pickle. 

3017 """ 

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

3019 setattr(self, name, value) 

3020 

3021 

3022_f = [ 

3023 Attribute( 

3024 name=name, 

3025 default=NOTHING, 

3026 validator=None, 

3027 repr=True, 

3028 cmp=None, 

3029 eq=True, 

3030 order=False, 

3031 hash=True, 

3032 init=True, 

3033 inherited=False, 

3034 ) 

3035 for name in Factory.__slots__ 

3036] 

3037 

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

3039 

3040 

3041class Converter: 

3042 """ 

3043 Stores a converter callable. 

3044 

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

3046 arguments are passed in the order they are documented. 

3047 

3048 Args: 

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

3050 

3051 takes_self (bool): 

3052 Pass the partially initialized instance that is being initialized 

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

3054 

3055 takes_field (bool): 

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

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

3058 

3059 .. versionadded:: 24.1.0 

3060 """ 

3061 

3062 __slots__ = ( 

3063 "__call__", 

3064 "_first_param_type", 

3065 "_global_name", 

3066 "converter", 

3067 "takes_field", 

3068 "takes_self", 

3069 ) 

3070 

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

3072 self.converter = converter 

3073 self.takes_self = takes_self 

3074 self.takes_field = takes_field 

3075 

3076 ex = _AnnotationExtractor(converter) 

3077 self._first_param_type = ex.get_first_param_type() 

3078 

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

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

3081 elif self.takes_self and not self.takes_field: 

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

3083 value, instance 

3084 ) 

3085 elif not self.takes_self and self.takes_field: 

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

3087 value, field 

3088 ) 

3089 else: 

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

3091 value, instance, field 

3092 ) 

3093 

3094 rt = ex.get_return_type() 

3095 if rt is not None: 

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

3097 

3098 @staticmethod 

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

3100 """ 

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

3102 would have. 

3103 """ 

3104 return f"__attr_converter_{attr_name}" 

3105 

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

3107 """ 

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

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

3110 `self.takes_self` and `self.takes_field`. 

3111 """ 

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

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

3114 

3115 if self.takes_self and self.takes_field: 

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

3117 

3118 if self.takes_self: 

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

3120 

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

3122 

3123 def __getstate__(self): 

3124 """ 

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

3126 computed when loading. 

3127 """ 

3128 return { 

3129 "converter": self.converter, 

3130 "takes_self": self.takes_self, 

3131 "takes_field": self.takes_field, 

3132 } 

3133 

3134 def __setstate__(self, state): 

3135 """ 

3136 Load instance from state. 

3137 """ 

3138 self.__init__(**state) 

3139 

3140 

3141_f = [ 

3142 Attribute( 

3143 name=name, 

3144 default=NOTHING, 

3145 validator=None, 

3146 repr=True, 

3147 cmp=None, 

3148 eq=True, 

3149 order=False, 

3150 hash=True, 

3151 init=True, 

3152 inherited=False, 

3153 ) 

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

3155] 

3156 

3157Converter = _add_hash( 

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

3159) 

3160 

3161 

3162def make_class( 

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

3164): 

3165 r""" 

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

3167 

3168 .. note:: 

3169 

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

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

3172 

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

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

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

3176 

3177 .. warning:: 

3178 

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

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

3181 you. 

3182 

3183 Args: 

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

3185 

3186 attrs (list | dict): 

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

3188 s / `attrs.field`\ s. 

3189 

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

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

3192 attributes is used. 

3193 

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

3195 

3196 class_body (dict): 

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

3198 

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

3200 

3201 Returns: 

3202 type: A new class with *attrs*. 

3203 

3204 .. versionadded:: 17.1.0 *bases* 

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

3206 .. versionchanged:: 23.2.0 *class_body* 

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

3208 """ 

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

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

3211 

3212 if isinstance(attrs, dict): 

3213 cls_dict = attrs 

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

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

3216 else: 

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

3218 raise TypeError(msg) 

3219 

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

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

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

3223 

3224 body = {} 

3225 if class_body is not None: 

3226 body.update(class_body) 

3227 if pre_init is not None: 

3228 body["__attrs_pre_init__"] = pre_init 

3229 if post_init is not None: 

3230 body["__attrs_post_init__"] = post_init 

3231 if user_init is not None: 

3232 body["__init__"] = user_init 

3233 

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

3235 

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

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

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

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

3240 with contextlib.suppress(AttributeError, ValueError): 

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

3242 "__name__", "__main__" 

3243 ) 

3244 

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

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

3247 ( 

3248 attributes_arguments["eq"], 

3249 attributes_arguments["order"], 

3250 ) = _determine_attrs_eq_order( 

3251 cmp, 

3252 attributes_arguments.get("eq"), 

3253 attributes_arguments.get("order"), 

3254 True, 

3255 ) 

3256 

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

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

3259 cls.__annotations__ = { 

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

3261 } 

3262 return cls 

3263 

3264 

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

3266# import into .validators / .converters. 

3267 

3268 

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

3270class _AndValidator: 

3271 """ 

3272 Compose many validators to a single one. 

3273 """ 

3274 

3275 _validators = attrib() 

3276 

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

3278 for v in self._validators: 

3279 v(inst, attr, value) 

3280 

3281 

3282def and_(*validators): 

3283 """ 

3284 A validator that composes multiple validators into one. 

3285 

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

3287 

3288 Args: 

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

3290 Arbitrary number of validators. 

3291 

3292 .. versionadded:: 17.1.0 

3293 """ 

3294 vals = [] 

3295 for validator in validators: 

3296 vals.extend( 

3297 validator._validators 

3298 if isinstance(validator, _AndValidator) 

3299 else [validator] 

3300 ) 

3301 

3302 return _AndValidator(tuple(vals)) 

3303 

3304 

3305def pipe(*converters): 

3306 """ 

3307 A converter that composes multiple converters into one. 

3308 

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

3310 *last* value. 

3311 

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

3313 have any. 

3314 

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

3316 Arbitrary number of converters. 

3317 

3318 .. versionadded:: 20.1.0 

3319 """ 

3320 

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

3322 

3323 if return_instance: 

3324 

3325 def pipe_converter(val, inst, field): 

3326 for c in converters: 

3327 val = ( 

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

3329 ) 

3330 

3331 return val 

3332 

3333 else: 

3334 

3335 def pipe_converter(val): 

3336 for c in converters: 

3337 val = c(val) 

3338 

3339 return val 

3340 

3341 if not converters: 

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

3343 A = TypeVar("A") 

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

3345 else: 

3346 # Get parameter type from first converter. 

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

3348 if t: 

3349 pipe_converter.__annotations__["val"] = t 

3350 

3351 last = converters[-1] 

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

3353 last = last.__call__ 

3354 

3355 # Get return type from last converter. 

3356 rt = _AnnotationExtractor(last).get_return_type() 

3357 if rt: 

3358 pipe_converter.__annotations__["return"] = rt 

3359 

3360 if return_instance: 

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

3362 return pipe_converter