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

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

1149 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 collect_by_mro (bool): 

1361 If True, *attrs* collects attributes from base classes correctly 

1362 according to the `method resolution order 

1363 <https://docs.python.org/3/howto/mro.html>`_. If False, *attrs* 

1364 will mimic the (wrong) behavior of `dataclasses` and :pep:`681`. 

1365 

1366 See also `issue #428 

1367 <https://github.com/python-attrs/attrs/issues/428>`_. 

1368 

1369 repr_ns (str): 

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

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

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

1373 pointless in Python 3 and is therefore deprecated. 

1374 

1375 .. caution:: 

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

1377 can have different defaults. 

1378 

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

1380 

1381 .. versionadded:: 16.0.0 *slots* 

1382 .. versionadded:: 16.1.0 *frozen* 

1383 .. versionadded:: 16.3.0 *str* 

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

1385 .. versionchanged:: 17.1.0 

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

1387 .. versionadded:: 17.3.0 *auto_attribs* 

1388 .. versionchanged:: 18.1.0 

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

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

1391 .. versionadded:: 18.2.0 *weakref_slot* 

1392 .. deprecated:: 18.2.0 

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

1394 `DeprecationWarning` if the classes compared are subclasses of 

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

1396 to each other. 

1397 .. versionchanged:: 19.2.0 

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

1399 subclasses comparable anymore. 

1400 .. versionadded:: 18.2.0 *kw_only* 

1401 .. versionadded:: 18.2.0 *cache_hash* 

1402 .. versionadded:: 19.1.0 *auto_exc* 

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

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

1405 .. versionadded:: 20.1.0 *auto_detect* 

1406 .. versionadded:: 20.1.0 *collect_by_mro* 

1407 .. versionadded:: 20.1.0 *getstate_setstate* 

1408 .. versionadded:: 20.1.0 *on_setattr* 

1409 .. versionadded:: 20.3.0 *field_transformer* 

1410 .. versionchanged:: 21.1.0 

1411 ``init=False`` injects ``__attrs_init__`` 

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

1413 .. versionchanged:: 21.1.0 *cmp* undeprecated 

1414 .. versionadded:: 21.3.0 *match_args* 

1415 .. versionadded:: 22.2.0 

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

1417 .. deprecated:: 24.1.0 *repr_ns* 

1418 .. versionchanged:: 24.1.0 

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

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

1421 uncomparable values like `math.nan`. 

1422 .. versionadded:: 24.1.0 

1423 If a class has an *inherited* classmethod called 

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

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

1426 .. versionchanged:: 25.4.0 

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

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

1429 .. versionadded:: 25.4.0 *force_kw_only* 

1430 """ 

1431 if repr_ns is not None: 

1432 import warnings 

1433 

1434 warnings.warn( 

1435 DeprecationWarning( 

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

1437 ), 

1438 stacklevel=2, 

1439 ) 

1440 

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

1442 

1443 # unsafe_hash takes precedence due to PEP 681. 

1444 if unsafe_hash is not None: 

1445 hash = unsafe_hash 

1446 

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

1448 on_setattr = setters.pipe(*on_setattr) 

1449 

1450 def wrap(cls): 

1451 nonlocal hash 

1452 is_frozen = frozen or _has_frozen_base_class(cls) 

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

1454 has_own_setattr = auto_detect and _has_own_attribute( 

1455 cls, "__setattr__" 

1456 ) 

1457 

1458 if has_own_setattr and is_frozen: 

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

1460 raise ValueError(msg) 

1461 

1462 eq = not is_exc and _determine_whether_to_implement( 

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

1464 ) 

1465 

1466 Hashability = ClassProps.Hashability 

1467 

1468 if is_exc: 

1469 hashability = Hashability.LEAVE_ALONE 

1470 elif hash is True: 

1471 hashability = ( 

1472 Hashability.HASHABLE_CACHED 

1473 if cache_hash 

1474 else Hashability.HASHABLE 

1475 ) 

1476 elif hash is False: 

1477 hashability = Hashability.LEAVE_ALONE 

1478 elif hash is None: 

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

1480 hashability = Hashability.LEAVE_ALONE 

1481 elif eq is True and is_frozen is True: 

1482 hashability = ( 

1483 Hashability.HASHABLE_CACHED 

1484 if cache_hash 

1485 else Hashability.HASHABLE 

1486 ) 

1487 elif eq is False: 

1488 hashability = Hashability.LEAVE_ALONE 

1489 else: 

1490 hashability = Hashability.UNHASHABLE 

1491 else: 

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

1493 raise TypeError(msg) 

1494 

1495 KeywordOnly = ClassProps.KeywordOnly 

1496 if kw_only: 

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

1498 else: 

1499 kwo = KeywordOnly.NO 

1500 

1501 props = ClassProps( 

1502 is_exception=is_exc, 

1503 is_frozen=is_frozen, 

1504 is_slotted=slots, 

1505 collected_fields_by_mro=collect_by_mro, 

1506 added_init=_determine_whether_to_implement( 

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

1508 ), 

1509 added_repr=_determine_whether_to_implement( 

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

1511 ), 

1512 added_eq=eq, 

1513 added_ordering=not is_exc 

1514 and _determine_whether_to_implement( 

1515 cls, 

1516 order_, 

1517 auto_detect, 

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

1519 ), 

1520 hashability=hashability, 

1521 added_match_args=match_args, 

1522 kw_only=kwo, 

1523 has_weakref_slot=weakref_slot, 

1524 added_str=str, 

1525 added_pickling=_determine_whether_to_implement( 

1526 cls, 

1527 getstate_setstate, 

1528 auto_detect, 

1529 ("__getstate__", "__setstate__"), 

1530 default=slots, 

1531 ), 

1532 on_setattr_hook=on_setattr, 

1533 field_transformer=field_transformer, 

1534 ) 

1535 

1536 if not props.is_hashable and cache_hash: 

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

1538 raise TypeError(msg) 

1539 

1540 builder = _ClassBuilder( 

1541 cls, 

1542 these, 

1543 auto_attribs=auto_attribs, 

1544 props=props, 

1545 has_custom_setattr=has_own_setattr, 

1546 ) 

1547 

1548 if props.added_repr: 

1549 builder.add_repr(repr_ns) 

1550 

1551 if props.added_str: 

1552 builder.add_str() 

1553 

1554 if props.added_eq: 

1555 builder.add_eq() 

1556 if props.added_ordering: 

1557 builder.add_order() 

1558 

1559 if not frozen: 

1560 builder.add_setattr() 

1561 

1562 if props.is_hashable: 

1563 builder.add_hash() 

1564 elif props.hashability is Hashability.UNHASHABLE: 

1565 builder.make_unhashable() 

1566 

1567 if props.added_init: 

1568 builder.add_init() 

1569 else: 

1570 builder.add_attrs_init() 

1571 if cache_hash: 

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

1573 raise TypeError(msg) 

1574 

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

1576 builder.add_replace() 

1577 

1578 if ( 

1579 PY_3_10_PLUS 

1580 and match_args 

1581 and not _has_own_attribute(cls, "__match_args__") 

1582 ): 

1583 builder.add_match_args() 

1584 

1585 return builder.build_class() 

1586 

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

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

1589 if maybe_cls is None: 

1590 return wrap 

1591 

1592 return wrap(maybe_cls) 

1593 

1594 

1595_attrs = attrs 

1596""" 

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

1598*attrs*. 

1599""" 

1600 

1601 

1602def _has_frozen_base_class(cls): 

1603 """ 

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

1605 __setattr__. 

1606 """ 

1607 return cls.__setattr__ is _frozen_setattrs 

1608 

1609 

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

1611 """ 

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

1613 """ 

1614 return ( 

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

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

1617 ) 

1618 

1619 

1620def _make_hash_script( 

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

1622) -> tuple[str, dict]: 

1623 attrs = tuple( 

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

1625 ) 

1626 

1627 tab = " " 

1628 

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

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

1631 globs = {} 

1632 

1633 hash_def = "def __hash__(self" 

1634 hash_func = "hash((" 

1635 closing_braces = "))" 

1636 if not cache_hash: 

1637 hash_def += "):" 

1638 else: 

1639 hash_def += ", *" 

1640 

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

1642 hash_func = "_cache_wrapper(" + hash_func 

1643 closing_braces += ")" 

1644 

1645 method_lines = [hash_def] 

1646 

1647 def append_hash_computation_lines(prefix, indent): 

1648 """ 

1649 Generate the code for actually computing the hash code. 

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

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

1652 """ 

1653 

1654 method_lines.extend( 

1655 [ 

1656 indent + prefix + hash_func, 

1657 indent + f" {type_hash},", 

1658 ] 

1659 ) 

1660 

1661 for a in attrs: 

1662 if a.eq_key: 

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

1664 globs[cmp_name] = a.eq_key 

1665 method_lines.append( 

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

1667 ) 

1668 else: 

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

1670 

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

1672 

1673 if cache_hash: 

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

1675 if frozen: 

1676 append_hash_computation_lines( 

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

1678 ) 

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

1680 else: 

1681 append_hash_computation_lines( 

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

1683 ) 

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

1685 else: 

1686 append_hash_computation_lines("return ", tab) 

1687 

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

1689 return script, globs 

1690 

1691 

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

1693 """ 

1694 Add a hash method to *cls*. 

1695 """ 

1696 script, globs = _make_hash_script( 

1697 cls, attrs, frozen=False, cache_hash=False 

1698 ) 

1699 _compile_and_eval( 

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

1701 ) 

1702 cls.__hash__ = globs["__hash__"] 

1703 return cls 

1704 

1705 

1706def __ne__(self, other): 

1707 """ 

1708 Check equality and either forward a NotImplemented or 

1709 return the result negated. 

1710 """ 

1711 result = self.__eq__(other) 

1712 if result is NotImplemented: 

1713 return NotImplemented 

1714 

1715 return not result 

1716 

1717 

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

1719 """ 

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

1721 """ 

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

1723 

1724 lines = [ 

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

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

1727 " return NotImplemented", 

1728 ] 

1729 

1730 globs = {} 

1731 if attrs: 

1732 lines.append(" return (") 

1733 for a in attrs: 

1734 if a.eq_key: 

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

1736 # Add the key function to the global namespace 

1737 # of the evaluated function. 

1738 globs[cmp_name] = a.eq_key 

1739 lines.append( 

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

1741 ) 

1742 else: 

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

1744 if a is not attrs[-1]: 

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

1746 lines.append(" )") 

1747 else: 

1748 lines.append(" return True") 

1749 

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

1751 

1752 return script, globs 

1753 

1754 

1755def _make_order(cls, attrs): 

1756 """ 

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

1758 """ 

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

1760 

1761 def attrs_to_tuple(obj): 

1762 """ 

1763 Save us some typing. 

1764 """ 

1765 return tuple( 

1766 key(value) if key else value 

1767 for value, key in ( 

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

1769 ) 

1770 ) 

1771 

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

1800 """ 

1801 Automatically created by attrs. 

1802 """ 

1803 if other.__class__ is self.__class__: 

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

1805 

1806 return NotImplemented 

1807 

1808 return __lt__, __le__, __gt__, __ge__ 

1809 

1810 

1811def _add_eq(cls, attrs=None): 

1812 """ 

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

1814 """ 

1815 if attrs is None: 

1816 attrs = cls.__attrs_attrs__ 

1817 

1818 script, globs = _make_eq_script(attrs) 

1819 _compile_and_eval( 

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

1821 ) 

1822 cls.__eq__ = globs["__eq__"] 

1823 cls.__ne__ = __ne__ 

1824 

1825 return cls 

1826 

1827 

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

1829 """ 

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

1831 """ 

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

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

1834 # callable. 

1835 attr_names_with_reprs = tuple( 

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

1837 for a in attrs 

1838 if a.repr is not False 

1839 ) 

1840 globs = { 

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

1842 } 

1843 globs["_compat"] = _compat 

1844 globs["AttributeError"] = AttributeError 

1845 globs["NOTHING"] = NOTHING 

1846 attribute_fragments = [] 

1847 for name, r, i in attr_names_with_reprs: 

1848 accessor = ( 

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

1850 ) 

1851 fragment = ( 

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

1853 if r == repr 

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

1855 ) 

1856 attribute_fragments.append(fragment) 

1857 repr_fragment = ", ".join(attribute_fragments) 

1858 

1859 if ns is None: 

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

1861 else: 

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

1863 

1864 lines = [ 

1865 "def __repr__(self):", 

1866 " try:", 

1867 " already_repring = _compat.repr_context.already_repring", 

1868 " except AttributeError:", 

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

1870 " _compat.repr_context.already_repring = already_repring", 

1871 " else:", 

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

1873 " return '...'", 

1874 " else:", 

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

1876 " try:", 

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

1878 " finally:", 

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

1880 ] 

1881 

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

1883 

1884 

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

1886 """ 

1887 Add a repr method to *cls*. 

1888 """ 

1889 if attrs is None: 

1890 attrs = cls.__attrs_attrs__ 

1891 

1892 script, globs = _make_repr_script(attrs, ns) 

1893 _compile_and_eval( 

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

1895 ) 

1896 cls.__repr__ = globs["__repr__"] 

1897 return cls 

1898 

1899 

1900def fields(cls): 

1901 """ 

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

1903 

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

1905 examples). 

1906 

1907 Args: 

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

1909 

1910 Raises: 

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

1912 

1913 attrs.exceptions.NotAnAttrsClassError: 

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

1915 

1916 Returns: 

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

1918 

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

1920 by name. 

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

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

1923 """ 

1924 generic_base = get_generic_base(cls) 

1925 

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

1927 type_ = type(cls) 

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

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

1930 raise TypeError(msg) 

1931 

1932 return fields(type_) 

1933 

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

1935 

1936 if attrs is None: 

1937 if generic_base is not None: 

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

1939 if attrs is not None: 

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

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

1942 # efficient. 

1943 cls.__attrs_attrs__ = attrs 

1944 return attrs 

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

1946 raise NotAnAttrsClassError(msg) 

1947 

1948 return attrs 

1949 

1950 

1951def fields_dict(cls): 

1952 """ 

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

1954 are the attribute names. 

1955 

1956 Args: 

1957 cls (type): Class to introspect. 

1958 

1959 Raises: 

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

1961 

1962 attrs.exceptions.NotAnAttrsClassError: 

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

1964 

1965 Returns: 

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

1967 

1968 .. versionadded:: 18.1.0 

1969 """ 

1970 if not isinstance(cls, type): 

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

1972 raise TypeError(msg) 

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

1974 if attrs is None: 

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

1976 raise NotAnAttrsClassError(msg) 

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

1978 

1979 

1980def validate(inst): 

1981 """ 

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

1983 

1984 Leaves all exceptions through. 

1985 

1986 Args: 

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

1988 """ 

1989 if _config._run_validators is False: 

1990 return 

1991 

1992 for a in fields(inst.__class__): 

1993 v = a.validator 

1994 if v is not None: 

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

1996 

1997 

1998def _is_slot_attr(a_name, base_attr_map): 

1999 """ 

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

2001 """ 

2002 cls = base_attr_map.get(a_name) 

2003 return cls and "__slots__" in cls.__dict__ 

2004 

2005 

2006def _make_init_script( 

2007 cls, 

2008 attrs, 

2009 pre_init, 

2010 pre_init_has_args, 

2011 post_init, 

2012 frozen, 

2013 slots, 

2014 cache_hash, 

2015 base_attr_map, 

2016 is_exc, 

2017 cls_on_setattr, 

2018 attrs_init, 

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

2020 has_cls_on_setattr = ( 

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

2022 ) 

2023 

2024 if frozen and has_cls_on_setattr: 

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

2026 raise ValueError(msg) 

2027 

2028 needs_cached_setattr = cache_hash or frozen 

2029 filtered_attrs = [] 

2030 attr_dict = {} 

2031 for a in attrs: 

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

2033 continue 

2034 

2035 filtered_attrs.append(a) 

2036 attr_dict[a.name] = a 

2037 

2038 if a.on_setattr is not None: 

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

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

2041 raise ValueError(msg) 

2042 

2043 needs_cached_setattr = True 

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

2045 needs_cached_setattr = True 

2046 

2047 script, globs, annotations = _attrs_to_init_script( 

2048 filtered_attrs, 

2049 frozen, 

2050 slots, 

2051 pre_init, 

2052 pre_init_has_args, 

2053 post_init, 

2054 cache_hash, 

2055 base_attr_map, 

2056 is_exc, 

2057 needs_cached_setattr, 

2058 has_cls_on_setattr, 

2059 "__attrs_init__" if attrs_init else "__init__", 

2060 ) 

2061 if cls.__module__ in sys.modules: 

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

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

2064 

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

2066 

2067 if needs_cached_setattr: 

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

2069 # setattr hooks. 

2070 globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__ 

2071 

2072 return script, globs, annotations 

2073 

2074 

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

2076 """ 

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

2078 """ 

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

2080 

2081 

2082def _setattr_with_converter( 

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

2084) -> str: 

2085 """ 

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

2087 its converter first. 

2088 """ 

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

2090 

2091 

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

2093 """ 

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

2095 relegate to _setattr. 

2096 """ 

2097 if has_on_setattr: 

2098 return _setattr(attr_name, value, True) 

2099 

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

2101 

2102 

2103def _assign_with_converter( 

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

2105) -> str: 

2106 """ 

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

2108 conversion. Otherwise relegate to _setattr_with_converter. 

2109 """ 

2110 if has_on_setattr: 

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

2112 

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

2114 

2115 

2116def _determine_setters( 

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

2118): 

2119 """ 

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

2121 and/or slotted. 

2122 """ 

2123 if frozen is True: 

2124 if slots is True: 

2125 return (), _setattr, _setattr_with_converter 

2126 

2127 # Dict frozen classes assign directly to __dict__. 

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

2129 # class. 

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

2131 

2132 def fmt_setter( 

2133 attr_name: str, value_var: str, has_on_setattr: bool 

2134 ) -> str: 

2135 if _is_slot_attr(attr_name, base_attr_map): 

2136 return _setattr(attr_name, value_var, has_on_setattr) 

2137 

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

2139 

2140 def fmt_setter_with_converter( 

2141 attr_name: str, 

2142 value_var: str, 

2143 has_on_setattr: bool, 

2144 converter: Converter, 

2145 ) -> str: 

2146 if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): 

2147 return _setattr_with_converter( 

2148 attr_name, value_var, has_on_setattr, converter 

2149 ) 

2150 

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

2152 

2153 return ( 

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

2155 fmt_setter, 

2156 fmt_setter_with_converter, 

2157 ) 

2158 

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

2160 return (), _assign, _assign_with_converter 

2161 

2162 

2163def _attrs_to_init_script( 

2164 attrs: list[Attribute], 

2165 is_frozen: bool, 

2166 is_slotted: bool, 

2167 call_pre_init: bool, 

2168 pre_init_has_args: bool, 

2169 call_post_init: bool, 

2170 does_cache_hash: bool, 

2171 base_attr_map: dict[str, type], 

2172 is_exc: bool, 

2173 needs_cached_setattr: bool, 

2174 has_cls_on_setattr: bool, 

2175 method_name: str, 

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

2177 """ 

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

2179 annotations for the initializer. 

2180 

2181 The globals are required by the generated script. 

2182 """ 

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

2184 

2185 if needs_cached_setattr: 

2186 lines.append( 

2187 # Circumvent the __setattr__ descriptor to save one lookup per 

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

2189 # does_cache_hash is True. 

2190 "_setattr = _cached_setattr_get(self)" 

2191 ) 

2192 

2193 extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters( 

2194 is_frozen, is_slotted, base_attr_map 

2195 ) 

2196 lines.extend(extra_lines) 

2197 

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

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

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

2201 attrs_to_validate = [] 

2202 

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

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

2205 names_for_globals = {} 

2206 annotations = {"return": None} 

2207 

2208 for a in attrs: 

2209 if a.validator: 

2210 attrs_to_validate.append(a) 

2211 

2212 attr_name = a.name 

2213 has_on_setattr = a.on_setattr is not None or ( 

2214 a.on_setattr is not setters.NO_OP and has_cls_on_setattr 

2215 ) 

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

2217 # explicitly provided 

2218 arg_name = a.alias 

2219 

2220 has_factory = isinstance(a.default, Factory) 

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

2222 

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

2224 converter = Converter(a.converter) 

2225 else: 

2226 converter = a.converter 

2227 

2228 if a.init is False: 

2229 if has_factory: 

2230 init_factory_name = _INIT_FACTORY_PAT % (a.name,) 

2231 if converter is not None: 

2232 lines.append( 

2233 fmt_setter_with_converter( 

2234 attr_name, 

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

2236 has_on_setattr, 

2237 converter, 

2238 ) 

2239 ) 

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

2241 converter.converter 

2242 ) 

2243 else: 

2244 lines.append( 

2245 fmt_setter( 

2246 attr_name, 

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

2248 has_on_setattr, 

2249 ) 

2250 ) 

2251 names_for_globals[init_factory_name] = a.default.factory 

2252 elif converter is not None: 

2253 lines.append( 

2254 fmt_setter_with_converter( 

2255 attr_name, 

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

2257 has_on_setattr, 

2258 converter, 

2259 ) 

2260 ) 

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

2262 converter.converter 

2263 ) 

2264 else: 

2265 lines.append( 

2266 fmt_setter( 

2267 attr_name, 

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

2269 has_on_setattr, 

2270 ) 

2271 ) 

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

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

2274 if a.kw_only: 

2275 kw_only_args.append(arg) 

2276 else: 

2277 args.append(arg) 

2278 pre_init_args.append(arg_name) 

2279 

2280 if converter is not None: 

2281 lines.append( 

2282 fmt_setter_with_converter( 

2283 attr_name, arg_name, has_on_setattr, converter 

2284 ) 

2285 ) 

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

2287 converter.converter 

2288 ) 

2289 else: 

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

2291 

2292 elif has_factory: 

2293 arg = f"{arg_name}=NOTHING" 

2294 if a.kw_only: 

2295 kw_only_args.append(arg) 

2296 else: 

2297 args.append(arg) 

2298 pre_init_args.append(arg_name) 

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

2300 

2301 init_factory_name = _INIT_FACTORY_PAT % (a.name,) 

2302 if converter is not None: 

2303 lines.append( 

2304 " " 

2305 + fmt_setter_with_converter( 

2306 attr_name, arg_name, has_on_setattr, converter 

2307 ) 

2308 ) 

2309 lines.append("else:") 

2310 lines.append( 

2311 " " 

2312 + fmt_setter_with_converter( 

2313 attr_name, 

2314 init_factory_name + "(" + maybe_self + ")", 

2315 has_on_setattr, 

2316 converter, 

2317 ) 

2318 ) 

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

2320 converter.converter 

2321 ) 

2322 else: 

2323 lines.append( 

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

2325 ) 

2326 lines.append("else:") 

2327 lines.append( 

2328 " " 

2329 + fmt_setter( 

2330 attr_name, 

2331 init_factory_name + "(" + maybe_self + ")", 

2332 has_on_setattr, 

2333 ) 

2334 ) 

2335 names_for_globals[init_factory_name] = a.default.factory 

2336 else: 

2337 if a.kw_only: 

2338 kw_only_args.append(arg_name) 

2339 else: 

2340 args.append(arg_name) 

2341 pre_init_args.append(arg_name) 

2342 

2343 if converter is not None: 

2344 lines.append( 

2345 fmt_setter_with_converter( 

2346 attr_name, arg_name, has_on_setattr, converter 

2347 ) 

2348 ) 

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

2350 converter.converter 

2351 ) 

2352 else: 

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

2354 

2355 if a.init is True: 

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

2357 annotations[arg_name] = a.type 

2358 elif converter is not None and converter._first_param_type: 

2359 # Use the type from the converter if present. 

2360 annotations[arg_name] = converter._first_param_type 

2361 

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

2363 names_for_globals["_config"] = _config 

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

2365 for a in attrs_to_validate: 

2366 val_name = "__attr_validator_" + a.name 

2367 attr_name = "__attr_" + a.name 

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

2369 names_for_globals[val_name] = a.validator 

2370 names_for_globals[attr_name] = a 

2371 

2372 if call_post_init: 

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

2374 

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

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

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

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

2379 # would result in silent bugs. 

2380 if does_cache_hash: 

2381 if is_frozen: 

2382 if is_slotted: 

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

2384 else: 

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

2386 else: 

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

2388 lines.append(init_hash_cache) 

2389 

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

2391 # initialization. 

2392 if is_exc: 

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

2394 

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

2396 

2397 args = ", ".join(args) 

2398 pre_init_args = ", ".join(pre_init_args) 

2399 if kw_only_args: 

2400 # leading comma & kw_only args 

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

2402 pre_init_kw_only_args = ", ".join( 

2403 [ 

2404 f"{kw_arg_name}={kw_arg_name}" 

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

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

2407 ] 

2408 ) 

2409 pre_init_args += ", " if pre_init_args else "" 

2410 pre_init_args += pre_init_kw_only_args 

2411 

2412 if call_pre_init and pre_init_has_args: 

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

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

2415 

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

2417 NL = "\n " 

2418 return ( 

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

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

2421""", 

2422 names_for_globals, 

2423 annotations, 

2424 ) 

2425 

2426 

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

2428 """ 

2429 The default __init__ parameter name for a field. 

2430 

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

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

2433 """ 

2434 

2435 return name.lstrip("_") 

2436 

2437 

2438class Attribute: 

2439 """ 

2440 *Read-only* representation of an attribute. 

2441 

2442 .. warning:: 

2443 

2444 You should never instantiate this class yourself. 

2445 

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

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

2448 

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

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

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

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

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

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

2455 from a base class. 

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

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

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

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

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

2461 

2462 Instances of this class are frequently used for introspection purposes 

2463 like: 

2464 

2465 - `fields` returns a tuple of them. 

2466 - Validators get them passed as the first argument. 

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

2468 them. 

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

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

2471 

2472 

2473 .. versionadded:: 20.1.0 *inherited* 

2474 .. versionadded:: 20.1.0 *on_setattr* 

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

2476 equality checks and hashing anymore. 

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

2478 .. versionadded:: 22.2.0 *alias* 

2479 .. versionadded:: 26.1.0 *alias_is_default* 

2480 

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

2482 """ 

2483 

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

2485 # instantiation. 

2486 __slots__ = ( # noqa: RUF023 

2487 "name", 

2488 "default", 

2489 "validator", 

2490 "repr", 

2491 "eq", 

2492 "eq_key", 

2493 "order", 

2494 "order_key", 

2495 "hash", 

2496 "init", 

2497 "metadata", 

2498 "type", 

2499 "converter", 

2500 "kw_only", 

2501 "inherited", 

2502 "on_setattr", 

2503 "alias", 

2504 "alias_is_default", 

2505 ) 

2506 

2507 def __init__( 

2508 self, 

2509 name, 

2510 default, 

2511 validator, 

2512 repr, 

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

2514 hash, 

2515 init, 

2516 inherited, 

2517 metadata=None, 

2518 type=None, 

2519 converter=None, 

2520 kw_only=False, 

2521 eq=None, 

2522 eq_key=None, 

2523 order=None, 

2524 order_key=None, 

2525 on_setattr=None, 

2526 alias=None, 

2527 alias_is_default=None, 

2528 ): 

2529 eq, eq_key, order, order_key = _determine_attrib_eq_order( 

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

2531 ) 

2532 

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

2534 bound_setattr = _OBJ_SETATTR.__get__(self) 

2535 

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

2537 # themselves. 

2538 bound_setattr("name", name) 

2539 bound_setattr("default", default) 

2540 bound_setattr("validator", validator) 

2541 bound_setattr("repr", repr) 

2542 bound_setattr("eq", eq) 

2543 bound_setattr("eq_key", eq_key) 

2544 bound_setattr("order", order) 

2545 bound_setattr("order_key", order_key) 

2546 bound_setattr("hash", hash) 

2547 bound_setattr("init", init) 

2548 bound_setattr("converter", converter) 

2549 bound_setattr( 

2550 "metadata", 

2551 ( 

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

2553 if metadata 

2554 else _EMPTY_METADATA_SINGLETON 

2555 ), 

2556 ) 

2557 bound_setattr("type", type) 

2558 bound_setattr("kw_only", kw_only) 

2559 bound_setattr("inherited", inherited) 

2560 bound_setattr("on_setattr", on_setattr) 

2561 bound_setattr("alias", alias) 

2562 bound_setattr( 

2563 "alias_is_default", 

2564 alias is None if alias_is_default is None else alias_is_default, 

2565 ) 

2566 

2567 def __setattr__(self, name, value): 

2568 raise FrozenInstanceError 

2569 

2570 @classmethod 

2571 def from_counting_attr( 

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

2573 ): 

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

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

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

2577 if type is None: 

2578 type = ca.type 

2579 elif ca.type is not None: 

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

2581 raise ValueError(msg) 

2582 return cls( 

2583 name, 

2584 ca._default, 

2585 ca._validator, 

2586 ca.repr, 

2587 None, 

2588 ca.hash, 

2589 ca.init, 

2590 False, 

2591 ca.metadata, 

2592 type, 

2593 ca._converter, 

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

2595 ca.eq, 

2596 ca.eq_key, 

2597 ca.order, 

2598 ca.order_key, 

2599 ca.on_setattr, 

2600 ca.alias, 

2601 ca.alias is None, 

2602 ) 

2603 

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

2605 def evolve(self, **changes): 

2606 """ 

2607 Copy *self* and apply *changes*. 

2608 

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

2610 with :class:`attrs.Attribute`. 

2611 

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

2613 

2614 .. versionadded:: 20.3.0 

2615 """ 

2616 new = copy.copy(self) 

2617 

2618 new._setattrs(changes.items()) 

2619 

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

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

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

2623 elif ( 

2624 "name" in changes 

2625 and "alias" not in changes 

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

2627 and self.alias_is_default 

2628 ): 

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

2630 _OBJ_SETATTR.__get__(new)( 

2631 "alias", _default_init_alias_for(new.name) 

2632 ) 

2633 

2634 return new 

2635 

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

2637 def __getstate__(self): 

2638 """ 

2639 Play nice with pickle. 

2640 """ 

2641 return tuple( 

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

2643 for name in self.__slots__ 

2644 ) 

2645 

2646 def __setstate__(self, state): 

2647 """ 

2648 Play nice with pickle. 

2649 """ 

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

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

2652 # heuristically. 

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

2654 alias_is_default = state_dict.get( 

2655 "alias" 

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

2657 state_dict["name"] 

2658 ) 

2659 state = (*state, alias_is_default) 

2660 

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

2662 

2663 def _setattrs(self, name_values_pairs): 

2664 bound_setattr = _OBJ_SETATTR.__get__(self) 

2665 for name, value in name_values_pairs: 

2666 if name != "metadata": 

2667 bound_setattr(name, value) 

2668 else: 

2669 bound_setattr( 

2670 name, 

2671 ( 

2672 types.MappingProxyType(dict(value)) 

2673 if value 

2674 else _EMPTY_METADATA_SINGLETON 

2675 ), 

2676 ) 

2677 

2678 

2679_a = [ 

2680 Attribute( 

2681 name=name, 

2682 default=NOTHING, 

2683 validator=None, 

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

2685 cmp=None, 

2686 eq=True, 

2687 order=False, 

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

2689 init=True, 

2690 inherited=False, 

2691 alias=_default_init_alias_for(name), 

2692 ) 

2693 for name in Attribute.__slots__ 

2694] 

2695 

2696Attribute = _add_hash( 

2697 _add_eq( 

2698 _add_repr(Attribute, attrs=_a), 

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

2700 ), 

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

2702) 

2703 

2704 

2705class _CountingAttr: 

2706 """ 

2707 Intermediate representation of attributes that uses a counter to preserve 

2708 the order in which the attributes have been defined. 

2709 

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

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

2712 """ 

2713 

2714 __slots__ = ( 

2715 "_converter", 

2716 "_default", 

2717 "_validator", 

2718 "alias", 

2719 "counter", 

2720 "eq", 

2721 "eq_key", 

2722 "hash", 

2723 "init", 

2724 "kw_only", 

2725 "metadata", 

2726 "on_setattr", 

2727 "order", 

2728 "order_key", 

2729 "repr", 

2730 "type", 

2731 ) 

2732 __attrs_attrs__ = ( 

2733 *tuple( 

2734 Attribute( 

2735 name=name, 

2736 alias=_default_init_alias_for(name), 

2737 default=NOTHING, 

2738 validator=None, 

2739 repr=True, 

2740 cmp=None, 

2741 hash=True, 

2742 init=True, 

2743 kw_only=False, 

2744 eq=True, 

2745 eq_key=None, 

2746 order=False, 

2747 order_key=None, 

2748 inherited=False, 

2749 on_setattr=None, 

2750 ) 

2751 for name in ( 

2752 "counter", 

2753 "_default", 

2754 "repr", 

2755 "eq", 

2756 "order", 

2757 "hash", 

2758 "init", 

2759 "on_setattr", 

2760 "alias", 

2761 ) 

2762 ), 

2763 Attribute( 

2764 name="metadata", 

2765 alias="metadata", 

2766 default=None, 

2767 validator=None, 

2768 repr=True, 

2769 cmp=None, 

2770 hash=False, 

2771 init=True, 

2772 kw_only=False, 

2773 eq=True, 

2774 eq_key=None, 

2775 order=False, 

2776 order_key=None, 

2777 inherited=False, 

2778 on_setattr=None, 

2779 ), 

2780 ) 

2781 cls_counter = 0 

2782 

2783 def __init__( 

2784 self, 

2785 default, 

2786 validator, 

2787 repr, 

2788 cmp, 

2789 hash, 

2790 init, 

2791 converter, 

2792 metadata, 

2793 type, 

2794 kw_only, 

2795 eq, 

2796 eq_key, 

2797 order, 

2798 order_key, 

2799 on_setattr, 

2800 alias, 

2801 ): 

2802 _CountingAttr.cls_counter += 1 

2803 self.counter = _CountingAttr.cls_counter 

2804 self._default = default 

2805 self._validator = validator 

2806 self._converter = converter 

2807 self.repr = repr 

2808 self.eq = eq 

2809 self.eq_key = eq_key 

2810 self.order = order 

2811 self.order_key = order_key 

2812 self.hash = hash 

2813 self.init = init 

2814 self.metadata = metadata 

2815 self.type = type 

2816 self.kw_only = kw_only 

2817 self.on_setattr = on_setattr 

2818 self.alias = alias 

2819 

2820 def validator(self, meth): 

2821 """ 

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

2823 

2824 Returns *meth* unchanged. 

2825 

2826 .. versionadded:: 17.1.0 

2827 """ 

2828 if self._validator is None: 

2829 self._validator = meth 

2830 else: 

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

2832 return meth 

2833 

2834 def default(self, meth): 

2835 """ 

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

2837 

2838 Returns *meth* unchanged. 

2839 

2840 Raises: 

2841 DefaultAlreadySetError: If default has been set before. 

2842 

2843 .. versionadded:: 17.1.0 

2844 """ 

2845 if self._default is not NOTHING: 

2846 raise DefaultAlreadySetError 

2847 

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

2849 

2850 return meth 

2851 

2852 def converter(self, meth): 

2853 """ 

2854 Decorator that appends *meth* to the list of converters. 

2855 

2856 Returns *meth* unchanged. 

2857 

2858 .. versionadded:: 26.2.0 

2859 """ 

2860 decorated_converter = Converter( 

2861 lambda value, _self, field: meth(_self, field, value), 

2862 takes_self=True, 

2863 takes_field=True, 

2864 ) 

2865 if self._converter is None: 

2866 self._converter = decorated_converter 

2867 else: 

2868 self._converter = pipe(self._converter, decorated_converter) 

2869 

2870 return meth 

2871 

2872 

2873_CountingAttr = _add_eq(_add_repr(_CountingAttr)) 

2874 

2875 

2876class ClassProps: 

2877 """ 

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

2879 `define()` decorators. 

2880 

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

2882 to construct the final class. 

2883 

2884 Warning: 

2885 

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

2887 strict backwards-compatibility guarantees. 

2888 

2889 

2890 Attributes: 

2891 is_exception (bool): 

2892 Whether the class is treated as an exception class. 

2893 

2894 is_slotted (bool): 

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

2896 

2897 has_weakref_slot (bool): 

2898 Whether the class has a slot for weak references. 

2899 

2900 is_frozen (bool): 

2901 Whether the class is frozen. 

2902 

2903 kw_only (KeywordOnly): 

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

2905 ``__init__`` method. 

2906 

2907 collected_fields_by_mro (bool): 

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

2909 That is, correctly but unlike `dataclasses`. 

2910 

2911 added_init (bool): 

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

2913 

2914 added_repr (bool): 

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

2916 

2917 added_eq (bool): 

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

2919 

2920 added_ordering (bool): 

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

2922 

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

2924 

2925 added_match_args (bool): 

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

2927 fields. 

2928 

2929 added_str (bool): 

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

2931 

2932 added_pickling (bool): 

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

2934 ``__setstate__`` methods for `pickle`. 

2935 

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

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

2938 

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

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

2941 

2942 .. versionadded:: 25.4.0 

2943 """ 

2944 

2945 class Hashability(enum.Enum): 

2946 """ 

2947 The hashability of a class. 

2948 

2949 .. versionadded:: 25.4.0 

2950 """ 

2951 

2952 HASHABLE = "hashable" 

2953 """Write a ``__hash__``.""" 

2954 HASHABLE_CACHED = "hashable_cache" 

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

2956 UNHASHABLE = "unhashable" 

2957 """Set ``__hash__`` to ``None``.""" 

2958 LEAVE_ALONE = "leave_alone" 

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

2960 

2961 class KeywordOnly(enum.Enum): 

2962 """ 

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

2964 

2965 .. versionadded:: 25.4.0 

2966 """ 

2967 

2968 NO = "no" 

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

2970 YES = "yes" 

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

2972 FORCE = "force" 

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

2974 

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

2976 "is_exception", 

2977 "is_slotted", 

2978 "has_weakref_slot", 

2979 "is_frozen", 

2980 "kw_only", 

2981 "collected_fields_by_mro", 

2982 "added_init", 

2983 "added_repr", 

2984 "added_eq", 

2985 "added_ordering", 

2986 "hashability", 

2987 "added_match_args", 

2988 "added_str", 

2989 "added_pickling", 

2990 "on_setattr_hook", 

2991 "field_transformer", 

2992 ) 

2993 

2994 def __init__( 

2995 self, 

2996 is_exception, 

2997 is_slotted, 

2998 has_weakref_slot, 

2999 is_frozen, 

3000 kw_only, 

3001 collected_fields_by_mro, 

3002 added_init, 

3003 added_repr, 

3004 added_eq, 

3005 added_ordering, 

3006 hashability, 

3007 added_match_args, 

3008 added_str, 

3009 added_pickling, 

3010 on_setattr_hook, 

3011 field_transformer, 

3012 ): 

3013 self.is_exception = is_exception 

3014 self.is_slotted = is_slotted 

3015 self.has_weakref_slot = has_weakref_slot 

3016 self.is_frozen = is_frozen 

3017 self.kw_only = kw_only 

3018 self.collected_fields_by_mro = collected_fields_by_mro 

3019 self.added_init = added_init 

3020 self.added_repr = added_repr 

3021 self.added_eq = added_eq 

3022 self.added_ordering = added_ordering 

3023 self.hashability = hashability 

3024 self.added_match_args = added_match_args 

3025 self.added_str = added_str 

3026 self.added_pickling = added_pickling 

3027 self.on_setattr_hook = on_setattr_hook 

3028 self.field_transformer = field_transformer 

3029 

3030 @property 

3031 def is_hashable(self): 

3032 return ( 

3033 self.hashability is ClassProps.Hashability.HASHABLE 

3034 or self.hashability is ClassProps.Hashability.HASHABLE_CACHED 

3035 ) 

3036 

3037 

3038_cas = [ 

3039 Attribute( 

3040 name=name, 

3041 default=NOTHING, 

3042 validator=None, 

3043 repr=True, 

3044 cmp=None, 

3045 eq=True, 

3046 order=False, 

3047 hash=True, 

3048 init=True, 

3049 inherited=False, 

3050 alias=_default_init_alias_for(name), 

3051 ) 

3052 for name in ClassProps.__slots__ 

3053] 

3054 

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

3056 

3057 

3058class Factory: 

3059 """ 

3060 Stores a factory callable. 

3061 

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

3063 generate a new value. 

3064 

3065 Args: 

3066 factory (typing.Callable): 

3067 A callable that takes either none or exactly one mandatory 

3068 positional argument depending on *takes_self*. 

3069 

3070 takes_self (bool): 

3071 Pass the partially initialized instance that is being initialized 

3072 as a positional argument. 

3073 

3074 .. versionadded:: 17.1.0 *takes_self* 

3075 """ 

3076 

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

3078 

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

3080 self.factory = factory 

3081 self.takes_self = takes_self 

3082 

3083 def __getstate__(self): 

3084 """ 

3085 Play nice with pickle. 

3086 """ 

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

3088 

3089 def __setstate__(self, state): 

3090 """ 

3091 Play nice with pickle. 

3092 """ 

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

3094 setattr(self, name, value) 

3095 

3096 

3097_f = [ 

3098 Attribute( 

3099 name=name, 

3100 default=NOTHING, 

3101 validator=None, 

3102 repr=True, 

3103 cmp=None, 

3104 eq=True, 

3105 order=False, 

3106 hash=True, 

3107 init=True, 

3108 inherited=False, 

3109 ) 

3110 for name in Factory.__slots__ 

3111] 

3112 

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

3114 

3115 

3116class Converter: 

3117 """ 

3118 Stores a converter callable. 

3119 

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

3121 arguments are passed in the order they are documented. 

3122 

3123 Args: 

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

3125 

3126 takes_self (bool): 

3127 Pass the partially initialized instance that is being initialized 

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

3129 

3130 takes_field (bool): 

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

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

3133 

3134 .. versionadded:: 24.1.0 

3135 """ 

3136 

3137 __slots__ = ( 

3138 "__call__", 

3139 "_first_param_type", 

3140 "_global_name", 

3141 "converter", 

3142 "takes_field", 

3143 "takes_self", 

3144 ) 

3145 

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

3147 self.converter = converter 

3148 self.takes_self = takes_self 

3149 self.takes_field = takes_field 

3150 

3151 ex = _AnnotationExtractor(converter) 

3152 self._first_param_type = ex.get_first_param_type() 

3153 

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

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

3156 elif self.takes_self and not self.takes_field: 

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

3158 value, instance 

3159 ) 

3160 elif not self.takes_self and self.takes_field: 

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

3162 value, field 

3163 ) 

3164 else: 

3165 self.__call__ = self.converter 

3166 

3167 rt = ex.get_return_type() 

3168 if rt is not None: 

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

3170 

3171 @staticmethod 

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

3173 """ 

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

3175 would have. 

3176 """ 

3177 return f"__attr_converter_{attr_name}" 

3178 

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

3180 """ 

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

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

3183 `self.takes_self` and `self.takes_field`. 

3184 """ 

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

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

3187 

3188 if self.takes_self and self.takes_field: 

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

3190 

3191 if self.takes_self: 

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

3193 

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

3195 

3196 def __getstate__(self): 

3197 """ 

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

3199 computed when loading. 

3200 """ 

3201 return { 

3202 "converter": self.converter, 

3203 "takes_self": self.takes_self, 

3204 "takes_field": self.takes_field, 

3205 } 

3206 

3207 def __setstate__(self, state): 

3208 """ 

3209 Load instance from state. 

3210 """ 

3211 self.__init__(**state) 

3212 

3213 

3214_f = [ 

3215 Attribute( 

3216 name=name, 

3217 default=NOTHING, 

3218 validator=None, 

3219 repr=True, 

3220 cmp=None, 

3221 eq=True, 

3222 order=False, 

3223 hash=True, 

3224 init=True, 

3225 inherited=False, 

3226 ) 

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

3228] 

3229 

3230Converter = _add_hash( 

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

3232) 

3233 

3234 

3235def make_class( 

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

3237): 

3238 r""" 

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

3240 

3241 .. note:: 

3242 

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

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

3245 

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

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

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

3249 

3250 .. warning:: 

3251 

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

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

3254 you. 

3255 

3256 Args: 

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

3258 

3259 attrs (list | dict): 

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

3261 s / `attrs.field`\ s. 

3262 

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

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

3265 attributes is used. 

3266 

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

3268 

3269 class_body (dict): 

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

3271 

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

3273 

3274 Returns: 

3275 type: A new class with *attrs*. 

3276 

3277 .. versionadded:: 17.1.0 *bases* 

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

3279 .. versionchanged:: 23.2.0 *class_body* 

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

3281 """ 

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

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

3284 

3285 if isinstance(attrs, dict): 

3286 cls_dict = attrs 

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

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

3289 else: 

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

3291 raise TypeError(msg) 

3292 

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

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

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

3296 

3297 body = {} 

3298 if class_body is not None: 

3299 body.update(class_body) 

3300 if pre_init is not None: 

3301 body["__attrs_pre_init__"] = pre_init 

3302 if post_init is not None: 

3303 body["__attrs_post_init__"] = post_init 

3304 if user_init is not None: 

3305 body["__init__"] = user_init 

3306 

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

3308 

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

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

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

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

3313 with contextlib.suppress(AttributeError, ValueError): 

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

3315 "__name__", "__main__" 

3316 ) 

3317 

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

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

3320 ( 

3321 attributes_arguments["eq"], 

3322 attributes_arguments["order"], 

3323 ) = _determine_attrs_eq_order( 

3324 cmp, 

3325 attributes_arguments.get("eq"), 

3326 attributes_arguments.get("order"), 

3327 True, 

3328 ) 

3329 

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

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

3332 cls.__annotations__ = { 

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

3334 } 

3335 return cls 

3336 

3337 

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

3339# import into .validators / .converters. 

3340 

3341 

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

3343class _AndValidator: 

3344 """ 

3345 Compose many validators to a single one. 

3346 """ 

3347 

3348 _validators = attrib() 

3349 

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

3351 for v in self._validators: 

3352 v(inst, attr, value) 

3353 

3354 

3355def and_(*validators): 

3356 """ 

3357 A validator that composes multiple validators into one. 

3358 

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

3360 

3361 Args: 

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

3363 Arbitrary number of validators. 

3364 

3365 .. versionadded:: 17.1.0 

3366 """ 

3367 vals = [] 

3368 for validator in validators: 

3369 vals.extend( 

3370 validator._validators 

3371 if isinstance(validator, _AndValidator) 

3372 else [validator] 

3373 ) 

3374 

3375 return _AndValidator(tuple(vals)) 

3376 

3377 

3378def pipe(*converters): 

3379 """ 

3380 A converter that composes multiple converters into one. 

3381 

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

3383 *last* value. 

3384 

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

3386 have any. 

3387 

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

3389 Arbitrary number of converters. 

3390 

3391 .. versionadded:: 20.1.0 

3392 """ 

3393 

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

3395 

3396 if return_instance: 

3397 

3398 def pipe_converter(val, inst, field): 

3399 for c in converters: 

3400 val = ( 

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

3402 ) 

3403 

3404 return val 

3405 

3406 else: 

3407 

3408 def pipe_converter(val): 

3409 for c in converters: 

3410 val = c(val) 

3411 

3412 return val 

3413 

3414 if not converters: 

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

3416 A = TypeVar("A") 

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

3418 else: 

3419 # Get parameter type from first converter. 

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

3421 if t: 

3422 pipe_converter.__annotations__["val"] = t 

3423 

3424 last = converters[-1] 

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

3426 last = last.__call__ 

3427 

3428 # Get return type from last converter. 

3429 rt = _AnnotationExtractor(last).get_return_type() 

3430 if rt: 

3431 pipe_converter.__annotations__["return"] = rt 

3432 

3433 if return_instance: 

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

3435 return pipe_converter