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

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

1143 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 # Resolve default field alias before executing field_transformer, so that 

467 # the transformer receives fully populated Attribute objects with usable 

468 # alias values. 

469 for a in attrs: 

470 if not a.alias: 

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

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

473 _OBJ_SETATTR.__get__(a)("alias_is_default", True) 

474 

475 if field_transformer is not None: 

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

477 

478 # Check attr order after executing the field_transformer. 

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

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

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

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

483 had_default = False 

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

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

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

487 raise ValueError(msg) 

488 

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

490 had_default = True 

491 

492 # Resolve default field alias for any new attributes that the 

493 # field_transformer may have added without setting an alias. 

494 for a in attrs: 

495 if not a.alias: 

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

497 _OBJ_SETATTR.__get__(a)("alias_is_default", True) 

498 

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

500 # add or remove attributes! 

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

502 AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) 

503 

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

505 

506 

507def _make_cached_property_getattr(cached_properties, original_getattr, cls): 

508 lines = [ 

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

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

511 "def wrapper(_cls):", 

512 " __class__ = _cls", 

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

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

515 " if func is not None:", 

516 " result = func(self)", 

517 " _setter = _cached_setattr_get(self)", 

518 " _setter(item, result)", 

519 " return result", 

520 ] 

521 if original_getattr is not None: 

522 lines.append( 

523 " return original_getattr(self, item)", 

524 ) 

525 else: 

526 lines.extend( 

527 [ 

528 " try:", 

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

530 " except AttributeError:", 

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

532 " raise", 

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

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

535 " raise AttributeError(original_error)", 

536 ] 

537 ) 

538 

539 lines.extend( 

540 [ 

541 " return __getattr__", 

542 "__getattr__ = wrapper(_cls)", 

543 ] 

544 ) 

545 

546 unique_filename = _generate_unique_filename(cls, "getattr") 

547 

548 glob = { 

549 "cached_properties": cached_properties, 

550 "_cached_setattr_get": _OBJ_SETATTR.__get__, 

551 "original_getattr": original_getattr, 

552 } 

553 

554 return _linecache_and_compile( 

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

556 )["__getattr__"] 

557 

558 

559def _frozen_setattrs(self, name, value): 

560 """ 

561 Attached to frozen classes as __setattr__. 

562 """ 

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

564 "__cause__", 

565 "__context__", 

566 "__traceback__", 

567 "__suppress_context__", 

568 "__notes__", 

569 ): 

570 BaseException.__setattr__(self, name, value) 

571 return 

572 

573 raise FrozenInstanceError 

574 

575 

576def _frozen_delattrs(self, name): 

577 """ 

578 Attached to frozen classes as __delattr__. 

579 """ 

580 if isinstance(self, BaseException) and name == "__notes__": 

581 BaseException.__delattr__(self, name) 

582 return 

583 

584 raise FrozenInstanceError 

585 

586 

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

588 """ 

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

590 *changes* applied. 

591 

592 .. tip:: 

593 

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

595 

596 Args: 

597 

598 inst: 

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

600 as a positional argument. 

601 

602 changes: 

603 Keyword changes in the new copy. 

604 

605 Returns: 

606 A copy of inst with *changes* incorporated. 

607 

608 Raises: 

609 TypeError: 

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

611 

612 attrs.exceptions.NotAnAttrsClassError: 

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

614 

615 .. versionadded:: 17.1.0 

616 .. deprecated:: 23.1.0 

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

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

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

620 argument. 

621 .. versionchanged:: 24.1.0 

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

623 """ 

624 try: 

625 (inst,) = args 

626 except ValueError: 

627 msg = ( 

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

629 ) 

630 raise TypeError(msg) from None 

631 

632 cls = inst.__class__ 

633 attrs = fields(cls) 

634 for a in attrs: 

635 if not a.init: 

636 continue 

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

638 init_name = a.alias 

639 if init_name not in changes: 

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

641 

642 return cls(**changes) 

643 

644 

645class _ClassBuilder: 

646 """ 

647 Iteratively build *one* class. 

648 """ 

649 

650 __slots__ = ( 

651 "_add_method_dunders", 

652 "_attr_names", 

653 "_attrs", 

654 "_base_attr_map", 

655 "_base_names", 

656 "_cache_hash", 

657 "_cls", 

658 "_cls_dict", 

659 "_delete_attribs", 

660 "_frozen", 

661 "_has_custom_setattr", 

662 "_has_post_init", 

663 "_has_pre_init", 

664 "_is_exc", 

665 "_on_setattr", 

666 "_pre_init_has_args", 

667 "_repr_added", 

668 "_script_snippets", 

669 "_slots", 

670 "_weakref_slot", 

671 "_wrote_own_setattr", 

672 ) 

673 

674 def __init__( 

675 self, 

676 cls: type, 

677 these, 

678 auto_attribs: bool, 

679 props: ClassProps, 

680 has_custom_setattr: bool, 

681 ): 

682 attrs, base_attrs, base_map = _transform_attrs( 

683 cls, 

684 these, 

685 auto_attribs, 

686 props.kw_only, 

687 props.collected_fields_by_mro, 

688 props.field_transformer, 

689 ) 

690 

691 self._cls = cls 

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

693 self._attrs = attrs 

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

695 self._base_attr_map = base_map 

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

697 self._slots = props.is_slotted 

698 self._frozen = props.is_frozen 

699 self._weakref_slot = props.has_weakref_slot 

700 self._cache_hash = ( 

701 props.hashability is ClassProps.Hashability.HASHABLE_CACHED 

702 ) 

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

704 self._pre_init_has_args = False 

705 if self._has_pre_init: 

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

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

708 pre_init_func = cls.__attrs_pre_init__ 

709 pre_init_signature = inspect.signature(pre_init_func) 

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

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

712 self._delete_attribs = not bool(these) 

713 self._is_exc = props.is_exception 

714 self._on_setattr = props.on_setattr_hook 

715 

716 self._has_custom_setattr = has_custom_setattr 

717 self._wrote_own_setattr = False 

718 

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

720 self._cls_dict["__attrs_props__"] = props 

721 

722 if props.is_frozen: 

723 self._cls_dict["__setattr__"] = _frozen_setattrs 

724 self._cls_dict["__delattr__"] = _frozen_delattrs 

725 

726 self._wrote_own_setattr = True 

727 elif self._on_setattr in ( 

728 _DEFAULT_ON_SETATTR, 

729 setters.validate, 

730 setters.convert, 

731 ): 

732 has_validator = has_converter = False 

733 for a in attrs: 

734 if a.validator is not None: 

735 has_validator = True 

736 if a.converter is not None: 

737 has_converter = True 

738 

739 if has_validator and has_converter: 

740 break 

741 if ( 

742 ( 

743 self._on_setattr == _DEFAULT_ON_SETATTR 

744 and not (has_validator or has_converter) 

745 ) 

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

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

748 ): 

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

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

751 # no on_setattr. 

752 self._on_setattr = None 

753 

754 if props.added_pickling: 

755 ( 

756 self._cls_dict["__getstate__"], 

757 self._cls_dict["__setstate__"], 

758 ) = self._make_getstate_setstate() 

759 

760 # tuples of script, globs, hook 

761 self._script_snippets: list[ 

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

763 ] = [] 

764 self._repr_added = False 

765 

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

767 # exist. 

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

769 self._cls, "__qualname__" 

770 ): 

771 self._add_method_dunders = self._add_method_dunders_safe 

772 else: 

773 self._add_method_dunders = self._add_method_dunders_unsafe 

774 

775 def __repr__(self): 

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

777 

778 def _eval_snippets(self) -> None: 

779 """ 

780 Evaluate any registered snippets in one go. 

781 """ 

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

783 globs = {} 

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

785 globs.update(snippet_globs) 

786 

787 locs = _linecache_and_compile( 

788 script, 

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

790 globs, 

791 ) 

792 

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

794 hook(self._cls_dict, locs) 

795 

796 def build_class(self): 

797 """ 

798 Finalize class based on the accumulated configuration. 

799 

800 Builder cannot be used after calling this method. 

801 """ 

802 self._eval_snippets() 

803 if self._slots is True: 

804 cls = self._create_slots_class() 

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

806 else: 

807 cls = self._patch_original_class() 

808 if PY_3_10_PLUS: 

809 cls = abc.update_abstractmethods(cls) 

810 

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

812 # _has_own_attribute does NOT work properly for classmethods. 

813 if ( 

814 getattr(cls, "__attrs_init_subclass__", None) 

815 and "__attrs_init_subclass__" not in cls.__dict__ 

816 ): 

817 cls.__attrs_init_subclass__() 

818 

819 return cls 

820 

821 def _patch_original_class(self): 

822 """ 

823 Apply accumulated methods and return the class. 

824 """ 

825 cls = self._cls 

826 base_names = self._base_names 

827 

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

829 if self._delete_attribs: 

830 for name in self._attr_names: 

831 if ( 

832 name not in base_names 

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

834 ): 

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

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

837 # same name by using only a type annotation. 

838 with contextlib.suppress(AttributeError): 

839 delattr(cls, name) 

840 

841 # Attach our dunder methods. 

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

843 setattr(cls, name, value) 

844 

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

846 # reset it to object's. 

847 if not self._wrote_own_setattr and getattr( 

848 cls, "__attrs_own_setattr__", False 

849 ): 

850 cls.__attrs_own_setattr__ = False 

851 

852 if not self._has_custom_setattr: 

853 cls.__setattr__ = _OBJ_SETATTR 

854 

855 return cls 

856 

857 def _create_slots_class(self): 

858 """ 

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

860 """ 

861 cd = { 

862 k: v 

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

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

865 } 

866 

867 # 3.14.0rc2+ 

868 if hasattr(sys, "_clear_type_descriptors"): 

869 sys._clear_type_descriptors(self._cls) 

870 

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

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

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

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

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

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

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

878 # XXX: OK with us. 

879 if not self._wrote_own_setattr: 

880 cd["__attrs_own_setattr__"] = False 

881 

882 if not self._has_custom_setattr: 

883 for base_cls in self._cls.__bases__: 

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

885 cd["__setattr__"] = _OBJ_SETATTR 

886 break 

887 

888 # Traverse the MRO to collect existing slots 

889 # and check for an existing __weakref__. 

890 existing_slots = {} 

891 weakref_inherited = False 

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

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

894 weakref_inherited = True 

895 existing_slots.update( 

896 { 

897 name: getattr(base_cls, name) 

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

899 } 

900 ) 

901 

902 base_names = set(self._base_names) 

903 

904 names = self._attr_names 

905 if ( 

906 self._weakref_slot 

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

908 and "__weakref__" not in names 

909 and not weakref_inherited 

910 ): 

911 names += ("__weakref__",) 

912 

913 cached_properties = { 

914 name: cached_prop.func 

915 for name, cached_prop in cd.items() 

916 if isinstance(cached_prop, cached_property) 

917 } 

918 

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

920 # To know to update them. 

921 additional_closure_functions_to_update = [] 

922 if cached_properties: 

923 class_annotations = _get_annotations(self._cls) 

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

925 # Add cached properties to names for slotting. 

926 names += (name,) 

927 # Clear out function from class to avoid clashing. 

928 del cd[name] 

929 additional_closure_functions_to_update.append(func) 

930 annotation = inspect.signature(func).return_annotation 

931 if annotation is not inspect.Parameter.empty: 

932 class_annotations[name] = annotation 

933 

934 original_getattr = cd.get("__getattr__") 

935 if original_getattr is not None: 

936 additional_closure_functions_to_update.append(original_getattr) 

937 

938 cd["__getattr__"] = _make_cached_property_getattr( 

939 cached_properties, original_getattr, self._cls 

940 ) 

941 

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

943 # Setting __slots__ to inherited attributes wastes memory. 

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

945 

946 # There are slots for attributes from current class 

947 # that are defined in parent classes. 

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

949 # we collect them here and update the class dict 

950 reused_slots = { 

951 slot: slot_descriptor 

952 for slot, slot_descriptor in existing_slots.items() 

953 if slot in slot_names 

954 } 

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

956 cd.update(reused_slots) 

957 if self._cache_hash: 

958 slot_names.append(_HASH_CACHE_FIELD) 

959 

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

961 

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

963 

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

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

966 

967 # The following is a fix for 

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

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

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

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

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

973 for item in itertools.chain( 

974 cls.__dict__.values(), additional_closure_functions_to_update 

975 ): 

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

977 # Class- and staticmethods hide their functions inside. 

978 # These might need to be rewritten as well. 

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

980 elif isinstance(item, property): 

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

982 # There is no universal way for other descriptors. 

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

984 else: 

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

986 

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

988 continue 

989 for cell in closure_cells: 

990 try: 

991 match = cell.cell_contents is self._cls 

992 except ValueError: # noqa: PERF203 

993 # ValueError: Cell is empty 

994 pass 

995 else: 

996 if match: 

997 cell.cell_contents = cls 

998 return cls 

999 

1000 def add_repr(self, ns): 

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

1002 

1003 def _attach_repr(cls_dict, globs): 

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

1005 

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

1007 self._repr_added = True 

1008 return self 

1009 

1010 def add_str(self): 

1011 if not self._repr_added: 

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

1013 raise ValueError(msg) 

1014 

1015 def __str__(self): 

1016 return self.__repr__() 

1017 

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

1019 return self 

1020 

1021 def _make_getstate_setstate(self): 

1022 """ 

1023 Create custom __setstate__ and __getstate__ methods. 

1024 """ 

1025 # __weakref__ is not writable. 

1026 state_attr_names = tuple( 

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

1028 ) 

1029 

1030 def slots_getstate(self): 

1031 """ 

1032 Automatically created by attrs. 

1033 """ 

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

1035 

1036 hash_caching_enabled = self._cache_hash 

1037 

1038 def slots_setstate(self, state): 

1039 """ 

1040 Automatically created by attrs. 

1041 """ 

1042 __bound_setattr = _OBJ_SETATTR.__get__(self) 

1043 if isinstance(state, tuple): 

1044 # Backward compatibility with attrs instances pickled with 

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

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

1047 __bound_setattr(name, value) 

1048 else: 

1049 for name in state_attr_names: 

1050 if name in state: 

1051 __bound_setattr(name, state[name]) 

1052 

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

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

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

1056 # miss. 

1057 if hash_caching_enabled: 

1058 __bound_setattr(_HASH_CACHE_FIELD, None) 

1059 

1060 return slots_getstate, slots_setstate 

1061 

1062 def make_unhashable(self): 

1063 self._cls_dict["__hash__"] = None 

1064 return self 

1065 

1066 def add_hash(self): 

1067 script, globs = _make_hash_script( 

1068 self._cls, 

1069 self._attrs, 

1070 frozen=self._frozen, 

1071 cache_hash=self._cache_hash, 

1072 ) 

1073 

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

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

1076 

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

1078 

1079 return self 

1080 

1081 def add_init(self): 

1082 script, globs, annotations = _make_init_script( 

1083 self._cls, 

1084 self._attrs, 

1085 self._has_pre_init, 

1086 self._pre_init_has_args, 

1087 self._has_post_init, 

1088 self._frozen, 

1089 self._slots, 

1090 self._cache_hash, 

1091 self._base_attr_map, 

1092 self._is_exc, 

1093 self._on_setattr, 

1094 attrs_init=False, 

1095 ) 

1096 

1097 def _attach_init(cls_dict, globs): 

1098 init = globs["__init__"] 

1099 init.__annotations__ = annotations 

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

1101 

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

1103 

1104 return self 

1105 

1106 def add_replace(self): 

1107 self._cls_dict["__replace__"] = self._add_method_dunders(evolve) 

1108 return self 

1109 

1110 def add_match_args(self): 

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

1112 field.name 

1113 for field in self._attrs 

1114 if field.init and not field.kw_only 

1115 ) 

1116 

1117 def add_attrs_init(self): 

1118 script, globs, annotations = _make_init_script( 

1119 self._cls, 

1120 self._attrs, 

1121 self._has_pre_init, 

1122 self._pre_init_has_args, 

1123 self._has_post_init, 

1124 self._frozen, 

1125 self._slots, 

1126 self._cache_hash, 

1127 self._base_attr_map, 

1128 self._is_exc, 

1129 self._on_setattr, 

1130 attrs_init=True, 

1131 ) 

1132 

1133 def _attach_attrs_init(cls_dict, globs): 

1134 init = globs["__attrs_init__"] 

1135 init.__annotations__ = annotations 

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

1137 

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

1139 

1140 return self 

1141 

1142 def add_eq(self): 

1143 cd = self._cls_dict 

1144 

1145 script, globs = _make_eq_script(self._attrs) 

1146 

1147 def _attach_eq(cls_dict, globs): 

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

1149 

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

1151 

1152 cd["__ne__"] = __ne__ 

1153 

1154 return self 

1155 

1156 def add_order(self): 

1157 cd = self._cls_dict 

1158 

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

1160 self._add_method_dunders(meth) 

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

1162 ) 

1163 

1164 return self 

1165 

1166 def add_setattr(self): 

1167 sa_attrs = {} 

1168 for a in self._attrs: 

1169 on_setattr = a.on_setattr or self._on_setattr 

1170 if on_setattr and on_setattr is not setters.NO_OP: 

1171 sa_attrs[a.name] = a, on_setattr 

1172 

1173 if not sa_attrs: 

1174 return self 

1175 

1176 if self._has_custom_setattr: 

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

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

1179 raise ValueError(msg) 

1180 

1181 # docstring comes from _add_method_dunders 

1182 def __setattr__(self, name, val): 

1183 try: 

1184 a, hook = sa_attrs[name] 

1185 except KeyError: 

1186 nval = val 

1187 else: 

1188 nval = hook(self, a, val) 

1189 

1190 _OBJ_SETATTR(self, name, nval) 

1191 

1192 self._cls_dict["__attrs_own_setattr__"] = True 

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

1194 self._wrote_own_setattr = True 

1195 

1196 return self 

1197 

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

1199 """ 

1200 Add __module__ and __qualname__ to a *method*. 

1201 """ 

1202 method.__module__ = self._cls.__module__ 

1203 

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

1205 

1206 method.__doc__ = ( 

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

1208 ) 

1209 

1210 return method 

1211 

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

1213 """ 

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

1215 """ 

1216 with contextlib.suppress(AttributeError): 

1217 method.__module__ = self._cls.__module__ 

1218 

1219 with contextlib.suppress(AttributeError): 

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

1221 

1222 with contextlib.suppress(AttributeError): 

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

1224 

1225 return method 

1226 

1227 

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

1229 """ 

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

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

1232 """ 

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

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

1235 raise ValueError(msg) 

1236 

1237 # cmp takes precedence due to bw-compatibility. 

1238 if cmp is not None: 

1239 return cmp, cmp 

1240 

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

1242 # mirrors equality. 

1243 if eq is None: 

1244 eq = default_eq 

1245 

1246 if order is None: 

1247 order = eq 

1248 

1249 if eq is False and order is True: 

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

1251 raise ValueError(msg) 

1252 

1253 return eq, order 

1254 

1255 

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

1257 """ 

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

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

1260 """ 

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

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

1263 raise ValueError(msg) 

1264 

1265 def decide_callable_or_boolean(value): 

1266 """ 

1267 Decide whether a key function is used. 

1268 """ 

1269 if callable(value): 

1270 value, key = True, value 

1271 else: 

1272 key = None 

1273 return value, key 

1274 

1275 # cmp takes precedence due to bw-compatibility. 

1276 if cmp is not None: 

1277 cmp, cmp_key = decide_callable_or_boolean(cmp) 

1278 return cmp, cmp_key, cmp, cmp_key 

1279 

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

1281 # mirrors equality. 

1282 if eq is None: 

1283 eq, eq_key = default_eq, None 

1284 else: 

1285 eq, eq_key = decide_callable_or_boolean(eq) 

1286 

1287 if order is None: 

1288 order, order_key = eq, eq_key 

1289 else: 

1290 order, order_key = decide_callable_or_boolean(order) 

1291 

1292 if eq is False and order is True: 

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

1294 raise ValueError(msg) 

1295 

1296 return eq, eq_key, order, order_key 

1297 

1298 

1299def _determine_whether_to_implement( 

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

1301): 

1302 """ 

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

1304 

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

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

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

1308 

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

1310 """ 

1311 if flag is True or flag is False: 

1312 return flag 

1313 

1314 if flag is None and auto_detect is False: 

1315 return default 

1316 

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

1318 for dunder in dunders: 

1319 if _has_own_attribute(cls, dunder): 

1320 return False 

1321 

1322 return default 

1323 

1324 

1325def attrs( 

1326 maybe_cls=None, 

1327 these=None, 

1328 repr_ns=None, 

1329 repr=None, 

1330 cmp=None, 

1331 hash=None, 

1332 init=None, 

1333 slots=False, 

1334 frozen=False, 

1335 weakref_slot=True, 

1336 str=False, 

1337 auto_attribs=False, 

1338 kw_only=False, 

1339 cache_hash=False, 

1340 auto_exc=False, 

1341 eq=None, 

1342 order=None, 

1343 auto_detect=False, 

1344 collect_by_mro=False, 

1345 getstate_setstate=None, 

1346 on_setattr=None, 

1347 field_transformer=None, 

1348 match_args=True, 

1349 unsafe_hash=None, 

1350 force_kw_only=True, 

1351): 

1352 r""" 

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

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

1355 

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

1357 *never* go away, though). 

1358 

1359 Args: 

1360 repr_ns (str): 

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

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

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

1364 pointless in Python 3 and is therefore deprecated. 

1365 

1366 .. caution:: 

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

1368 can have different defaults. 

1369 

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

1371 

1372 .. versionadded:: 16.0.0 *slots* 

1373 .. versionadded:: 16.1.0 *frozen* 

1374 .. versionadded:: 16.3.0 *str* 

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

1376 .. versionchanged:: 17.1.0 

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

1378 .. versionadded:: 17.3.0 *auto_attribs* 

1379 .. versionchanged:: 18.1.0 

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

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

1382 .. versionadded:: 18.2.0 *weakref_slot* 

1383 .. deprecated:: 18.2.0 

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

1385 `DeprecationWarning` if the classes compared are subclasses of 

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

1387 to each other. 

1388 .. versionchanged:: 19.2.0 

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

1390 subclasses comparable anymore. 

1391 .. versionadded:: 18.2.0 *kw_only* 

1392 .. versionadded:: 18.2.0 *cache_hash* 

1393 .. versionadded:: 19.1.0 *auto_exc* 

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

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

1396 .. versionadded:: 20.1.0 *auto_detect* 

1397 .. versionadded:: 20.1.0 *collect_by_mro* 

1398 .. versionadded:: 20.1.0 *getstate_setstate* 

1399 .. versionadded:: 20.1.0 *on_setattr* 

1400 .. versionadded:: 20.3.0 *field_transformer* 

1401 .. versionchanged:: 21.1.0 

1402 ``init=False`` injects ``__attrs_init__`` 

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

1404 .. versionchanged:: 21.1.0 *cmp* undeprecated 

1405 .. versionadded:: 21.3.0 *match_args* 

1406 .. versionadded:: 22.2.0 

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

1408 .. deprecated:: 24.1.0 *repr_ns* 

1409 .. versionchanged:: 24.1.0 

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

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

1412 uncomparable values like `math.nan`. 

1413 .. versionadded:: 24.1.0 

1414 If a class has an *inherited* classmethod called 

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

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

1417 .. versionchanged:: 25.4.0 

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

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

1420 .. versionadded:: 25.4.0 *force_kw_only* 

1421 """ 

1422 if repr_ns is not None: 

1423 import warnings 

1424 

1425 warnings.warn( 

1426 DeprecationWarning( 

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

1428 ), 

1429 stacklevel=2, 

1430 ) 

1431 

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

1433 

1434 # unsafe_hash takes precedence due to PEP 681. 

1435 if unsafe_hash is not None: 

1436 hash = unsafe_hash 

1437 

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

1439 on_setattr = setters.pipe(*on_setattr) 

1440 

1441 def wrap(cls): 

1442 nonlocal hash 

1443 is_frozen = frozen or _has_frozen_base_class(cls) 

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

1445 has_own_setattr = auto_detect and _has_own_attribute( 

1446 cls, "__setattr__" 

1447 ) 

1448 

1449 if has_own_setattr and is_frozen: 

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

1451 raise ValueError(msg) 

1452 

1453 eq = not is_exc and _determine_whether_to_implement( 

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

1455 ) 

1456 

1457 Hashability = ClassProps.Hashability 

1458 

1459 if is_exc: 

1460 hashability = Hashability.LEAVE_ALONE 

1461 elif hash is True: 

1462 hashability = ( 

1463 Hashability.HASHABLE_CACHED 

1464 if cache_hash 

1465 else Hashability.HASHABLE 

1466 ) 

1467 elif hash is False: 

1468 hashability = Hashability.LEAVE_ALONE 

1469 elif hash is None: 

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

1471 hashability = Hashability.LEAVE_ALONE 

1472 elif eq is True and is_frozen is True: 

1473 hashability = ( 

1474 Hashability.HASHABLE_CACHED 

1475 if cache_hash 

1476 else Hashability.HASHABLE 

1477 ) 

1478 elif eq is False: 

1479 hashability = Hashability.LEAVE_ALONE 

1480 else: 

1481 hashability = Hashability.UNHASHABLE 

1482 else: 

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

1484 raise TypeError(msg) 

1485 

1486 KeywordOnly = ClassProps.KeywordOnly 

1487 if kw_only: 

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

1489 else: 

1490 kwo = KeywordOnly.NO 

1491 

1492 props = ClassProps( 

1493 is_exception=is_exc, 

1494 is_frozen=is_frozen, 

1495 is_slotted=slots, 

1496 collected_fields_by_mro=collect_by_mro, 

1497 added_init=_determine_whether_to_implement( 

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

1499 ), 

1500 added_repr=_determine_whether_to_implement( 

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

1502 ), 

1503 added_eq=eq, 

1504 added_ordering=not is_exc 

1505 and _determine_whether_to_implement( 

1506 cls, 

1507 order_, 

1508 auto_detect, 

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

1510 ), 

1511 hashability=hashability, 

1512 added_match_args=match_args, 

1513 kw_only=kwo, 

1514 has_weakref_slot=weakref_slot, 

1515 added_str=str, 

1516 added_pickling=_determine_whether_to_implement( 

1517 cls, 

1518 getstate_setstate, 

1519 auto_detect, 

1520 ("__getstate__", "__setstate__"), 

1521 default=slots, 

1522 ), 

1523 on_setattr_hook=on_setattr, 

1524 field_transformer=field_transformer, 

1525 ) 

1526 

1527 if not props.is_hashable and cache_hash: 

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

1529 raise TypeError(msg) 

1530 

1531 builder = _ClassBuilder( 

1532 cls, 

1533 these, 

1534 auto_attribs=auto_attribs, 

1535 props=props, 

1536 has_custom_setattr=has_own_setattr, 

1537 ) 

1538 

1539 if props.added_repr: 

1540 builder.add_repr(repr_ns) 

1541 

1542 if props.added_str: 

1543 builder.add_str() 

1544 

1545 if props.added_eq: 

1546 builder.add_eq() 

1547 if props.added_ordering: 

1548 builder.add_order() 

1549 

1550 if not frozen: 

1551 builder.add_setattr() 

1552 

1553 if props.is_hashable: 

1554 builder.add_hash() 

1555 elif props.hashability is Hashability.UNHASHABLE: 

1556 builder.make_unhashable() 

1557 

1558 if props.added_init: 

1559 builder.add_init() 

1560 else: 

1561 builder.add_attrs_init() 

1562 if cache_hash: 

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

1564 raise TypeError(msg) 

1565 

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

1567 builder.add_replace() 

1568 

1569 if ( 

1570 PY_3_10_PLUS 

1571 and match_args 

1572 and not _has_own_attribute(cls, "__match_args__") 

1573 ): 

1574 builder.add_match_args() 

1575 

1576 return builder.build_class() 

1577 

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

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

1580 if maybe_cls is None: 

1581 return wrap 

1582 

1583 return wrap(maybe_cls) 

1584 

1585 

1586_attrs = attrs 

1587""" 

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

1589*attrs*. 

1590""" 

1591 

1592 

1593def _has_frozen_base_class(cls): 

1594 """ 

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

1596 __setattr__. 

1597 """ 

1598 return cls.__setattr__ is _frozen_setattrs 

1599 

1600 

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

1602 """ 

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

1604 """ 

1605 return ( 

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

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

1608 ) 

1609 

1610 

1611def _make_hash_script( 

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

1613) -> tuple[str, dict]: 

1614 attrs = tuple( 

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

1616 ) 

1617 

1618 tab = " " 

1619 

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

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

1622 globs = {} 

1623 

1624 hash_def = "def __hash__(self" 

1625 hash_func = "hash((" 

1626 closing_braces = "))" 

1627 if not cache_hash: 

1628 hash_def += "):" 

1629 else: 

1630 hash_def += ", *" 

1631 

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

1633 hash_func = "_cache_wrapper(" + hash_func 

1634 closing_braces += ")" 

1635 

1636 method_lines = [hash_def] 

1637 

1638 def append_hash_computation_lines(prefix, indent): 

1639 """ 

1640 Generate the code for actually computing the hash code. 

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

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

1643 """ 

1644 

1645 method_lines.extend( 

1646 [ 

1647 indent + prefix + hash_func, 

1648 indent + f" {type_hash},", 

1649 ] 

1650 ) 

1651 

1652 for a in attrs: 

1653 if a.eq_key: 

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

1655 globs[cmp_name] = a.eq_key 

1656 method_lines.append( 

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

1658 ) 

1659 else: 

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

1661 

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

1663 

1664 if cache_hash: 

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

1666 if frozen: 

1667 append_hash_computation_lines( 

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

1669 ) 

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

1671 else: 

1672 append_hash_computation_lines( 

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

1674 ) 

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

1676 else: 

1677 append_hash_computation_lines("return ", tab) 

1678 

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

1680 return script, globs 

1681 

1682 

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

1684 """ 

1685 Add a hash method to *cls*. 

1686 """ 

1687 script, globs = _make_hash_script( 

1688 cls, attrs, frozen=False, cache_hash=False 

1689 ) 

1690 _compile_and_eval( 

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

1692 ) 

1693 cls.__hash__ = globs["__hash__"] 

1694 return cls 

1695 

1696 

1697def __ne__(self, other): 

1698 """ 

1699 Check equality and either forward a NotImplemented or 

1700 return the result negated. 

1701 """ 

1702 result = self.__eq__(other) 

1703 if result is NotImplemented: 

1704 return NotImplemented 

1705 

1706 return not result 

1707 

1708 

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

1710 """ 

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

1712 """ 

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

1714 

1715 lines = [ 

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

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

1718 " return NotImplemented", 

1719 ] 

1720 

1721 globs = {} 

1722 if attrs: 

1723 lines.append(" return (") 

1724 for a in attrs: 

1725 if a.eq_key: 

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

1727 # Add the key function to the global namespace 

1728 # of the evaluated function. 

1729 globs[cmp_name] = a.eq_key 

1730 lines.append( 

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

1732 ) 

1733 else: 

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

1735 if a is not attrs[-1]: 

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

1737 lines.append(" )") 

1738 else: 

1739 lines.append(" return True") 

1740 

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

1742 

1743 return script, globs 

1744 

1745 

1746def _make_order(cls, attrs): 

1747 """ 

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

1749 """ 

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

1751 

1752 def attrs_to_tuple(obj): 

1753 """ 

1754 Save us some typing. 

1755 """ 

1756 return tuple( 

1757 key(value) if key else value 

1758 for value, key in ( 

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

1760 ) 

1761 ) 

1762 

1763 def __lt__(self, other): 

1764 """ 

1765 Automatically created by attrs. 

1766 """ 

1767 if other.__class__ is self.__class__: 

1768 return attrs_to_tuple(self) < attrs_to_tuple(other) 

1769 

1770 return NotImplemented 

1771 

1772 def __le__(self, other): 

1773 """ 

1774 Automatically created by attrs. 

1775 """ 

1776 if other.__class__ is self.__class__: 

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

1778 

1779 return NotImplemented 

1780 

1781 def __gt__(self, other): 

1782 """ 

1783 Automatically created by attrs. 

1784 """ 

1785 if other.__class__ is self.__class__: 

1786 return attrs_to_tuple(self) > attrs_to_tuple(other) 

1787 

1788 return NotImplemented 

1789 

1790 def __ge__(self, other): 

1791 """ 

1792 Automatically created by attrs. 

1793 """ 

1794 if other.__class__ is self.__class__: 

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

1796 

1797 return NotImplemented 

1798 

1799 return __lt__, __le__, __gt__, __ge__ 

1800 

1801 

1802def _add_eq(cls, attrs=None): 

1803 """ 

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

1805 """ 

1806 if attrs is None: 

1807 attrs = cls.__attrs_attrs__ 

1808 

1809 script, globs = _make_eq_script(attrs) 

1810 _compile_and_eval( 

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

1812 ) 

1813 cls.__eq__ = globs["__eq__"] 

1814 cls.__ne__ = __ne__ 

1815 

1816 return cls 

1817 

1818 

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

1820 """ 

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

1822 """ 

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

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

1825 # callable. 

1826 attr_names_with_reprs = tuple( 

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

1828 for a in attrs 

1829 if a.repr is not False 

1830 ) 

1831 globs = { 

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

1833 } 

1834 globs["_compat"] = _compat 

1835 globs["AttributeError"] = AttributeError 

1836 globs["NOTHING"] = NOTHING 

1837 attribute_fragments = [] 

1838 for name, r, i in attr_names_with_reprs: 

1839 accessor = ( 

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

1841 ) 

1842 fragment = ( 

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

1844 if r == repr 

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

1846 ) 

1847 attribute_fragments.append(fragment) 

1848 repr_fragment = ", ".join(attribute_fragments) 

1849 

1850 if ns is None: 

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

1852 else: 

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

1854 

1855 lines = [ 

1856 "def __repr__(self):", 

1857 " try:", 

1858 " already_repring = _compat.repr_context.already_repring", 

1859 " except AttributeError:", 

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

1861 " _compat.repr_context.already_repring = already_repring", 

1862 " else:", 

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

1864 " return '...'", 

1865 " else:", 

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

1867 " try:", 

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

1869 " finally:", 

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

1871 ] 

1872 

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

1874 

1875 

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

1877 """ 

1878 Add a repr method to *cls*. 

1879 """ 

1880 if attrs is None: 

1881 attrs = cls.__attrs_attrs__ 

1882 

1883 script, globs = _make_repr_script(attrs, ns) 

1884 _compile_and_eval( 

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

1886 ) 

1887 cls.__repr__ = globs["__repr__"] 

1888 return cls 

1889 

1890 

1891def fields(cls): 

1892 """ 

1893 Return the tuple of *attrs* attributes for a class or instance. 

1894 

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

1896 examples). 

1897 

1898 Args: 

1899 cls (type): Class or instance to introspect. 

1900 

1901 Raises: 

1902 TypeError: If *cls* is neither a class nor an *attrs* instance. 

1903 

1904 attrs.exceptions.NotAnAttrsClassError: 

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

1906 

1907 Returns: 

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

1909 

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

1911 by name. 

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

1913 .. versionchanged:: 26.1.0 Add support for instances. 

1914 """ 

1915 generic_base = get_generic_base(cls) 

1916 

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

1918 type_ = type(cls) 

1919 if getattr(type_, "__attrs_attrs__", None) is None: 

1920 msg = "Passed object must be a class or attrs instance." 

1921 raise TypeError(msg) 

1922 

1923 return fields(type_) 

1924 

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

1926 

1927 if attrs is None: 

1928 if generic_base is not None: 

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

1930 if attrs is not None: 

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

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

1933 # efficient. 

1934 cls.__attrs_attrs__ = attrs 

1935 return attrs 

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

1937 raise NotAnAttrsClassError(msg) 

1938 

1939 return attrs 

1940 

1941 

1942def fields_dict(cls): 

1943 """ 

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

1945 are the attribute names. 

1946 

1947 Args: 

1948 cls (type): Class to introspect. 

1949 

1950 Raises: 

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

1952 

1953 attrs.exceptions.NotAnAttrsClassError: 

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

1955 

1956 Returns: 

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

1958 

1959 .. versionadded:: 18.1.0 

1960 """ 

1961 if not isinstance(cls, type): 

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

1963 raise TypeError(msg) 

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

1965 if attrs is None: 

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

1967 raise NotAnAttrsClassError(msg) 

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

1969 

1970 

1971def validate(inst): 

1972 """ 

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

1974 

1975 Leaves all exceptions through. 

1976 

1977 Args: 

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

1979 """ 

1980 if _config._run_validators is False: 

1981 return 

1982 

1983 for a in fields(inst.__class__): 

1984 v = a.validator 

1985 if v is not None: 

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

1987 

1988 

1989def _is_slot_attr(a_name, base_attr_map): 

1990 """ 

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

1992 """ 

1993 cls = base_attr_map.get(a_name) 

1994 return cls and "__slots__" in cls.__dict__ 

1995 

1996 

1997def _make_init_script( 

1998 cls, 

1999 attrs, 

2000 pre_init, 

2001 pre_init_has_args, 

2002 post_init, 

2003 frozen, 

2004 slots, 

2005 cache_hash, 

2006 base_attr_map, 

2007 is_exc, 

2008 cls_on_setattr, 

2009 attrs_init, 

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

2011 has_cls_on_setattr = ( 

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

2013 ) 

2014 

2015 if frozen and has_cls_on_setattr: 

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

2017 raise ValueError(msg) 

2018 

2019 needs_cached_setattr = cache_hash or frozen 

2020 filtered_attrs = [] 

2021 attr_dict = {} 

2022 for a in attrs: 

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

2024 continue 

2025 

2026 filtered_attrs.append(a) 

2027 attr_dict[a.name] = a 

2028 

2029 if a.on_setattr is not None: 

2030 if frozen is True and a.on_setattr is not setters.NO_OP: 

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

2032 raise ValueError(msg) 

2033 

2034 needs_cached_setattr = True 

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

2036 needs_cached_setattr = True 

2037 

2038 script, globs, annotations = _attrs_to_init_script( 

2039 filtered_attrs, 

2040 frozen, 

2041 slots, 

2042 pre_init, 

2043 pre_init_has_args, 

2044 post_init, 

2045 cache_hash, 

2046 base_attr_map, 

2047 is_exc, 

2048 needs_cached_setattr, 

2049 has_cls_on_setattr, 

2050 "__attrs_init__" if attrs_init else "__init__", 

2051 ) 

2052 if cls.__module__ in sys.modules: 

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

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

2055 

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

2057 

2058 if needs_cached_setattr: 

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

2060 # setattr hooks. 

2061 globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__ 

2062 

2063 return script, globs, annotations 

2064 

2065 

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

2067 """ 

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

2069 """ 

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

2071 

2072 

2073def _setattr_with_converter( 

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

2075) -> str: 

2076 """ 

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

2078 its converter first. 

2079 """ 

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

2081 

2082 

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

2084 """ 

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

2086 relegate to _setattr. 

2087 """ 

2088 if has_on_setattr: 

2089 return _setattr(attr_name, value, True) 

2090 

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

2092 

2093 

2094def _assign_with_converter( 

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

2096) -> str: 

2097 """ 

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

2099 conversion. Otherwise relegate to _setattr_with_converter. 

2100 """ 

2101 if has_on_setattr: 

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

2103 

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

2105 

2106 

2107def _determine_setters( 

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

2109): 

2110 """ 

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

2112 and/or slotted. 

2113 """ 

2114 if frozen is True: 

2115 if slots is True: 

2116 return (), _setattr, _setattr_with_converter 

2117 

2118 # Dict frozen classes assign directly to __dict__. 

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

2120 # class. 

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

2122 

2123 def fmt_setter( 

2124 attr_name: str, value_var: str, has_on_setattr: bool 

2125 ) -> str: 

2126 if _is_slot_attr(attr_name, base_attr_map): 

2127 return _setattr(attr_name, value_var, has_on_setattr) 

2128 

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

2130 

2131 def fmt_setter_with_converter( 

2132 attr_name: str, 

2133 value_var: str, 

2134 has_on_setattr: bool, 

2135 converter: Converter, 

2136 ) -> str: 

2137 if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): 

2138 return _setattr_with_converter( 

2139 attr_name, value_var, has_on_setattr, converter 

2140 ) 

2141 

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

2143 

2144 return ( 

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

2146 fmt_setter, 

2147 fmt_setter_with_converter, 

2148 ) 

2149 

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

2151 return (), _assign, _assign_with_converter 

2152 

2153 

2154def _attrs_to_init_script( 

2155 attrs: list[Attribute], 

2156 is_frozen: bool, 

2157 is_slotted: bool, 

2158 call_pre_init: bool, 

2159 pre_init_has_args: bool, 

2160 call_post_init: bool, 

2161 does_cache_hash: bool, 

2162 base_attr_map: dict[str, type], 

2163 is_exc: bool, 

2164 needs_cached_setattr: bool, 

2165 has_cls_on_setattr: bool, 

2166 method_name: str, 

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

2168 """ 

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

2170 annotations for the initializer. 

2171 

2172 The globals are required by the generated script. 

2173 """ 

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

2175 

2176 if needs_cached_setattr: 

2177 lines.append( 

2178 # Circumvent the __setattr__ descriptor to save one lookup per 

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

2180 # does_cache_hash is True. 

2181 "_setattr = _cached_setattr_get(self)" 

2182 ) 

2183 

2184 extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters( 

2185 is_frozen, is_slotted, base_attr_map 

2186 ) 

2187 lines.extend(extra_lines) 

2188 

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

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

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

2192 attrs_to_validate = [] 

2193 

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

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

2196 names_for_globals = {} 

2197 annotations = {"return": None} 

2198 

2199 for a in attrs: 

2200 if a.validator: 

2201 attrs_to_validate.append(a) 

2202 

2203 attr_name = a.name 

2204 has_on_setattr = a.on_setattr is not None or ( 

2205 a.on_setattr is not setters.NO_OP and has_cls_on_setattr 

2206 ) 

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

2208 # explicitly provided 

2209 arg_name = a.alias 

2210 

2211 has_factory = isinstance(a.default, Factory) 

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

2213 

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

2215 converter = Converter(a.converter) 

2216 else: 

2217 converter = a.converter 

2218 

2219 if a.init is False: 

2220 if has_factory: 

2221 init_factory_name = _INIT_FACTORY_PAT % (a.name,) 

2222 if converter is not None: 

2223 lines.append( 

2224 fmt_setter_with_converter( 

2225 attr_name, 

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

2227 has_on_setattr, 

2228 converter, 

2229 ) 

2230 ) 

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

2232 converter.converter 

2233 ) 

2234 else: 

2235 lines.append( 

2236 fmt_setter( 

2237 attr_name, 

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

2239 has_on_setattr, 

2240 ) 

2241 ) 

2242 names_for_globals[init_factory_name] = a.default.factory 

2243 elif converter is not None: 

2244 lines.append( 

2245 fmt_setter_with_converter( 

2246 attr_name, 

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

2248 has_on_setattr, 

2249 converter, 

2250 ) 

2251 ) 

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

2253 converter.converter 

2254 ) 

2255 else: 

2256 lines.append( 

2257 fmt_setter( 

2258 attr_name, 

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

2260 has_on_setattr, 

2261 ) 

2262 ) 

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

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

2265 if a.kw_only: 

2266 kw_only_args.append(arg) 

2267 else: 

2268 args.append(arg) 

2269 pre_init_args.append(arg_name) 

2270 

2271 if converter is not None: 

2272 lines.append( 

2273 fmt_setter_with_converter( 

2274 attr_name, arg_name, has_on_setattr, converter 

2275 ) 

2276 ) 

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

2278 converter.converter 

2279 ) 

2280 else: 

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

2282 

2283 elif has_factory: 

2284 arg = f"{arg_name}=NOTHING" 

2285 if a.kw_only: 

2286 kw_only_args.append(arg) 

2287 else: 

2288 args.append(arg) 

2289 pre_init_args.append(arg_name) 

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

2291 

2292 init_factory_name = _INIT_FACTORY_PAT % (a.name,) 

2293 if converter is not None: 

2294 lines.append( 

2295 " " 

2296 + fmt_setter_with_converter( 

2297 attr_name, arg_name, has_on_setattr, converter 

2298 ) 

2299 ) 

2300 lines.append("else:") 

2301 lines.append( 

2302 " " 

2303 + fmt_setter_with_converter( 

2304 attr_name, 

2305 init_factory_name + "(" + maybe_self + ")", 

2306 has_on_setattr, 

2307 converter, 

2308 ) 

2309 ) 

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

2311 converter.converter 

2312 ) 

2313 else: 

2314 lines.append( 

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

2316 ) 

2317 lines.append("else:") 

2318 lines.append( 

2319 " " 

2320 + fmt_setter( 

2321 attr_name, 

2322 init_factory_name + "(" + maybe_self + ")", 

2323 has_on_setattr, 

2324 ) 

2325 ) 

2326 names_for_globals[init_factory_name] = a.default.factory 

2327 else: 

2328 if a.kw_only: 

2329 kw_only_args.append(arg_name) 

2330 else: 

2331 args.append(arg_name) 

2332 pre_init_args.append(arg_name) 

2333 

2334 if converter is not None: 

2335 lines.append( 

2336 fmt_setter_with_converter( 

2337 attr_name, arg_name, has_on_setattr, converter 

2338 ) 

2339 ) 

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

2341 converter.converter 

2342 ) 

2343 else: 

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

2345 

2346 if a.init is True: 

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

2348 annotations[arg_name] = a.type 

2349 elif converter is not None and converter._first_param_type: 

2350 # Use the type from the converter if present. 

2351 annotations[arg_name] = converter._first_param_type 

2352 

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

2354 names_for_globals["_config"] = _config 

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

2356 for a in attrs_to_validate: 

2357 val_name = "__attr_validator_" + a.name 

2358 attr_name = "__attr_" + a.name 

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

2360 names_for_globals[val_name] = a.validator 

2361 names_for_globals[attr_name] = a 

2362 

2363 if call_post_init: 

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

2365 

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

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

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

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

2370 # would result in silent bugs. 

2371 if does_cache_hash: 

2372 if is_frozen: 

2373 if is_slotted: 

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

2375 else: 

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

2377 else: 

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

2379 lines.append(init_hash_cache) 

2380 

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

2382 # initialization. 

2383 if is_exc: 

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

2385 

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

2387 

2388 args = ", ".join(args) 

2389 pre_init_args = ", ".join(pre_init_args) 

2390 if kw_only_args: 

2391 # leading comma & kw_only args 

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

2393 pre_init_kw_only_args = ", ".join( 

2394 [ 

2395 f"{kw_arg_name}={kw_arg_name}" 

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

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

2398 ] 

2399 ) 

2400 pre_init_args += ", " if pre_init_args else "" 

2401 pre_init_args += pre_init_kw_only_args 

2402 

2403 if call_pre_init and pre_init_has_args: 

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

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

2406 

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

2408 NL = "\n " 

2409 return ( 

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

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

2412""", 

2413 names_for_globals, 

2414 annotations, 

2415 ) 

2416 

2417 

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

2419 """ 

2420 The default __init__ parameter name for a field. 

2421 

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

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

2424 """ 

2425 

2426 return name.lstrip("_") 

2427 

2428 

2429class Attribute: 

2430 """ 

2431 *Read-only* representation of an attribute. 

2432 

2433 .. warning:: 

2434 

2435 You should never instantiate this class yourself. 

2436 

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

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

2439 

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

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

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

2443 - ``alias_is_default`` (`bool`): Whether the ``alias`` was automatically 

2444 generated (``True``) or explicitly provided by the user (``False``). 

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

2446 from a base class. 

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

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

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

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

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

2452 

2453 Instances of this class are frequently used for introspection purposes 

2454 like: 

2455 

2456 - `fields` returns a tuple of them. 

2457 - Validators get them passed as the first argument. 

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

2459 them. 

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

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

2462 

2463 

2464 .. versionadded:: 20.1.0 *inherited* 

2465 .. versionadded:: 20.1.0 *on_setattr* 

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

2467 equality checks and hashing anymore. 

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

2469 .. versionadded:: 22.2.0 *alias* 

2470 .. versionadded:: 26.1.0 *alias_is_default* 

2471 

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

2473 """ 

2474 

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

2476 # instantiation. 

2477 __slots__ = ( # noqa: RUF023 

2478 "name", 

2479 "default", 

2480 "validator", 

2481 "repr", 

2482 "eq", 

2483 "eq_key", 

2484 "order", 

2485 "order_key", 

2486 "hash", 

2487 "init", 

2488 "metadata", 

2489 "type", 

2490 "converter", 

2491 "kw_only", 

2492 "inherited", 

2493 "on_setattr", 

2494 "alias", 

2495 "alias_is_default", 

2496 ) 

2497 

2498 def __init__( 

2499 self, 

2500 name, 

2501 default, 

2502 validator, 

2503 repr, 

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

2505 hash, 

2506 init, 

2507 inherited, 

2508 metadata=None, 

2509 type=None, 

2510 converter=None, 

2511 kw_only=False, 

2512 eq=None, 

2513 eq_key=None, 

2514 order=None, 

2515 order_key=None, 

2516 on_setattr=None, 

2517 alias=None, 

2518 alias_is_default=None, 

2519 ): 

2520 eq, eq_key, order, order_key = _determine_attrib_eq_order( 

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

2522 ) 

2523 

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

2525 bound_setattr = _OBJ_SETATTR.__get__(self) 

2526 

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

2528 # themselves. 

2529 bound_setattr("name", name) 

2530 bound_setattr("default", default) 

2531 bound_setattr("validator", validator) 

2532 bound_setattr("repr", repr) 

2533 bound_setattr("eq", eq) 

2534 bound_setattr("eq_key", eq_key) 

2535 bound_setattr("order", order) 

2536 bound_setattr("order_key", order_key) 

2537 bound_setattr("hash", hash) 

2538 bound_setattr("init", init) 

2539 bound_setattr("converter", converter) 

2540 bound_setattr( 

2541 "metadata", 

2542 ( 

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

2544 if metadata 

2545 else _EMPTY_METADATA_SINGLETON 

2546 ), 

2547 ) 

2548 bound_setattr("type", type) 

2549 bound_setattr("kw_only", kw_only) 

2550 bound_setattr("inherited", inherited) 

2551 bound_setattr("on_setattr", on_setattr) 

2552 bound_setattr("alias", alias) 

2553 bound_setattr( 

2554 "alias_is_default", 

2555 alias is None if alias_is_default is None else alias_is_default, 

2556 ) 

2557 

2558 def __setattr__(self, name, value): 

2559 raise FrozenInstanceError 

2560 

2561 @classmethod 

2562 def from_counting_attr( 

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

2564 ): 

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

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

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

2568 if type is None: 

2569 type = ca.type 

2570 elif ca.type is not None: 

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

2572 raise ValueError(msg) 

2573 return cls( 

2574 name, 

2575 ca._default, 

2576 ca._validator, 

2577 ca.repr, 

2578 None, 

2579 ca.hash, 

2580 ca.init, 

2581 False, 

2582 ca.metadata, 

2583 type, 

2584 ca.converter, 

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

2586 ca.eq, 

2587 ca.eq_key, 

2588 ca.order, 

2589 ca.order_key, 

2590 ca.on_setattr, 

2591 ca.alias, 

2592 ca.alias is None, 

2593 ) 

2594 

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

2596 def evolve(self, **changes): 

2597 """ 

2598 Copy *self* and apply *changes*. 

2599 

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

2601 with :class:`attrs.Attribute`. 

2602 

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

2604 

2605 .. versionadded:: 20.3.0 

2606 """ 

2607 new = copy.copy(self) 

2608 

2609 new._setattrs(changes.items()) 

2610 

2611 if "alias" in changes and "alias_is_default" not in changes: 

2612 # Explicit alias provided -- no longer the default. 

2613 _OBJ_SETATTR.__get__(new)("alias_is_default", False) 

2614 elif ( 

2615 "name" in changes 

2616 and "alias" not in changes 

2617 # Don't auto-generate alias if the user picked picked the old one. 

2618 and self.alias_is_default 

2619 ): 

2620 # Name changed, alias was auto-generated -- update it. 

2621 _OBJ_SETATTR.__get__(new)( 

2622 "alias", _default_init_alias_for(new.name) 

2623 ) 

2624 

2625 return new 

2626 

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

2628 def __getstate__(self): 

2629 """ 

2630 Play nice with pickle. 

2631 """ 

2632 return tuple( 

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

2634 for name in self.__slots__ 

2635 ) 

2636 

2637 def __setstate__(self, state): 

2638 """ 

2639 Play nice with pickle. 

2640 """ 

2641 if len(state) < len(self.__slots__): 

2642 # Pre-26.1.0 pickle without alias_is_default -- infer it 

2643 # heuristically. 

2644 state_dict = dict(zip(self.__slots__, state)) 

2645 alias_is_default = state_dict.get( 

2646 "alias" 

2647 ) is None or state_dict.get("alias") == _default_init_alias_for( 

2648 state_dict["name"] 

2649 ) 

2650 state = (*state, alias_is_default) 

2651 

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

2653 

2654 def _setattrs(self, name_values_pairs): 

2655 bound_setattr = _OBJ_SETATTR.__get__(self) 

2656 for name, value in name_values_pairs: 

2657 if name != "metadata": 

2658 bound_setattr(name, value) 

2659 else: 

2660 bound_setattr( 

2661 name, 

2662 ( 

2663 types.MappingProxyType(dict(value)) 

2664 if value 

2665 else _EMPTY_METADATA_SINGLETON 

2666 ), 

2667 ) 

2668 

2669 

2670_a = [ 

2671 Attribute( 

2672 name=name, 

2673 default=NOTHING, 

2674 validator=None, 

2675 repr=(name != "alias_is_default"), 

2676 cmp=None, 

2677 eq=True, 

2678 order=False, 

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

2680 init=True, 

2681 inherited=False, 

2682 alias=_default_init_alias_for(name), 

2683 ) 

2684 for name in Attribute.__slots__ 

2685] 

2686 

2687Attribute = _add_hash( 

2688 _add_eq( 

2689 _add_repr(Attribute, attrs=_a), 

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

2691 ), 

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

2693) 

2694 

2695 

2696class _CountingAttr: 

2697 """ 

2698 Intermediate representation of attributes that uses a counter to preserve 

2699 the order in which the attributes have been defined. 

2700 

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

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

2703 """ 

2704 

2705 __slots__ = ( 

2706 "_default", 

2707 "_validator", 

2708 "alias", 

2709 "converter", 

2710 "counter", 

2711 "eq", 

2712 "eq_key", 

2713 "hash", 

2714 "init", 

2715 "kw_only", 

2716 "metadata", 

2717 "on_setattr", 

2718 "order", 

2719 "order_key", 

2720 "repr", 

2721 "type", 

2722 ) 

2723 __attrs_attrs__ = ( 

2724 *tuple( 

2725 Attribute( 

2726 name=name, 

2727 alias=_default_init_alias_for(name), 

2728 default=NOTHING, 

2729 validator=None, 

2730 repr=True, 

2731 cmp=None, 

2732 hash=True, 

2733 init=True, 

2734 kw_only=False, 

2735 eq=True, 

2736 eq_key=None, 

2737 order=False, 

2738 order_key=None, 

2739 inherited=False, 

2740 on_setattr=None, 

2741 ) 

2742 for name in ( 

2743 "counter", 

2744 "_default", 

2745 "repr", 

2746 "eq", 

2747 "order", 

2748 "hash", 

2749 "init", 

2750 "on_setattr", 

2751 "alias", 

2752 ) 

2753 ), 

2754 Attribute( 

2755 name="metadata", 

2756 alias="metadata", 

2757 default=None, 

2758 validator=None, 

2759 repr=True, 

2760 cmp=None, 

2761 hash=False, 

2762 init=True, 

2763 kw_only=False, 

2764 eq=True, 

2765 eq_key=None, 

2766 order=False, 

2767 order_key=None, 

2768 inherited=False, 

2769 on_setattr=None, 

2770 ), 

2771 ) 

2772 cls_counter = 0 

2773 

2774 def __init__( 

2775 self, 

2776 default, 

2777 validator, 

2778 repr, 

2779 cmp, 

2780 hash, 

2781 init, 

2782 converter, 

2783 metadata, 

2784 type, 

2785 kw_only, 

2786 eq, 

2787 eq_key, 

2788 order, 

2789 order_key, 

2790 on_setattr, 

2791 alias, 

2792 ): 

2793 _CountingAttr.cls_counter += 1 

2794 self.counter = _CountingAttr.cls_counter 

2795 self._default = default 

2796 self._validator = validator 

2797 self.converter = converter 

2798 self.repr = repr 

2799 self.eq = eq 

2800 self.eq_key = eq_key 

2801 self.order = order 

2802 self.order_key = order_key 

2803 self.hash = hash 

2804 self.init = init 

2805 self.metadata = metadata 

2806 self.type = type 

2807 self.kw_only = kw_only 

2808 self.on_setattr = on_setattr 

2809 self.alias = alias 

2810 

2811 def validator(self, meth): 

2812 """ 

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

2814 

2815 Returns *meth* unchanged. 

2816 

2817 .. versionadded:: 17.1.0 

2818 """ 

2819 if self._validator is None: 

2820 self._validator = meth 

2821 else: 

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

2823 return meth 

2824 

2825 def default(self, meth): 

2826 """ 

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

2828 

2829 Returns *meth* unchanged. 

2830 

2831 Raises: 

2832 DefaultAlreadySetError: If default has been set before. 

2833 

2834 .. versionadded:: 17.1.0 

2835 """ 

2836 if self._default is not NOTHING: 

2837 raise DefaultAlreadySetError 

2838 

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

2840 

2841 return meth 

2842 

2843 

2844_CountingAttr = _add_eq(_add_repr(_CountingAttr)) 

2845 

2846 

2847class ClassProps: 

2848 """ 

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

2850 `define()` decorators. 

2851 

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

2853 to construct the final class. 

2854 

2855 Warning: 

2856 

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

2858 strict backwards-compatibility guarantees. 

2859 

2860 

2861 Attributes: 

2862 is_exception (bool): 

2863 Whether the class is treated as an exception class. 

2864 

2865 is_slotted (bool): 

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

2867 

2868 has_weakref_slot (bool): 

2869 Whether the class has a slot for weak references. 

2870 

2871 is_frozen (bool): 

2872 Whether the class is frozen. 

2873 

2874 kw_only (KeywordOnly): 

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

2876 ``__init__`` method. 

2877 

2878 collected_fields_by_mro (bool): 

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

2880 That is, correctly but unlike `dataclasses`. 

2881 

2882 added_init (bool): 

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

2884 

2885 added_repr (bool): 

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

2887 

2888 added_eq (bool): 

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

2890 

2891 added_ordering (bool): 

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

2893 

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

2895 

2896 added_match_args (bool): 

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

2898 fields. 

2899 

2900 added_str (bool): 

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

2902 

2903 added_pickling (bool): 

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

2905 ``__setstate__`` methods for `pickle`. 

2906 

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

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

2909 

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

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

2912 

2913 .. versionadded:: 25.4.0 

2914 """ 

2915 

2916 class Hashability(enum.Enum): 

2917 """ 

2918 The hashability of a class. 

2919 

2920 .. versionadded:: 25.4.0 

2921 """ 

2922 

2923 HASHABLE = "hashable" 

2924 """Write a ``__hash__``.""" 

2925 HASHABLE_CACHED = "hashable_cache" 

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

2927 UNHASHABLE = "unhashable" 

2928 """Set ``__hash__`` to ``None``.""" 

2929 LEAVE_ALONE = "leave_alone" 

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

2931 

2932 class KeywordOnly(enum.Enum): 

2933 """ 

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

2935 

2936 .. versionadded:: 25.4.0 

2937 """ 

2938 

2939 NO = "no" 

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

2941 YES = "yes" 

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

2943 FORCE = "force" 

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

2945 

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

2947 "is_exception", 

2948 "is_slotted", 

2949 "has_weakref_slot", 

2950 "is_frozen", 

2951 "kw_only", 

2952 "collected_fields_by_mro", 

2953 "added_init", 

2954 "added_repr", 

2955 "added_eq", 

2956 "added_ordering", 

2957 "hashability", 

2958 "added_match_args", 

2959 "added_str", 

2960 "added_pickling", 

2961 "on_setattr_hook", 

2962 "field_transformer", 

2963 ) 

2964 

2965 def __init__( 

2966 self, 

2967 is_exception, 

2968 is_slotted, 

2969 has_weakref_slot, 

2970 is_frozen, 

2971 kw_only, 

2972 collected_fields_by_mro, 

2973 added_init, 

2974 added_repr, 

2975 added_eq, 

2976 added_ordering, 

2977 hashability, 

2978 added_match_args, 

2979 added_str, 

2980 added_pickling, 

2981 on_setattr_hook, 

2982 field_transformer, 

2983 ): 

2984 self.is_exception = is_exception 

2985 self.is_slotted = is_slotted 

2986 self.has_weakref_slot = has_weakref_slot 

2987 self.is_frozen = is_frozen 

2988 self.kw_only = kw_only 

2989 self.collected_fields_by_mro = collected_fields_by_mro 

2990 self.added_init = added_init 

2991 self.added_repr = added_repr 

2992 self.added_eq = added_eq 

2993 self.added_ordering = added_ordering 

2994 self.hashability = hashability 

2995 self.added_match_args = added_match_args 

2996 self.added_str = added_str 

2997 self.added_pickling = added_pickling 

2998 self.on_setattr_hook = on_setattr_hook 

2999 self.field_transformer = field_transformer 

3000 

3001 @property 

3002 def is_hashable(self): 

3003 return ( 

3004 self.hashability is ClassProps.Hashability.HASHABLE 

3005 or self.hashability is ClassProps.Hashability.HASHABLE_CACHED 

3006 ) 

3007 

3008 

3009_cas = [ 

3010 Attribute( 

3011 name=name, 

3012 default=NOTHING, 

3013 validator=None, 

3014 repr=True, 

3015 cmp=None, 

3016 eq=True, 

3017 order=False, 

3018 hash=True, 

3019 init=True, 

3020 inherited=False, 

3021 alias=_default_init_alias_for(name), 

3022 ) 

3023 for name in ClassProps.__slots__ 

3024] 

3025 

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

3027 

3028 

3029class Factory: 

3030 """ 

3031 Stores a factory callable. 

3032 

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

3034 generate a new value. 

3035 

3036 Args: 

3037 factory (typing.Callable): 

3038 A callable that takes either none or exactly one mandatory 

3039 positional argument depending on *takes_self*. 

3040 

3041 takes_self (bool): 

3042 Pass the partially initialized instance that is being initialized 

3043 as a positional argument. 

3044 

3045 .. versionadded:: 17.1.0 *takes_self* 

3046 """ 

3047 

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

3049 

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

3051 self.factory = factory 

3052 self.takes_self = takes_self 

3053 

3054 def __getstate__(self): 

3055 """ 

3056 Play nice with pickle. 

3057 """ 

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

3059 

3060 def __setstate__(self, state): 

3061 """ 

3062 Play nice with pickle. 

3063 """ 

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

3065 setattr(self, name, value) 

3066 

3067 

3068_f = [ 

3069 Attribute( 

3070 name=name, 

3071 default=NOTHING, 

3072 validator=None, 

3073 repr=True, 

3074 cmp=None, 

3075 eq=True, 

3076 order=False, 

3077 hash=True, 

3078 init=True, 

3079 inherited=False, 

3080 ) 

3081 for name in Factory.__slots__ 

3082] 

3083 

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

3085 

3086 

3087class Converter: 

3088 """ 

3089 Stores a converter callable. 

3090 

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

3092 arguments are passed in the order they are documented. 

3093 

3094 Args: 

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

3096 

3097 takes_self (bool): 

3098 Pass the partially initialized instance that is being initialized 

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

3100 

3101 takes_field (bool): 

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

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

3104 

3105 .. versionadded:: 24.1.0 

3106 """ 

3107 

3108 __slots__ = ( 

3109 "__call__", 

3110 "_first_param_type", 

3111 "_global_name", 

3112 "converter", 

3113 "takes_field", 

3114 "takes_self", 

3115 ) 

3116 

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

3118 self.converter = converter 

3119 self.takes_self = takes_self 

3120 self.takes_field = takes_field 

3121 

3122 ex = _AnnotationExtractor(converter) 

3123 self._first_param_type = ex.get_first_param_type() 

3124 

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

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

3127 elif self.takes_self and not self.takes_field: 

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

3129 value, instance 

3130 ) 

3131 elif not self.takes_self and self.takes_field: 

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

3133 value, field 

3134 ) 

3135 else: 

3136 self.__call__ = self.converter 

3137 

3138 rt = ex.get_return_type() 

3139 if rt is not None: 

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

3141 

3142 @staticmethod 

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

3144 """ 

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

3146 would have. 

3147 """ 

3148 return f"__attr_converter_{attr_name}" 

3149 

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

3151 """ 

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

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

3154 `self.takes_self` and `self.takes_field`. 

3155 """ 

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

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

3158 

3159 if self.takes_self and self.takes_field: 

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

3161 

3162 if self.takes_self: 

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

3164 

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

3166 

3167 def __getstate__(self): 

3168 """ 

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

3170 computed when loading. 

3171 """ 

3172 return { 

3173 "converter": self.converter, 

3174 "takes_self": self.takes_self, 

3175 "takes_field": self.takes_field, 

3176 } 

3177 

3178 def __setstate__(self, state): 

3179 """ 

3180 Load instance from state. 

3181 """ 

3182 self.__init__(**state) 

3183 

3184 

3185_f = [ 

3186 Attribute( 

3187 name=name, 

3188 default=NOTHING, 

3189 validator=None, 

3190 repr=True, 

3191 cmp=None, 

3192 eq=True, 

3193 order=False, 

3194 hash=True, 

3195 init=True, 

3196 inherited=False, 

3197 ) 

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

3199] 

3200 

3201Converter = _add_hash( 

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

3203) 

3204 

3205 

3206def make_class( 

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

3208): 

3209 r""" 

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

3211 

3212 .. note:: 

3213 

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

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

3216 

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

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

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

3220 

3221 .. warning:: 

3222 

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

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

3225 you. 

3226 

3227 Args: 

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

3229 

3230 attrs (list | dict): 

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

3232 s / `attrs.field`\ s. 

3233 

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

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

3236 attributes is used. 

3237 

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

3239 

3240 class_body (dict): 

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

3242 

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

3244 

3245 Returns: 

3246 type: A new class with *attrs*. 

3247 

3248 .. versionadded:: 17.1.0 *bases* 

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

3250 .. versionchanged:: 23.2.0 *class_body* 

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

3252 """ 

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

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

3255 

3256 if isinstance(attrs, dict): 

3257 cls_dict = attrs 

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

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

3260 else: 

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

3262 raise TypeError(msg) 

3263 

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

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

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

3267 

3268 body = {} 

3269 if class_body is not None: 

3270 body.update(class_body) 

3271 if pre_init is not None: 

3272 body["__attrs_pre_init__"] = pre_init 

3273 if post_init is not None: 

3274 body["__attrs_post_init__"] = post_init 

3275 if user_init is not None: 

3276 body["__init__"] = user_init 

3277 

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

3279 

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

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

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

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

3284 with contextlib.suppress(AttributeError, ValueError): 

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

3286 "__name__", "__main__" 

3287 ) 

3288 

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

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

3291 ( 

3292 attributes_arguments["eq"], 

3293 attributes_arguments["order"], 

3294 ) = _determine_attrs_eq_order( 

3295 cmp, 

3296 attributes_arguments.get("eq"), 

3297 attributes_arguments.get("order"), 

3298 True, 

3299 ) 

3300 

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

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

3303 cls.__annotations__ = { 

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

3305 } 

3306 return cls 

3307 

3308 

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

3310# import into .validators / .converters. 

3311 

3312 

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

3314class _AndValidator: 

3315 """ 

3316 Compose many validators to a single one. 

3317 """ 

3318 

3319 _validators = attrib() 

3320 

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

3322 for v in self._validators: 

3323 v(inst, attr, value) 

3324 

3325 

3326def and_(*validators): 

3327 """ 

3328 A validator that composes multiple validators into one. 

3329 

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

3331 

3332 Args: 

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

3334 Arbitrary number of validators. 

3335 

3336 .. versionadded:: 17.1.0 

3337 """ 

3338 vals = [] 

3339 for validator in validators: 

3340 vals.extend( 

3341 validator._validators 

3342 if isinstance(validator, _AndValidator) 

3343 else [validator] 

3344 ) 

3345 

3346 return _AndValidator(tuple(vals)) 

3347 

3348 

3349def pipe(*converters): 

3350 """ 

3351 A converter that composes multiple converters into one. 

3352 

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

3354 *last* value. 

3355 

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

3357 have any. 

3358 

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

3360 Arbitrary number of converters. 

3361 

3362 .. versionadded:: 20.1.0 

3363 """ 

3364 

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

3366 

3367 if return_instance: 

3368 

3369 def pipe_converter(val, inst, field): 

3370 for c in converters: 

3371 val = ( 

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

3373 ) 

3374 

3375 return val 

3376 

3377 else: 

3378 

3379 def pipe_converter(val): 

3380 for c in converters: 

3381 val = c(val) 

3382 

3383 return val 

3384 

3385 if not converters: 

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

3387 A = TypeVar("A") 

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

3389 else: 

3390 # Get parameter type from first converter. 

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

3392 if t: 

3393 pipe_converter.__annotations__["val"] = t 

3394 

3395 last = converters[-1] 

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

3397 last = last.__call__ 

3398 

3399 # Get return type from last converter. 

3400 rt = _AnnotationExtractor(last).get_return_type() 

3401 if rt: 

3402 pipe_converter.__annotations__["return"] = rt 

3403 

3404 if return_instance: 

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

3406 return pipe_converter