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

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

1164 statements  

1# SPDX-License-Identifier: MIT 

2 

3from __future__ import annotations 

4 

5import abc 

6import contextlib 

7import enum 

8import itertools 

9import linecache 

10import sys 

11import types 

12import unicodedata 

13import weakref 

14 

15from collections.abc import Callable, Mapping 

16from functools import cached_property 

17from typing import Any, NamedTuple, TypeVar 

18 

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

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

21from . import _compat, _config, setters 

22from ._compat import ( 

23 PY_3_11_PLUS, 

24 PY_3_13_PLUS, 

25 _AnnotationExtractor, 

26 _get_annotations, 

27 _lazy_is_generator, 

28 get_generic_base, 

29) 

30from .exceptions import ( 

31 DefaultAlreadySetError, 

32 FrozenInstanceError, 

33 NotAnAttrsClassError, 

34 UnannotatedAttributeError, 

35) 

36 

37 

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

39_OBJ_SETATTR = object.__setattr__ 

40_INIT_FACTORY_PAT = "__attr_factory_%s" 

41_CLASSVAR_PREFIXES = ( 

42 "typing.ClassVar", 

43 "t.ClassVar", 

44 "ClassVar", 

45 "typing_extensions.ClassVar", 

46) 

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

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

49# (when slots=True) 

50_HASH_CACHE_FIELD = "_attrs_cached_hash" 

51 

52_EMPTY_METADATA_SINGLETON = types.MappingProxyType({}) 

53 

54# Unique object for unequivocal getattr() defaults. 

55_SENTINEL = object() 

56 

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

58 

59 

60class _Nothing(enum.Enum): 

61 """ 

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

63 

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

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

66 

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

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

69 """ 

70 

71 NOTHING = enum.auto() 

72 

73 def __repr__(self): 

74 return "NOTHING" 

75 

76 def __bool__(self): 

77 return False 

78 

79 

80NOTHING = _Nothing.NOTHING 

81""" 

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

83 

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

85""" 

86 

87 

88class _CacheHashWrapper(int): 

89 """ 

90 An integer subclass that pickles / copies as None 

91 

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

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

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

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

96 

97 See GH #613 for more details. 

98 """ 

99 

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

101 return _none_constructor, _args 

102 

103 

104def attrib( 

105 default=NOTHING, 

106 validator=None, 

107 repr=True, 

108 cmp=None, 

109 hash=None, 

110 init=True, 

111 metadata=None, 

112 type=None, 

113 converter=None, 

114 factory=None, 

115 kw_only=None, 

116 eq=None, 

117 order=None, 

118 on_setattr=None, 

119 alias=None, 

120): 

121 """ 

122 Create a new field / attribute on a class. 

123 

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

125 

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

127 though). 

128 

129 .. warning:: 

130 

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

132 `attr.s` (or similar)! 

133 

134 

135 .. versionadded:: 15.2.0 *convert* 

136 .. versionadded:: 16.3.0 *metadata* 

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

138 .. versionchanged:: 17.1.0 

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

140 .. versionadded:: 17.3.0 *type* 

141 .. deprecated:: 17.4.0 *convert* 

142 .. versionadded:: 17.4.0 

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

144 consistency with other noun-based arguments. 

145 .. versionadded:: 18.1.0 

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

147 .. versionadded:: 18.2.0 *kw_only* 

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

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

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

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

152 .. versionadded:: 20.1.0 *on_setattr* 

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

154 .. versionchanged:: 21.1.0 

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

156 .. versionchanged:: 21.1.0 *cmp* undeprecated 

157 .. versionadded:: 22.2.0 *alias* 

158 .. versionchanged:: 25.4.0 

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

160 None. 

161 .. versionchanged:: 26.2.0 

162 *on_setattr* hooks can now be generator functions to run code before and 

163 after an attribute is set. 

164 """ 

165 eq, eq_key, order, order_key = _determine_attrib_eq_order( 

166 cmp, eq, order, True 

167 ) 

168 

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

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

171 raise TypeError(msg) 

172 

173 if factory is not None: 

174 if default is not NOTHING: 

175 msg = ( 

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

177 ) 

178 raise ValueError(msg) 

179 if not callable(factory): 

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

181 raise ValueError(msg) 

182 default = Factory(factory) 

183 

184 if metadata is None: 

185 metadata = {} 

186 

187 # Apply syntactic sugar by auto-wrapping. 

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

189 on_setattr = setters.pipe(*on_setattr) 

190 

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

192 validator = and_(*validator) 

193 

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

195 converter = pipe(*converter) 

196 

197 return _CountingAttr( 

198 default=default, 

199 validator=validator, 

200 repr=repr, 

201 cmp=None, 

202 hash=hash, 

203 init=init, 

204 converter=converter, 

205 metadata=metadata, 

206 type=type, 

207 kw_only=kw_only, 

208 eq=eq, 

209 eq_key=eq_key, 

210 order=order, 

211 order_key=order_key, 

212 on_setattr=on_setattr, 

213 alias=alias, 

214 ) 

215 

216 

217def _compile_and_eval( 

218 script: str, 

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

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

221 filename: str = "", 

222) -> None: 

223 """ 

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

225 variables. 

226 """ 

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

228 eval(bytecode, globs, locs) 

229 

230 

231def _linecache_and_compile( 

232 script: str, 

233 filename: str, 

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

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

236) -> dict[str, Any]: 

237 """ 

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

239 """ 

240 

241 locs = {} if locals is None else locals 

242 

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

244 # we add a fake linecache entry. 

245 count = 1 

246 base_filename = filename 

247 while True: 

248 linecache_tuple = ( 

249 len(script), 

250 None, 

251 script.splitlines(True), 

252 filename, 

253 ) 

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

255 if old_val == linecache_tuple: 

256 break 

257 

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

259 count += 1 

260 

261 _compile_and_eval(script, globs, locs, filename) 

262 

263 return locs 

264 

265 

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

267 """ 

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

269 

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

271 

272 class MyClassAttributes(tuple): 

273 __slots__ = () 

274 x = property(itemgetter(0)) 

275 """ 

276 attr_class_name = f"{cls_name}Attributes" 

277 body = {} 

278 for i, attr_name in enumerate(attr_names): 

279 

280 def getter(self, i=i): 

281 return self[i] 

282 

283 body[attr_name] = property(getter) 

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

285 

286 

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

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

289class _Attributes(NamedTuple): 

290 attrs: type 

291 base_attrs: list[Attribute] 

292 base_attrs_map: dict[str, type] 

293 

294 

295def _is_class_var(annot): 

296 """ 

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

298 

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

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

301 disadvantage compared to plain old classes. 

302 """ 

303 annot = getattr(annot, "__forward_arg__", annot) 

304 annot = str(annot) 

305 

306 # Annotation can be quoted. 

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

308 annot = annot[1:-1] 

309 

310 return annot.startswith(_CLASSVAR_PREFIXES) 

311 

312 

313def _has_own_attribute(cls, attrib_name): 

314 """ 

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

316 """ 

317 return attrib_name in cls.__dict__ 

318 

319 

320def _collect_base_attrs( 

321 cls, taken_attr_names 

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

323 """ 

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

325 """ 

326 base_attrs = [] 

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

328 

329 # Traverse the MRO and collect attributes. 

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

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

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

333 continue 

334 

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

336 base_attrs.append(a) 

337 base_attr_map[a.name] = base_cls 

338 

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

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

341 # instance. 

342 filtered = [] 

343 seen = set() 

344 for a in reversed(base_attrs): 

345 if a.name in seen: 

346 continue 

347 filtered.insert(0, a) 

348 seen.add(a.name) 

349 

350 return filtered, base_attr_map 

351 

352 

353def _collect_base_attrs_broken(cls, taken_attr_names): 

354 """ 

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

356 

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

358 

359 Adhere to the old incorrect behavior. 

360 

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

362 leads to the buggy behavior reported in #428. 

363 """ 

364 base_attrs = [] 

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

366 

367 # Traverse the MRO and collect attributes. 

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

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

370 if a.name in taken_attr_names: 

371 continue 

372 

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

374 taken_attr_names.add(a.name) 

375 base_attrs.append(a) 

376 base_attr_map[a.name] = base_cls 

377 

378 return base_attrs, base_attr_map 

379 

380 

381def _transform_attrs( 

382 cls, 

383 these, 

384 auto_attribs, 

385 kw_only, 

386 collect_by_mro, 

387 field_transformer, 

388) -> _Attributes: 

389 """ 

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

391 

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

393 

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

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

396 

397 Return an `_Attributes`. 

398 """ 

399 cd = cls.__dict__ 

400 anns = _get_annotations(cls) 

401 

402 if these is not None: 

403 ca_list = list(these.items()) 

404 elif auto_attribs is True: 

405 ca_names = { 

406 name 

407 for name, attr in cd.items() 

408 if attr.__class__ is _CountingAttr 

409 } 

410 ca_list = [] 

411 annot_names = set() 

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

413 if _is_class_var(type): 

414 continue 

415 annot_names.add(attr_name) 

416 a = cd.get(attr_name, NOTHING) 

417 

418 if a.__class__ is not _CountingAttr: 

419 a = attrib(a) 

420 ca_list.append((attr_name, a)) 

421 

422 unannotated = ca_names - annot_names 

423 if unannotated: 

424 raise UnannotatedAttributeError( 

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

426 + ", ".join( 

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

428 ) 

429 + "." 

430 ) 

431 else: 

432 ca_list = sorted( 

433 ( 

434 (name, attr) 

435 for name, attr in cd.items() 

436 if attr.__class__ is _CountingAttr 

437 ), 

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

439 ) 

440 

441 fca = Attribute.from_counting_attr 

442 no = ClassProps.KeywordOnly.NO 

443 own_attrs = [ 

444 fca( 

445 attr_name, 

446 ca, 

447 kw_only is not no, 

448 anns.get(attr_name), 

449 ) 

450 for attr_name, ca in ca_list 

451 ] 

452 

453 if collect_by_mro: 

454 base_attrs, base_attr_map = _collect_base_attrs( 

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

456 ) 

457 else: 

458 base_attrs, base_attr_map = _collect_base_attrs_broken( 

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

460 ) 

461 

462 if kw_only is ClassProps.KeywordOnly.FORCE: 

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

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

465 

466 attrs = base_attrs + own_attrs 

467 

468 # Resolve default field alias before executing field_transformer, so that 

469 # the transformer receives fully populated Attribute objects with usable 

470 # alias values. 

471 for a in attrs: 

472 if not a.alias: 

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

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

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

476 

477 if field_transformer is not None: 

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

479 

480 # Check attr order after executing the field_transformer. 

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

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

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

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

485 had_default = False 

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

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

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

489 raise ValueError(msg) 

490 

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

492 had_default = True 

493 

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

495 # field_transformer may have added without setting an alias. 

496 for a in attrs: 

497 if not a.alias: 

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

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

500 

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

502 # add or remove attributes! 

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

504 AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) 

505 

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

507 

508 

509def _make_cached_property_getattr(cached_properties, original_getattr, cls): 

510 lines = [ 

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

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

513 "def wrapper(_cls):", 

514 " __class__ = _cls", 

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

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

517 " if func is not None:", 

518 " result = func(self)", 

519 " _setter = _cached_setattr_get(self)", 

520 " _setter(item, result)", 

521 " return result", 

522 ] 

523 if original_getattr is not None: 

524 lines.append( 

525 " return original_getattr(self, item)", 

526 ) 

527 else: 

528 lines.extend( 

529 [ 

530 " try:", 

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

532 " except AttributeError:", 

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

534 " raise", 

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

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

537 " raise AttributeError(original_error)", 

538 ] 

539 ) 

540 

541 lines.extend( 

542 [ 

543 " return __getattr__", 

544 "__getattr__ = wrapper(_cls)", 

545 ] 

546 ) 

547 

548 unique_filename = _generate_unique_filename(cls, "getattr") 

549 

550 glob = { 

551 "cached_properties": cached_properties, 

552 "_cached_setattr_get": _OBJ_SETATTR.__get__, 

553 "original_getattr": original_getattr, 

554 } 

555 

556 return _linecache_and_compile( 

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

558 )["__getattr__"] 

559 

560 

561def _frozen_setattrs(self, name, value): 

562 """ 

563 Attached to frozen classes as __setattr__. 

564 """ 

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

566 "__cause__", 

567 "__context__", 

568 "__traceback__", 

569 "__suppress_context__", 

570 "__notes__", 

571 ): 

572 BaseException.__setattr__(self, name, value) 

573 return 

574 

575 raise FrozenInstanceError 

576 

577 

578def _frozen_delattrs(self, name): 

579 """ 

580 Attached to frozen classes as __delattr__. 

581 """ 

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

583 BaseException.__delattr__(self, name) 

584 return 

585 

586 raise FrozenInstanceError 

587 

588 

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

590 """ 

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

592 *changes* applied. 

593 

594 .. tip:: 

595 

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

597 

598 Args: 

599 

600 inst: 

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

602 as a positional argument. 

603 

604 changes: 

605 Keyword changes in the new copy. 

606 

607 Returns: 

608 A copy of inst with *changes* incorporated. 

609 

610 Raises: 

611 TypeError: 

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

613 

614 attrs.exceptions.NotAnAttrsClassError: 

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

616 

617 .. versionadded:: 17.1.0 

618 .. deprecated:: 23.1.0 

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

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

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

622 argument. 

623 .. versionchanged:: 24.1.0 

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

625 """ 

626 try: 

627 (inst,) = args 

628 except ValueError: 

629 msg = ( 

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

631 ) 

632 raise TypeError(msg) from None 

633 

634 cls = inst.__class__ 

635 attrs = fields(cls) 

636 for a in attrs: 

637 if not a.init: 

638 continue 

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

640 init_name = a.alias 

641 if init_name not in changes: 

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

643 

644 return cls(**changes) 

645 

646 

647class _ClassBuilder: 

648 """ 

649 Iteratively build *one* class. 

650 """ 

651 

652 __slots__ = ( 

653 "_add_method_dunders", 

654 "_attr_names", 

655 "_attrs", 

656 "_base_attr_map", 

657 "_base_names", 

658 "_cache_hash", 

659 "_cls", 

660 "_cls_dict", 

661 "_delete_attribs", 

662 "_frozen", 

663 "_has_custom_setattr", 

664 "_has_post_init", 

665 "_has_pre_init", 

666 "_is_exc", 

667 "_on_setattr", 

668 "_pre_init_has_args", 

669 "_repr_added", 

670 "_script_snippets", 

671 "_slots", 

672 "_weakref_slot", 

673 "_wrote_own_setattr", 

674 ) 

675 

676 def __init__( 

677 self, 

678 cls: type, 

679 these, 

680 auto_attribs: bool, 

681 props: ClassProps, 

682 has_custom_setattr: bool, 

683 ): 

684 attrs, base_attrs, base_map = _transform_attrs( 

685 cls, 

686 these, 

687 auto_attribs, 

688 props.kw_only, 

689 props.collected_fields_by_mro, 

690 props.field_transformer, 

691 ) 

692 

693 self._cls = cls 

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

695 self._attrs = attrs 

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

697 self._base_attr_map = base_map 

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

699 self._slots = props.is_slotted 

700 self._frozen = props.is_frozen 

701 self._weakref_slot = props.has_weakref_slot 

702 self._cache_hash = ( 

703 props.hashability is ClassProps.Hashability.HASHABLE_CACHED 

704 ) 

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

706 self._pre_init_has_args = False 

707 if self._has_pre_init: 

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

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

710 import inspect 

711 

712 pre_init_func = cls.__attrs_pre_init__ 

713 pre_init_signature = inspect.signature(pre_init_func) 

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

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

716 self._delete_attribs = not bool(these) 

717 self._is_exc = props.is_exception 

718 self._on_setattr = props.on_setattr_hook 

719 

720 self._has_custom_setattr = has_custom_setattr 

721 self._wrote_own_setattr = False 

722 

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

724 self._cls_dict["__attrs_props__"] = props 

725 

726 if props.is_frozen: 

727 self._cls_dict["__setattr__"] = _frozen_setattrs 

728 self._cls_dict["__delattr__"] = _frozen_delattrs 

729 

730 self._wrote_own_setattr = True 

731 elif self._on_setattr in ( 

732 _DEFAULT_ON_SETATTR, 

733 setters.validate, 

734 setters.convert, 

735 ): 

736 has_validator = has_converter = False 

737 for a in attrs: 

738 if a.validator is not None: 

739 has_validator = True 

740 if a.converter is not None: 

741 has_converter = True 

742 

743 if has_validator and has_converter: 

744 break 

745 if ( 

746 ( 

747 self._on_setattr == _DEFAULT_ON_SETATTR 

748 and not (has_validator or has_converter) 

749 ) 

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

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

752 ): 

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

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

755 # no on_setattr. 

756 self._on_setattr = None 

757 

758 if props.added_pickling: 

759 ( 

760 self._cls_dict["__getstate__"], 

761 self._cls_dict["__setstate__"], 

762 ) = self._make_getstate_setstate() 

763 

764 # tuples of script, globs, hook 

765 self._script_snippets: list[ 

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

767 ] = [] 

768 self._repr_added = False 

769 

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

771 # exist. 

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

773 self._cls, "__qualname__" 

774 ): 

775 self._add_method_dunders = self._add_method_dunders_safe 

776 else: 

777 self._add_method_dunders = self._add_method_dunders_unsafe 

778 

779 def __repr__(self): 

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

781 

782 def _eval_snippets(self) -> None: 

783 """ 

784 Evaluate any registered snippets in one go. 

785 """ 

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

787 globs = {} 

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

789 globs.update(snippet_globs) 

790 

791 locs = _linecache_and_compile( 

792 script, 

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

794 globs, 

795 ) 

796 

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

798 hook(self._cls_dict, locs) 

799 

800 def build_class(self): 

801 """ 

802 Finalize class based on the accumulated configuration. 

803 

804 Builder cannot be used after calling this method. 

805 """ 

806 self._eval_snippets() 

807 if self._slots is True: 

808 cls = self._create_slots_class() 

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

810 else: 

811 cls = abc.update_abstractmethods(self._patch_original_class()) 

812 

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

814 # _has_own_attribute does NOT work properly for classmethods. 

815 if ( 

816 getattr(cls, "__attrs_init_subclass__", None) 

817 and "__attrs_init_subclass__" not in cls.__dict__ 

818 ): 

819 cls.__attrs_init_subclass__() 

820 

821 return cls 

822 

823 def _patch_original_class(self): 

824 """ 

825 Apply accumulated methods and return the class. 

826 """ 

827 cls = self._cls 

828 base_names = self._base_names 

829 

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

831 if self._delete_attribs: 

832 for name in self._attr_names: 

833 if ( 

834 name not in base_names 

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

836 ): 

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

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

839 # same name by using only a type annotation. 

840 with contextlib.suppress(AttributeError): 

841 delattr(cls, name) 

842 

843 # Attach our dunder methods. 

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

845 setattr(cls, name, value) 

846 

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

848 # reset it to object's. 

849 if not self._wrote_own_setattr and getattr( 

850 cls, "__attrs_own_setattr__", False 

851 ): 

852 cls.__attrs_own_setattr__ = False 

853 

854 if not self._has_custom_setattr: 

855 cls.__setattr__ = _OBJ_SETATTR 

856 

857 return cls 

858 

859 def _create_slots_class(self): 

860 """ 

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

862 """ 

863 cd = { 

864 k: v 

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

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

867 } 

868 

869 # 3.14.0rc2+ 

870 if hasattr(sys, "_clear_type_descriptors"): 

871 sys._clear_type_descriptors(self._cls) 

872 

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

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

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

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

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

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

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

880 # XXX: OK with us. 

881 if not self._wrote_own_setattr: 

882 cd["__attrs_own_setattr__"] = False 

883 

884 if not self._has_custom_setattr: 

885 for base_cls in self._cls.__bases__: 

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

887 cd["__setattr__"] = _OBJ_SETATTR 

888 break 

889 

890 # Traverse the MRO to collect existing slots 

891 # and check for an existing __weakref__. 

892 existing_slots = {} 

893 weakref_inherited = False 

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

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

896 weakref_inherited = True 

897 existing_slots.update( 

898 { 

899 name: getattr(base_cls, name) 

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

901 } 

902 ) 

903 

904 base_names = set(self._base_names) 

905 

906 names = self._attr_names 

907 if ( 

908 self._weakref_slot 

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

910 and "__weakref__" not in names 

911 and not weakref_inherited 

912 ): 

913 names += ("__weakref__",) 

914 

915 cached_properties = { 

916 name: cached_prop.func 

917 for name, cached_prop in cd.items() 

918 if isinstance(cached_prop, cached_property) 

919 } 

920 

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

922 # To know to update them. 

923 additional_closure_functions_to_update = [] 

924 if cached_properties: 

925 import inspect 

926 

927 class_annotations = _get_annotations(self._cls) 

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

929 # Add cached properties to names for slotting. 

930 names += (name,) 

931 # Clear out function from class to avoid clashing. 

932 del cd[name] 

933 additional_closure_functions_to_update.append(func) 

934 annotation = inspect.signature(func).return_annotation 

935 if annotation is not inspect.Parameter.empty: 

936 class_annotations[name] = annotation 

937 

938 original_getattr = cd.get("__getattr__") 

939 if original_getattr is not None: 

940 additional_closure_functions_to_update.append(original_getattr) 

941 

942 cd["__getattr__"] = _make_cached_property_getattr( 

943 cached_properties, original_getattr, self._cls 

944 ) 

945 

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

947 # Setting __slots__ to inherited attributes wastes memory. 

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

949 

950 # There are slots for attributes from current class 

951 # that are defined in parent classes. 

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

953 # we collect them here and update the class dict 

954 reused_slots = { 

955 slot: slot_descriptor 

956 for slot, slot_descriptor in existing_slots.items() 

957 if slot in slot_names 

958 } 

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

960 cd.update(reused_slots) 

961 if self._cache_hash: 

962 slot_names.append(_HASH_CACHE_FIELD) 

963 

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

965 

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

967 

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

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

970 

971 # The following is a fix for 

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

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

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

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

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

977 for item in itertools.chain( 

978 cls.__dict__.values(), additional_closure_functions_to_update 

979 ): 

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

981 # Class- and staticmethods hide their functions inside. 

982 # These might need to be rewritten as well. 

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

984 elif isinstance(item, property): 

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

986 # There is no universal way for other descriptors. 

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

988 else: 

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

990 

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

992 continue 

993 for cell in closure_cells: 

994 try: 

995 match = cell.cell_contents is self._cls 

996 except ValueError: # noqa: PERF203 

997 # ValueError: Cell is empty 

998 pass 

999 else: 

1000 if match: 

1001 cell.cell_contents = cls 

1002 return cls 

1003 

1004 def add_repr(self, ns): 

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

1006 

1007 def _attach_repr(cls_dict, globs): 

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

1009 

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

1011 self._repr_added = True 

1012 return self 

1013 

1014 def add_str(self): 

1015 if not self._repr_added: 

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

1017 raise ValueError(msg) 

1018 

1019 def __str__(self): 

1020 return self.__repr__() 

1021 

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

1023 return self 

1024 

1025 def _make_getstate_setstate(self): 

1026 """ 

1027 Create custom __setstate__ and __getstate__ methods. 

1028 """ 

1029 # __weakref__ is not writable. 

1030 state_attr_names = tuple( 

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

1032 ) 

1033 

1034 def slots_getstate(self): 

1035 """ 

1036 Automatically created by attrs. 

1037 """ 

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

1039 

1040 hash_caching_enabled = self._cache_hash 

1041 

1042 def slots_setstate(self, state): 

1043 """ 

1044 Automatically created by attrs. 

1045 """ 

1046 __bound_setattr = _OBJ_SETATTR.__get__(self) 

1047 if isinstance(state, tuple): 

1048 # Backward compatibility with attrs instances pickled with 

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

1050 for name, value in zip(state_attr_names, state, strict=False): 

1051 __bound_setattr(name, value) 

1052 else: 

1053 for name in state_attr_names: 

1054 if name in state: 

1055 __bound_setattr(name, state[name]) 

1056 

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

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

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

1060 # miss. 

1061 if hash_caching_enabled: 

1062 __bound_setattr(_HASH_CACHE_FIELD, None) 

1063 

1064 return slots_getstate, slots_setstate 

1065 

1066 def make_unhashable(self): 

1067 self._cls_dict["__hash__"] = None 

1068 return self 

1069 

1070 def add_hash(self): 

1071 script, globs = _make_hash_script( 

1072 self._cls, 

1073 self._attrs, 

1074 frozen=self._frozen, 

1075 cache_hash=self._cache_hash, 

1076 ) 

1077 

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

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

1080 

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

1082 

1083 return self 

1084 

1085 def add_init(self): 

1086 script, globs, annotations = _make_init_script( 

1087 self._cls, 

1088 self._attrs, 

1089 self._has_pre_init, 

1090 self._pre_init_has_args, 

1091 self._has_post_init, 

1092 self._frozen, 

1093 self._slots, 

1094 self._cache_hash, 

1095 self._base_attr_map, 

1096 self._is_exc, 

1097 self._on_setattr, 

1098 attrs_init=False, 

1099 ) 

1100 

1101 def _attach_init(cls_dict, globs): 

1102 init = globs["__init__"] 

1103 init.__annotations__ = annotations 

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

1105 

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

1107 

1108 return self 

1109 

1110 def add_replace(self): 

1111 # We create a local `evolve` proxy because it gets modified in place. 

1112 def __replace__(*args, **changes): 

1113 return evolve(*args, **changes) 

1114 

1115 self._cls_dict["__replace__"] = self._add_method_dunders(__replace__) 

1116 return self 

1117 

1118 def add_match_args(self): 

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

1120 field.name 

1121 for field in self._attrs 

1122 if field.init and not field.kw_only 

1123 ) 

1124 

1125 def add_attrs_init(self): 

1126 script, globs, annotations = _make_init_script( 

1127 self._cls, 

1128 self._attrs, 

1129 self._has_pre_init, 

1130 self._pre_init_has_args, 

1131 self._has_post_init, 

1132 self._frozen, 

1133 self._slots, 

1134 self._cache_hash, 

1135 self._base_attr_map, 

1136 self._is_exc, 

1137 self._on_setattr, 

1138 attrs_init=True, 

1139 ) 

1140 

1141 def _attach_attrs_init(cls_dict, globs): 

1142 init = globs["__attrs_init__"] 

1143 init.__annotations__ = annotations 

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

1145 

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

1147 

1148 return self 

1149 

1150 def add_eq(self): 

1151 cd = self._cls_dict 

1152 

1153 script, globs = _make_eq_script(self._attrs) 

1154 

1155 def _attach_eq(cls_dict, globs): 

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

1157 

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

1159 

1160 cd["__ne__"] = __ne__ 

1161 

1162 return self 

1163 

1164 def add_order(self): 

1165 cd = self._cls_dict 

1166 

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

1168 self._add_method_dunders(meth) 

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

1170 ) 

1171 

1172 return self 

1173 

1174 def add_setattr(self): 

1175 sa_attrs = {} 

1176 for a in self._attrs: 

1177 on_setattr = a.on_setattr or self._on_setattr 

1178 if on_setattr and on_setattr is not setters.NO_OP: 

1179 sa_attrs[a.name] = ( 

1180 a, 

1181 on_setattr, 

1182 _lazy_is_generator(on_setattr), 

1183 ) 

1184 

1185 if not sa_attrs: 

1186 return self 

1187 

1188 if self._has_custom_setattr: 

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

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

1191 raise ValueError(msg) 

1192 

1193 # docstring comes from _add_method_dunders 

1194 def __setattr__(self, name, val): 

1195 try: 

1196 a, hook, is_gen = sa_attrs[name] 

1197 except KeyError: 

1198 _OBJ_SETATTR(self, name, val) 

1199 

1200 return 

1201 

1202 if is_gen(): 

1203 gen = hook(self, a, val) 

1204 nval = next(gen) 

1205 _OBJ_SETATTR(self, name, nval) 

1206 try: 

1207 next(gen) 

1208 except StopIteration: 

1209 return 

1210 

1211 gen.close() 

1212 msg = "Generator on_setattr hook yielded more than once." 

1213 raise RuntimeError(msg) 

1214 

1215 nval = hook(self, a, val) 

1216 _OBJ_SETATTR(self, name, nval) 

1217 

1218 return 

1219 

1220 self._cls_dict["__attrs_own_setattr__"] = True 

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

1222 self._wrote_own_setattr = True 

1223 

1224 return self 

1225 

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

1227 """ 

1228 Add __module__ and __qualname__ to a *method*. 

1229 """ 

1230 method.__module__ = self._cls.__module__ 

1231 

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

1233 

1234 method.__doc__ = ( 

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

1236 ) 

1237 

1238 return method 

1239 

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

1241 """ 

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

1243 """ 

1244 with contextlib.suppress(AttributeError): 

1245 method.__module__ = self._cls.__module__ 

1246 

1247 with contextlib.suppress(AttributeError): 

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

1249 

1250 with contextlib.suppress(AttributeError): 

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

1252 

1253 return method 

1254 

1255 

1256def _determine_attrs_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 # cmp takes precedence due to bw-compatibility. 

1266 if cmp is not None: 

1267 return cmp, cmp 

1268 

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

1270 # mirrors equality. 

1271 if eq is None: 

1272 eq = default_eq 

1273 

1274 if order is None: 

1275 order = eq 

1276 

1277 if eq is False and order is True: 

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

1279 raise ValueError(msg) 

1280 

1281 return eq, order 

1282 

1283 

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

1285 """ 

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

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

1288 """ 

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

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

1291 raise ValueError(msg) 

1292 

1293 def decide_callable_or_boolean(value): 

1294 """ 

1295 Decide whether a key function is used. 

1296 """ 

1297 if callable(value): 

1298 value, key = True, value 

1299 else: 

1300 key = None 

1301 return value, key 

1302 

1303 # cmp takes precedence due to bw-compatibility. 

1304 if cmp is not None: 

1305 cmp, cmp_key = decide_callable_or_boolean(cmp) 

1306 return cmp, cmp_key, cmp, cmp_key 

1307 

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

1309 # mirrors equality. 

1310 if eq is None: 

1311 eq, eq_key = default_eq, None 

1312 else: 

1313 eq, eq_key = decide_callable_or_boolean(eq) 

1314 

1315 if order is None: 

1316 order, order_key = eq, eq_key 

1317 else: 

1318 order, order_key = decide_callable_or_boolean(order) 

1319 

1320 if eq is False and order is True: 

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

1322 raise ValueError(msg) 

1323 

1324 return eq, eq_key, order, order_key 

1325 

1326 

1327def _determine_whether_to_implement( 

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

1329): 

1330 """ 

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

1332 

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

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

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

1336 

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

1338 """ 

1339 if flag is True or flag is False: 

1340 return flag 

1341 

1342 if flag is None and auto_detect is False: 

1343 return default 

1344 

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

1346 for dunder in dunders: 

1347 if _has_own_attribute(cls, dunder): 

1348 return False 

1349 

1350 return default 

1351 

1352 

1353def attrs( 

1354 maybe_cls=None, 

1355 these=None, 

1356 repr_ns=None, 

1357 repr=None, 

1358 cmp=None, 

1359 hash=None, 

1360 init=None, 

1361 slots=False, 

1362 frozen=False, 

1363 weakref_slot=True, 

1364 str=False, 

1365 auto_attribs=False, 

1366 kw_only=False, 

1367 cache_hash=False, 

1368 auto_exc=False, 

1369 eq=None, 

1370 order=None, 

1371 auto_detect=False, 

1372 collect_by_mro=False, 

1373 getstate_setstate=None, 

1374 on_setattr=None, 

1375 field_transformer=None, 

1376 match_args=True, 

1377 unsafe_hash=None, 

1378 force_kw_only=True, 

1379): 

1380 r""" 

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

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

1383 

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

1385 *never* go away, though). 

1386 

1387 Args: 

1388 collect_by_mro (bool): 

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

1390 according to the `method resolution order 

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

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

1393 

1394 See also `issue #428 

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

1396 

1397 repr_ns (str): 

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

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

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

1401 pointless in Python 3 and is therefore deprecated. 

1402 

1403 .. caution:: 

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

1405 can have different defaults. 

1406 

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

1408 

1409 .. versionadded:: 16.0.0 *slots* 

1410 .. versionadded:: 16.1.0 *frozen* 

1411 .. versionadded:: 16.3.0 *str* 

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

1413 .. versionchanged:: 17.1.0 

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

1415 .. versionadded:: 17.3.0 *auto_attribs* 

1416 .. versionchanged:: 18.1.0 

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

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

1419 .. versionadded:: 18.2.0 *weakref_slot* 

1420 .. deprecated:: 18.2.0 

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

1422 `DeprecationWarning` if the classes compared are subclasses of 

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

1424 to each other. 

1425 .. versionchanged:: 19.2.0 

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

1427 subclasses comparable anymore. 

1428 .. versionadded:: 18.2.0 *kw_only* 

1429 .. versionadded:: 18.2.0 *cache_hash* 

1430 .. versionadded:: 19.1.0 *auto_exc* 

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

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

1433 .. versionadded:: 20.1.0 *auto_detect* 

1434 .. versionadded:: 20.1.0 *collect_by_mro* 

1435 .. versionadded:: 20.1.0 *getstate_setstate* 

1436 .. versionadded:: 20.1.0 *on_setattr* 

1437 .. versionadded:: 20.3.0 *field_transformer* 

1438 .. versionchanged:: 21.1.0 

1439 ``init=False`` injects ``__attrs_init__`` 

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

1441 .. versionchanged:: 21.1.0 *cmp* undeprecated 

1442 .. versionadded:: 21.3.0 *match_args* 

1443 .. versionadded:: 22.2.0 

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

1445 .. deprecated:: 24.1.0 *repr_ns* 

1446 .. versionchanged:: 24.1.0 

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

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

1449 uncomparable values like `math.nan`. 

1450 .. versionadded:: 24.1.0 

1451 If a class has an *inherited* classmethod called 

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

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

1454 .. versionchanged:: 25.4.0 

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

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

1457 .. versionadded:: 25.4.0 *force_kw_only* 

1458 """ 

1459 if repr_ns is not None: 

1460 import warnings 

1461 

1462 warnings.warn( 

1463 DeprecationWarning( 

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

1465 ), 

1466 stacklevel=2, 

1467 ) 

1468 

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

1470 

1471 # unsafe_hash takes precedence due to PEP 681. 

1472 if unsafe_hash is not None: 

1473 hash = unsafe_hash 

1474 

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

1476 on_setattr = setters.pipe(*on_setattr) 

1477 

1478 def wrap(cls): 

1479 nonlocal hash 

1480 is_frozen = frozen or _has_frozen_base_class(cls) 

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

1482 has_own_setattr = auto_detect and _has_own_attribute( 

1483 cls, "__setattr__" 

1484 ) 

1485 

1486 if has_own_setattr and is_frozen: 

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

1488 raise ValueError(msg) 

1489 

1490 eq = not is_exc and _determine_whether_to_implement( 

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

1492 ) 

1493 

1494 Hashability = ClassProps.Hashability 

1495 

1496 if is_exc: 

1497 hashability = Hashability.LEAVE_ALONE 

1498 elif hash is True: 

1499 hashability = ( 

1500 Hashability.HASHABLE_CACHED 

1501 if cache_hash 

1502 else Hashability.HASHABLE 

1503 ) 

1504 elif hash is False: 

1505 hashability = Hashability.LEAVE_ALONE 

1506 elif hash is None: 

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

1508 hashability = Hashability.LEAVE_ALONE 

1509 elif eq is True and is_frozen is True: 

1510 hashability = ( 

1511 Hashability.HASHABLE_CACHED 

1512 if cache_hash 

1513 else Hashability.HASHABLE 

1514 ) 

1515 elif eq is False: 

1516 hashability = Hashability.LEAVE_ALONE 

1517 else: 

1518 hashability = Hashability.UNHASHABLE 

1519 else: 

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

1521 raise TypeError(msg) 

1522 

1523 KeywordOnly = ClassProps.KeywordOnly 

1524 if kw_only: 

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

1526 else: 

1527 kwo = KeywordOnly.NO 

1528 

1529 props = ClassProps( 

1530 is_exception=is_exc, 

1531 is_frozen=is_frozen, 

1532 is_slotted=slots, 

1533 collected_fields_by_mro=collect_by_mro, 

1534 added_init=_determine_whether_to_implement( 

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

1536 ), 

1537 added_repr=_determine_whether_to_implement( 

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

1539 ), 

1540 added_eq=eq, 

1541 added_ordering=not is_exc 

1542 and _determine_whether_to_implement( 

1543 cls, 

1544 order_, 

1545 auto_detect, 

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

1547 ), 

1548 hashability=hashability, 

1549 added_match_args=match_args, 

1550 kw_only=kwo, 

1551 has_weakref_slot=weakref_slot, 

1552 added_str=str, 

1553 added_pickling=_determine_whether_to_implement( 

1554 cls, 

1555 getstate_setstate, 

1556 auto_detect, 

1557 ("__getstate__", "__setstate__"), 

1558 default=slots, 

1559 ), 

1560 on_setattr_hook=on_setattr, 

1561 field_transformer=field_transformer, 

1562 ) 

1563 

1564 if not props.is_hashable and cache_hash: 

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

1566 raise TypeError(msg) 

1567 

1568 builder = _ClassBuilder( 

1569 cls, 

1570 these, 

1571 auto_attribs=auto_attribs, 

1572 props=props, 

1573 has_custom_setattr=has_own_setattr, 

1574 ) 

1575 

1576 if props.added_repr: 

1577 builder.add_repr(repr_ns) 

1578 

1579 if props.added_str: 

1580 builder.add_str() 

1581 

1582 if props.added_eq: 

1583 builder.add_eq() 

1584 if props.added_ordering: 

1585 builder.add_order() 

1586 

1587 if not frozen: 

1588 builder.add_setattr() 

1589 

1590 if props.is_hashable: 

1591 builder.add_hash() 

1592 elif props.hashability is Hashability.UNHASHABLE: 

1593 builder.make_unhashable() 

1594 

1595 if props.added_init: 

1596 builder.add_init() 

1597 else: 

1598 builder.add_attrs_init() 

1599 if cache_hash: 

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

1601 raise TypeError(msg) 

1602 

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

1604 builder.add_replace() 

1605 

1606 if match_args and not _has_own_attribute(cls, "__match_args__"): 

1607 builder.add_match_args() 

1608 

1609 return builder.build_class() 

1610 

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

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

1613 if maybe_cls is None: 

1614 return wrap 

1615 

1616 return wrap(maybe_cls) 

1617 

1618 

1619_attrs = attrs 

1620""" 

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

1622*attrs*. 

1623""" 

1624 

1625 

1626def _has_frozen_base_class(cls): 

1627 """ 

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

1629 __setattr__. 

1630 """ 

1631 return cls.__setattr__ is _frozen_setattrs 

1632 

1633 

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

1635 """ 

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

1637 """ 

1638 return ( 

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

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

1641 ) 

1642 

1643 

1644def _make_hash_script( 

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

1646) -> tuple[str, dict]: 

1647 attrs = tuple( 

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

1649 ) 

1650 

1651 tab = " " 

1652 

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

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

1655 globs = {} 

1656 

1657 hash_def = "def __hash__(self" 

1658 hash_func = "hash((" 

1659 closing_braces = "))" 

1660 if not cache_hash: 

1661 hash_def += "):" 

1662 else: 

1663 hash_def += ", *" 

1664 

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

1666 hash_func = "_cache_wrapper(" + hash_func 

1667 closing_braces += ")" 

1668 

1669 method_lines = [hash_def] 

1670 

1671 def append_hash_computation_lines(prefix, indent): 

1672 """ 

1673 Generate the code for actually computing the hash code. 

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

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

1676 """ 

1677 

1678 method_lines.extend( 

1679 [ 

1680 indent + prefix + hash_func, 

1681 indent + f" {type_hash},", 

1682 ] 

1683 ) 

1684 

1685 for a in attrs: 

1686 if a.eq_key: 

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

1688 globs[cmp_name] = a.eq_key 

1689 method_lines.append( 

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

1691 ) 

1692 else: 

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

1694 

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

1696 

1697 if cache_hash: 

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

1699 if frozen: 

1700 append_hash_computation_lines( 

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

1702 ) 

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

1704 else: 

1705 append_hash_computation_lines( 

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

1707 ) 

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

1709 else: 

1710 append_hash_computation_lines("return ", tab) 

1711 

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

1713 return script, globs 

1714 

1715 

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

1717 """ 

1718 Add a hash method to *cls*. 

1719 """ 

1720 script, globs = _make_hash_script( 

1721 cls, attrs, frozen=False, cache_hash=False 

1722 ) 

1723 _compile_and_eval( 

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

1725 ) 

1726 cls.__hash__ = globs["__hash__"] 

1727 return cls 

1728 

1729 

1730def __ne__(self, other): 

1731 """ 

1732 Check equality and either forward a NotImplemented or 

1733 return the result negated. 

1734 """ 

1735 result = self.__eq__(other) 

1736 if result is NotImplemented: 

1737 return NotImplemented 

1738 

1739 return not result 

1740 

1741 

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

1743 """ 

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

1745 """ 

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

1747 

1748 lines = [ 

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

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

1751 " return NotImplemented", 

1752 ] 

1753 

1754 globs = {} 

1755 if attrs: 

1756 lines.append(" return (") 

1757 for a in attrs: 

1758 if a.eq_key: 

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

1760 # Add the key function to the global namespace 

1761 # of the evaluated function. 

1762 globs[cmp_name] = a.eq_key 

1763 lines.append( 

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

1765 ) 

1766 else: 

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

1768 if a is not attrs[-1]: 

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

1770 lines.append(" )") 

1771 else: 

1772 lines.append(" return True") 

1773 

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

1775 

1776 return script, globs 

1777 

1778 

1779def _make_order(cls, attrs): 

1780 """ 

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

1782 """ 

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

1784 

1785 def attrs_to_tuple(obj): 

1786 """ 

1787 Save us some typing. 

1788 """ 

1789 return tuple( 

1790 key(value) if key else value 

1791 for value, key in ( 

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

1793 ) 

1794 ) 

1795 

1796 def __lt__(self, other): 

1797 """ 

1798 Automatically created by attrs. 

1799 """ 

1800 if other.__class__ is self.__class__: 

1801 return attrs_to_tuple(self) < attrs_to_tuple(other) 

1802 

1803 return NotImplemented 

1804 

1805 def __le__(self, other): 

1806 """ 

1807 Automatically created by attrs. 

1808 """ 

1809 if other.__class__ is self.__class__: 

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

1811 

1812 return NotImplemented 

1813 

1814 def __gt__(self, other): 

1815 """ 

1816 Automatically created by attrs. 

1817 """ 

1818 if other.__class__ is self.__class__: 

1819 return attrs_to_tuple(self) > attrs_to_tuple(other) 

1820 

1821 return NotImplemented 

1822 

1823 def __ge__(self, other): 

1824 """ 

1825 Automatically created by attrs. 

1826 """ 

1827 if other.__class__ is self.__class__: 

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

1829 

1830 return NotImplemented 

1831 

1832 return __lt__, __le__, __gt__, __ge__ 

1833 

1834 

1835def _add_eq(cls, attrs=None): 

1836 """ 

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

1838 """ 

1839 if attrs is None: 

1840 attrs = cls.__attrs_attrs__ 

1841 

1842 script, globs = _make_eq_script(attrs) 

1843 _compile_and_eval( 

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

1845 ) 

1846 cls.__eq__ = globs["__eq__"] 

1847 cls.__ne__ = __ne__ 

1848 

1849 return cls 

1850 

1851 

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

1853 """ 

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

1855 """ 

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

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

1858 # callable. 

1859 attr_names_with_reprs = tuple( 

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

1861 for a in attrs 

1862 if a.repr is not False 

1863 ) 

1864 globs = { 

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

1866 } 

1867 globs["_compat"] = _compat 

1868 globs["AttributeError"] = AttributeError 

1869 globs["NOTHING"] = NOTHING 

1870 attribute_fragments = [] 

1871 for name, r, i in attr_names_with_reprs: 

1872 accessor = ( 

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

1874 ) 

1875 fragment = ( 

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

1877 if r == repr 

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

1879 ) 

1880 attribute_fragments.append(fragment) 

1881 repr_fragment = ", ".join(attribute_fragments) 

1882 

1883 if ns is None: 

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

1885 else: 

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

1887 

1888 lines = [ 

1889 "def __repr__(self):", 

1890 " try:", 

1891 " already_repring = _compat.repr_context.already_repring", 

1892 " except AttributeError:", 

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

1894 " _compat.repr_context.already_repring = already_repring", 

1895 " else:", 

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

1897 " return '...'", 

1898 " else:", 

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

1900 " try:", 

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

1902 " finally:", 

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

1904 ] 

1905 

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

1907 

1908 

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

1910 """ 

1911 Add a repr method to *cls*. 

1912 """ 

1913 if attrs is None: 

1914 attrs = cls.__attrs_attrs__ 

1915 

1916 script, globs = _make_repr_script(attrs, ns) 

1917 _compile_and_eval( 

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

1919 ) 

1920 cls.__repr__ = globs["__repr__"] 

1921 return cls 

1922 

1923 

1924def fields(cls): 

1925 """ 

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

1927 

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

1929 examples). 

1930 

1931 Args: 

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

1933 

1934 Raises: 

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

1936 

1937 attrs.exceptions.NotAnAttrsClassError: 

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

1939 

1940 Returns: 

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

1942 

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

1944 by name. 

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

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

1947 """ 

1948 generic_base = get_generic_base(cls) 

1949 

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

1951 type_ = type(cls) 

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

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

1954 raise TypeError(msg) 

1955 

1956 return fields(type_) 

1957 

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

1959 

1960 if attrs is None: 

1961 if generic_base is not None: 

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

1963 if attrs is not None: 

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

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

1966 # efficient. 

1967 cls.__attrs_attrs__ = attrs 

1968 return attrs 

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

1970 raise NotAnAttrsClassError(msg) 

1971 

1972 return attrs 

1973 

1974 

1975def fields_dict(cls): 

1976 """ 

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

1978 are the attribute names. 

1979 

1980 Args: 

1981 cls (type): Class to introspect. 

1982 

1983 Raises: 

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

1985 

1986 attrs.exceptions.NotAnAttrsClassError: 

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

1988 

1989 Returns: 

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

1991 

1992 .. versionadded:: 18.1.0 

1993 """ 

1994 if not isinstance(cls, type): 

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

1996 raise TypeError(msg) 

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

1998 if attrs is None: 

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

2000 raise NotAnAttrsClassError(msg) 

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

2002 

2003 

2004def validate(inst): 

2005 """ 

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

2007 

2008 Leaves all exceptions through. 

2009 

2010 Args: 

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

2012 """ 

2013 if _config._run_validators is False: 

2014 return 

2015 

2016 for a in fields(inst.__class__): 

2017 v = a.validator 

2018 if v is not None: 

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

2020 

2021 

2022def _is_slot_attr(a_name, base_attr_map): 

2023 """ 

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

2025 """ 

2026 cls = base_attr_map.get(a_name) 

2027 return cls and "__slots__" in cls.__dict__ 

2028 

2029 

2030def _make_init_script( 

2031 cls, 

2032 attrs, 

2033 pre_init, 

2034 pre_init_has_args, 

2035 post_init, 

2036 frozen, 

2037 slots, 

2038 cache_hash, 

2039 base_attr_map, 

2040 is_exc, 

2041 cls_on_setattr, 

2042 attrs_init, 

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

2044 has_cls_on_setattr = ( 

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

2046 ) 

2047 

2048 if frozen and has_cls_on_setattr: 

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

2050 raise ValueError(msg) 

2051 

2052 needs_cached_setattr = cache_hash or frozen 

2053 filtered_attrs = [] 

2054 attr_dict = {} 

2055 for a in attrs: 

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

2057 continue 

2058 

2059 filtered_attrs.append(a) 

2060 attr_dict[a.name] = a 

2061 

2062 if a.on_setattr is not None: 

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

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

2065 raise ValueError(msg) 

2066 

2067 needs_cached_setattr = True 

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

2069 needs_cached_setattr = True 

2070 

2071 script, globs, annotations = _attrs_to_init_script( 

2072 filtered_attrs, 

2073 frozen, 

2074 slots, 

2075 pre_init, 

2076 pre_init_has_args, 

2077 post_init, 

2078 cache_hash, 

2079 base_attr_map, 

2080 is_exc, 

2081 needs_cached_setattr, 

2082 has_cls_on_setattr, 

2083 "__attrs_init__" if attrs_init else "__init__", 

2084 ) 

2085 if cls.__module__ in sys.modules: 

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

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

2088 

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

2090 

2091 if needs_cached_setattr: 

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

2093 # setattr hooks. 

2094 globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__ 

2095 

2096 return script, globs, annotations 

2097 

2098 

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

2100 """ 

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

2102 """ 

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

2104 

2105 

2106def _setattr_with_converter( 

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

2108) -> str: 

2109 """ 

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

2111 its converter first. 

2112 """ 

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

2114 

2115 

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

2117 """ 

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

2119 relegate to _setattr. 

2120 """ 

2121 if has_on_setattr: 

2122 return _setattr(attr_name, value, True) 

2123 

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

2125 

2126 

2127def _assign_with_converter( 

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

2129) -> str: 

2130 """ 

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

2132 conversion. Otherwise relegate to _setattr_with_converter. 

2133 """ 

2134 if has_on_setattr: 

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

2136 

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

2138 

2139 

2140def _determine_setters( 

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

2142): 

2143 """ 

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

2145 and/or slotted. 

2146 """ 

2147 if frozen is True: 

2148 if slots is True: 

2149 return (), _setattr, _setattr_with_converter 

2150 

2151 # Dict frozen classes assign directly to __dict__. 

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

2153 # class. 

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

2155 

2156 def fmt_setter( 

2157 attr_name: str, value_var: str, has_on_setattr: bool 

2158 ) -> str: 

2159 if _is_slot_attr(attr_name, base_attr_map): 

2160 return _setattr(attr_name, value_var, has_on_setattr) 

2161 

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

2163 

2164 def fmt_setter_with_converter( 

2165 attr_name: str, 

2166 value_var: str, 

2167 has_on_setattr: bool, 

2168 converter: Converter, 

2169 ) -> str: 

2170 if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): 

2171 return _setattr_with_converter( 

2172 attr_name, value_var, has_on_setattr, converter 

2173 ) 

2174 

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

2176 

2177 return ( 

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

2179 fmt_setter, 

2180 fmt_setter_with_converter, 

2181 ) 

2182 

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

2184 return (), _assign, _assign_with_converter 

2185 

2186 

2187def _attrs_to_init_script( 

2188 attrs: list[Attribute], 

2189 is_frozen: bool, 

2190 is_slotted: bool, 

2191 call_pre_init: bool, 

2192 pre_init_has_args: bool, 

2193 call_post_init: bool, 

2194 does_cache_hash: bool, 

2195 base_attr_map: dict[str, type], 

2196 is_exc: bool, 

2197 needs_cached_setattr: bool, 

2198 has_cls_on_setattr: bool, 

2199 method_name: str, 

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

2201 """ 

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

2203 annotations for the initializer. 

2204 

2205 The globals are required by the generated script. 

2206 """ 

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

2208 

2209 if needs_cached_setattr: 

2210 lines.append( 

2211 # Circumvent the __setattr__ descriptor to save one lookup per 

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

2213 # does_cache_hash is True. 

2214 "_setattr = _cached_setattr_get(self)" 

2215 ) 

2216 

2217 extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters( 

2218 is_frozen, is_slotted, base_attr_map 

2219 ) 

2220 lines.extend(extra_lines) 

2221 

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

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

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

2225 attrs_to_validate = [] 

2226 

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

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

2229 names_for_globals = {} 

2230 annotations = {"return": None} 

2231 

2232 for a in attrs: 

2233 if a.validator: 

2234 attrs_to_validate.append(a) 

2235 

2236 attr_name = a.name 

2237 has_on_setattr = a.on_setattr is not None or ( 

2238 a.on_setattr is not setters.NO_OP and has_cls_on_setattr 

2239 ) 

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

2241 # explicitly provided 

2242 arg_name = a.alias 

2243 

2244 has_factory = isinstance(a.default, Factory) 

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

2246 

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

2248 converter = Converter(a.converter) 

2249 else: 

2250 converter = a.converter 

2251 

2252 if a.init is False: 

2253 if has_factory: 

2254 init_factory_name = _INIT_FACTORY_PAT % (a.name,) 

2255 if converter is not None: 

2256 lines.append( 

2257 fmt_setter_with_converter( 

2258 attr_name, 

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

2260 has_on_setattr, 

2261 converter, 

2262 ) 

2263 ) 

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

2265 converter.converter 

2266 ) 

2267 else: 

2268 lines.append( 

2269 fmt_setter( 

2270 attr_name, 

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

2272 has_on_setattr, 

2273 ) 

2274 ) 

2275 names_for_globals[init_factory_name] = a.default.factory 

2276 elif converter is not None: 

2277 lines.append( 

2278 fmt_setter_with_converter( 

2279 attr_name, 

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

2281 has_on_setattr, 

2282 converter, 

2283 ) 

2284 ) 

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

2286 converter.converter 

2287 ) 

2288 else: 

2289 lines.append( 

2290 fmt_setter( 

2291 attr_name, 

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

2293 has_on_setattr, 

2294 ) 

2295 ) 

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

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

2298 if a.kw_only: 

2299 kw_only_args.append(arg) 

2300 else: 

2301 args.append(arg) 

2302 pre_init_args.append(arg_name) 

2303 

2304 if converter is not None: 

2305 lines.append( 

2306 fmt_setter_with_converter( 

2307 attr_name, arg_name, has_on_setattr, converter 

2308 ) 

2309 ) 

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

2311 converter.converter 

2312 ) 

2313 else: 

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

2315 

2316 elif has_factory: 

2317 arg = f"{arg_name}=NOTHING" 

2318 if a.kw_only: 

2319 kw_only_args.append(arg) 

2320 else: 

2321 args.append(arg) 

2322 pre_init_args.append(arg_name) 

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

2324 

2325 init_factory_name = _INIT_FACTORY_PAT % (a.name,) 

2326 if converter is not None: 

2327 lines.append( 

2328 " " 

2329 + fmt_setter_with_converter( 

2330 attr_name, arg_name, has_on_setattr, converter 

2331 ) 

2332 ) 

2333 lines.append("else:") 

2334 lines.append( 

2335 " " 

2336 + fmt_setter_with_converter( 

2337 attr_name, 

2338 init_factory_name + "(" + maybe_self + ")", 

2339 has_on_setattr, 

2340 converter, 

2341 ) 

2342 ) 

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

2344 converter.converter 

2345 ) 

2346 else: 

2347 lines.append( 

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

2349 ) 

2350 lines.append("else:") 

2351 lines.append( 

2352 " " 

2353 + fmt_setter( 

2354 attr_name, 

2355 init_factory_name + "(" + maybe_self + ")", 

2356 has_on_setattr, 

2357 ) 

2358 ) 

2359 names_for_globals[init_factory_name] = a.default.factory 

2360 else: 

2361 if a.kw_only: 

2362 kw_only_args.append(arg_name) 

2363 else: 

2364 args.append(arg_name) 

2365 pre_init_args.append(arg_name) 

2366 

2367 if converter is not None: 

2368 lines.append( 

2369 fmt_setter_with_converter( 

2370 attr_name, arg_name, has_on_setattr, converter 

2371 ) 

2372 ) 

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

2374 converter.converter 

2375 ) 

2376 else: 

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

2378 

2379 if a.init is True: 

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

2381 annotations[arg_name] = a.type 

2382 elif converter is not None and converter._first_param_type: 

2383 # Use the type from the converter if present. 

2384 annotations[arg_name] = converter._first_param_type 

2385 

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

2387 names_for_globals["_config"] = _config 

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

2389 for a in attrs_to_validate: 

2390 val_name = "__attr_validator_" + a.name 

2391 attr_name = "__attr_" + a.name 

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

2393 names_for_globals[val_name] = a.validator 

2394 names_for_globals[attr_name] = a 

2395 

2396 if call_post_init: 

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

2398 

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

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

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

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

2403 # would result in silent bugs. 

2404 if does_cache_hash: 

2405 if is_frozen: 

2406 if is_slotted: 

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

2408 else: 

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

2410 else: 

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

2412 lines.append(init_hash_cache) 

2413 

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

2415 # initialization. 

2416 if is_exc: 

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

2418 

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

2420 

2421 args = ", ".join(args) 

2422 pre_init_args = ", ".join(pre_init_args) 

2423 if kw_only_args: 

2424 # leading comma & kw_only args 

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

2426 pre_init_kw_only_args = ", ".join( 

2427 [ 

2428 f"{kw_arg_name}={kw_arg_name}" 

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

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

2431 ] 

2432 ) 

2433 pre_init_args += ", " if pre_init_args else "" 

2434 pre_init_args += pre_init_kw_only_args 

2435 

2436 if call_pre_init and pre_init_has_args: 

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

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

2439 

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

2441 NL = "\n " 

2442 return ( 

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

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

2445""", 

2446 names_for_globals, 

2447 annotations, 

2448 ) 

2449 

2450 

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

2452 """ 

2453 The default __init__ parameter name for a field. 

2454 

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

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

2457 """ 

2458 

2459 return name.lstrip("_") 

2460 

2461 

2462class Attribute: 

2463 """ 

2464 *Read-only* representation of an attribute. 

2465 

2466 .. warning:: 

2467 

2468 You should never instantiate this class yourself. 

2469 

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

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

2472 

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

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

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

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

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

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

2479 from a base class. 

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

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

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

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

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

2485 

2486 Instances of this class are frequently used for introspection purposes 

2487 like: 

2488 

2489 - `fields` returns a tuple of them. 

2490 - Validators get them passed as the first argument. 

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

2492 them. 

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

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

2495 

2496 

2497 .. versionadded:: 20.1.0 *inherited* 

2498 .. versionadded:: 20.1.0 *on_setattr* 

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

2500 equality checks and hashing anymore. 

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

2502 .. versionadded:: 22.2.0 *alias* 

2503 .. versionadded:: 26.1.0 *alias_is_default* 

2504 

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

2506 """ 

2507 

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

2509 # instantiation. 

2510 __slots__ = ( # noqa: RUF023 

2511 "name", 

2512 "default", 

2513 "validator", 

2514 "repr", 

2515 "eq", 

2516 "eq_key", 

2517 "order", 

2518 "order_key", 

2519 "hash", 

2520 "init", 

2521 "metadata", 

2522 "type", 

2523 "converter", 

2524 "kw_only", 

2525 "inherited", 

2526 "on_setattr", 

2527 "alias", 

2528 "alias_is_default", 

2529 ) 

2530 

2531 def __init__( 

2532 self, 

2533 name, 

2534 default, 

2535 validator, 

2536 repr, 

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

2538 hash, 

2539 init, 

2540 inherited, 

2541 metadata=None, 

2542 type=None, 

2543 converter=None, 

2544 kw_only=False, 

2545 eq=None, 

2546 eq_key=None, 

2547 order=None, 

2548 order_key=None, 

2549 on_setattr=None, 

2550 alias=None, 

2551 alias_is_default=None, 

2552 ): 

2553 eq, eq_key, order, order_key = _determine_attrib_eq_order( 

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

2555 ) 

2556 

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

2558 bound_setattr = _OBJ_SETATTR.__get__(self) 

2559 

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

2561 # themselves. 

2562 bound_setattr("name", name) 

2563 bound_setattr("default", default) 

2564 bound_setattr("validator", validator) 

2565 bound_setattr("repr", repr) 

2566 bound_setattr("eq", eq) 

2567 bound_setattr("eq_key", eq_key) 

2568 bound_setattr("order", order) 

2569 bound_setattr("order_key", order_key) 

2570 bound_setattr("hash", hash) 

2571 bound_setattr("init", init) 

2572 bound_setattr("converter", converter) 

2573 bound_setattr( 

2574 "metadata", 

2575 ( 

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

2577 if metadata 

2578 else _EMPTY_METADATA_SINGLETON 

2579 ), 

2580 ) 

2581 bound_setattr("type", type) 

2582 bound_setattr("kw_only", kw_only) 

2583 bound_setattr("inherited", inherited) 

2584 bound_setattr("on_setattr", on_setattr) 

2585 bound_setattr("alias", alias) 

2586 bound_setattr( 

2587 "alias_is_default", 

2588 alias is None if alias_is_default is None else alias_is_default, 

2589 ) 

2590 

2591 def __setattr__(self, name, value): 

2592 raise FrozenInstanceError 

2593 

2594 @classmethod 

2595 def from_counting_attr( 

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

2597 ): 

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

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

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

2601 if type is None: 

2602 type = ca.type 

2603 elif ca.type is not None: 

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

2605 raise ValueError(msg) 

2606 return cls( 

2607 name, 

2608 ca._default, 

2609 ca._validator, 

2610 ca.repr, 

2611 None, 

2612 ca.hash, 

2613 ca.init, 

2614 False, 

2615 ca.metadata, 

2616 type, 

2617 ca._converter, 

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

2619 ca.eq, 

2620 ca.eq_key, 

2621 ca.order, 

2622 ca.order_key, 

2623 ca.on_setattr, 

2624 ca.alias, 

2625 ca.alias is None, 

2626 ) 

2627 

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

2629 def evolve(self, **changes): 

2630 """ 

2631 Copy *self* and apply *changes*. 

2632 

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

2634 with :class:`attrs.Attribute`. 

2635 

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

2637 

2638 .. versionadded:: 20.3.0 

2639 """ 

2640 import copy 

2641 

2642 new = copy.copy(self) 

2643 

2644 new._setattrs(changes.items()) 

2645 

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

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

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

2649 elif ( 

2650 "name" in changes 

2651 and "alias" not in changes 

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

2653 and self.alias_is_default 

2654 ): 

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

2656 _OBJ_SETATTR.__get__(new)( 

2657 "alias", _default_init_alias_for(new.name) 

2658 ) 

2659 

2660 return new 

2661 

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

2663 def __getstate__(self): 

2664 """ 

2665 Play nice with pickle. 

2666 """ 

2667 return tuple( 

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

2669 for name in self.__slots__ 

2670 ) 

2671 

2672 def __setstate__(self, state): 

2673 """ 

2674 Play nice with pickle. 

2675 """ 

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

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

2678 # heuristically. 

2679 state_dict = dict(zip(self.__slots__, state, strict=False)) 

2680 alias_is_default = state_dict.get( 

2681 "alias" 

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

2683 state_dict["name"] 

2684 ) 

2685 state = (*state, alias_is_default) 

2686 

2687 self._setattrs(zip(self.__slots__, state, strict=True)) 

2688 

2689 def _setattrs(self, name_values_pairs): 

2690 bound_setattr = _OBJ_SETATTR.__get__(self) 

2691 for name, value in name_values_pairs: 

2692 if name != "metadata": 

2693 bound_setattr(name, value) 

2694 else: 

2695 bound_setattr( 

2696 name, 

2697 ( 

2698 types.MappingProxyType(dict(value)) 

2699 if value 

2700 else _EMPTY_METADATA_SINGLETON 

2701 ), 

2702 ) 

2703 

2704 

2705_a = [ 

2706 Attribute( 

2707 name=name, 

2708 default=NOTHING, 

2709 validator=None, 

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

2711 cmp=None, 

2712 eq=True, 

2713 order=False, 

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

2715 init=True, 

2716 inherited=False, 

2717 alias=_default_init_alias_for(name), 

2718 ) 

2719 for name in Attribute.__slots__ 

2720] 

2721 

2722Attribute = _add_hash( 

2723 _add_eq( 

2724 _add_repr(Attribute, attrs=_a), 

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

2726 ), 

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

2728) 

2729 

2730 

2731class _CountingAttr: 

2732 """ 

2733 Intermediate representation of attributes that uses a counter to preserve 

2734 the order in which the attributes have been defined. 

2735 

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

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

2738 """ 

2739 

2740 __slots__ = ( 

2741 "_converter", 

2742 "_default", 

2743 "_validator", 

2744 "alias", 

2745 "counter", 

2746 "eq", 

2747 "eq_key", 

2748 "hash", 

2749 "init", 

2750 "kw_only", 

2751 "metadata", 

2752 "on_setattr", 

2753 "order", 

2754 "order_key", 

2755 "repr", 

2756 "type", 

2757 ) 

2758 __attrs_attrs__ = ( 

2759 *tuple( 

2760 Attribute( 

2761 name=name, 

2762 alias=_default_init_alias_for(name), 

2763 default=NOTHING, 

2764 validator=None, 

2765 repr=True, 

2766 cmp=None, 

2767 hash=True, 

2768 init=True, 

2769 kw_only=False, 

2770 eq=True, 

2771 eq_key=None, 

2772 order=False, 

2773 order_key=None, 

2774 inherited=False, 

2775 on_setattr=None, 

2776 ) 

2777 for name in ( 

2778 "counter", 

2779 "_default", 

2780 "repr", 

2781 "eq", 

2782 "order", 

2783 "hash", 

2784 "init", 

2785 "on_setattr", 

2786 "alias", 

2787 ) 

2788 ), 

2789 Attribute( 

2790 name="metadata", 

2791 alias="metadata", 

2792 default=None, 

2793 validator=None, 

2794 repr=True, 

2795 cmp=None, 

2796 hash=False, 

2797 init=True, 

2798 kw_only=False, 

2799 eq=True, 

2800 eq_key=None, 

2801 order=False, 

2802 order_key=None, 

2803 inherited=False, 

2804 on_setattr=None, 

2805 ), 

2806 ) 

2807 cls_counter = 0 

2808 

2809 def __init__( 

2810 self, 

2811 default, 

2812 validator, 

2813 repr, 

2814 cmp, 

2815 hash, 

2816 init, 

2817 converter, 

2818 metadata, 

2819 type, 

2820 kw_only, 

2821 eq, 

2822 eq_key, 

2823 order, 

2824 order_key, 

2825 on_setattr, 

2826 alias, 

2827 ): 

2828 _CountingAttr.cls_counter += 1 

2829 self.counter = _CountingAttr.cls_counter 

2830 self._default = default 

2831 self._validator = validator 

2832 self._converter = converter 

2833 self.repr = repr 

2834 self.eq = eq 

2835 self.eq_key = eq_key 

2836 self.order = order 

2837 self.order_key = order_key 

2838 self.hash = hash 

2839 self.init = init 

2840 self.metadata = metadata 

2841 self.type = type 

2842 self.kw_only = kw_only 

2843 self.on_setattr = on_setattr 

2844 self.alias = alias 

2845 

2846 def validator(self, meth): 

2847 """ 

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

2849 

2850 Returns *meth* unchanged. 

2851 

2852 .. versionadded:: 17.1.0 

2853 """ 

2854 if self._validator is None: 

2855 self._validator = meth 

2856 else: 

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

2858 return meth 

2859 

2860 def default(self, meth): 

2861 """ 

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

2863 

2864 Returns *meth* unchanged. 

2865 

2866 Raises: 

2867 DefaultAlreadySetError: If default has been set before. 

2868 

2869 .. versionadded:: 17.1.0 

2870 """ 

2871 if self._default is not NOTHING: 

2872 raise DefaultAlreadySetError 

2873 

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

2875 

2876 return meth 

2877 

2878 def converter(self, meth): 

2879 """ 

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

2881 

2882 Returns *meth* unchanged. 

2883 

2884 .. versionadded:: 26.2.0 

2885 """ 

2886 decorated_converter = Converter( 

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

2888 takes_self=True, 

2889 takes_field=True, 

2890 ) 

2891 if self._converter is None: 

2892 self._converter = decorated_converter 

2893 else: 

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

2895 

2896 return meth 

2897 

2898 

2899_CountingAttr = _add_eq(_add_repr(_CountingAttr)) 

2900 

2901 

2902class ClassProps: 

2903 """ 

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

2905 `define()` decorators. 

2906 

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

2908 to construct the final class. 

2909 

2910 Warning: 

2911 

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

2913 strict backwards-compatibility guarantees. 

2914 

2915 

2916 Attributes: 

2917 is_exception (bool): 

2918 Whether the class is treated as an exception class. 

2919 

2920 is_slotted (bool): 

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

2922 

2923 has_weakref_slot (bool): 

2924 Whether the class has a slot for weak references. 

2925 

2926 is_frozen (bool): 

2927 Whether the class is frozen. 

2928 

2929 kw_only (KeywordOnly): 

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

2931 ``__init__`` method. 

2932 

2933 collected_fields_by_mro (bool): 

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

2935 That is, correctly but unlike `dataclasses`. 

2936 

2937 added_init (bool): 

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

2939 

2940 added_repr (bool): 

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

2942 

2943 added_eq (bool): 

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

2945 

2946 added_ordering (bool): 

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

2948 

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

2950 

2951 added_match_args (bool): 

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

2953 fields. 

2954 

2955 added_str (bool): 

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

2957 

2958 added_pickling (bool): 

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

2960 ``__setstate__`` methods for `pickle`. 

2961 

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

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

2964 

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

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

2967 

2968 .. versionadded:: 25.4.0 

2969 """ 

2970 

2971 class Hashability(enum.Enum): 

2972 """ 

2973 The hashability of a class. 

2974 

2975 .. versionadded:: 25.4.0 

2976 """ 

2977 

2978 HASHABLE = "hashable" 

2979 """Write a ``__hash__``.""" 

2980 HASHABLE_CACHED = "hashable_cache" 

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

2982 UNHASHABLE = "unhashable" 

2983 """Set ``__hash__`` to ``None``.""" 

2984 LEAVE_ALONE = "leave_alone" 

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

2986 

2987 class KeywordOnly(enum.Enum): 

2988 """ 

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

2990 

2991 .. versionadded:: 25.4.0 

2992 """ 

2993 

2994 NO = "no" 

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

2996 YES = "yes" 

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

2998 FORCE = "force" 

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

3000 

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

3002 "is_exception", 

3003 "is_slotted", 

3004 "has_weakref_slot", 

3005 "is_frozen", 

3006 "kw_only", 

3007 "collected_fields_by_mro", 

3008 "added_init", 

3009 "added_repr", 

3010 "added_eq", 

3011 "added_ordering", 

3012 "hashability", 

3013 "added_match_args", 

3014 "added_str", 

3015 "added_pickling", 

3016 "on_setattr_hook", 

3017 "field_transformer", 

3018 ) 

3019 

3020 def __init__( 

3021 self, 

3022 is_exception, 

3023 is_slotted, 

3024 has_weakref_slot, 

3025 is_frozen, 

3026 kw_only, 

3027 collected_fields_by_mro, 

3028 added_init, 

3029 added_repr, 

3030 added_eq, 

3031 added_ordering, 

3032 hashability, 

3033 added_match_args, 

3034 added_str, 

3035 added_pickling, 

3036 on_setattr_hook, 

3037 field_transformer, 

3038 ): 

3039 self.is_exception = is_exception 

3040 self.is_slotted = is_slotted 

3041 self.has_weakref_slot = has_weakref_slot 

3042 self.is_frozen = is_frozen 

3043 self.kw_only = kw_only 

3044 self.collected_fields_by_mro = collected_fields_by_mro 

3045 self.added_init = added_init 

3046 self.added_repr = added_repr 

3047 self.added_eq = added_eq 

3048 self.added_ordering = added_ordering 

3049 self.hashability = hashability 

3050 self.added_match_args = added_match_args 

3051 self.added_str = added_str 

3052 self.added_pickling = added_pickling 

3053 self.on_setattr_hook = on_setattr_hook 

3054 self.field_transformer = field_transformer 

3055 

3056 @property 

3057 def is_hashable(self): 

3058 return ( 

3059 self.hashability is ClassProps.Hashability.HASHABLE 

3060 or self.hashability is ClassProps.Hashability.HASHABLE_CACHED 

3061 ) 

3062 

3063 

3064_cas = [ 

3065 Attribute( 

3066 name=name, 

3067 default=NOTHING, 

3068 validator=None, 

3069 repr=True, 

3070 cmp=None, 

3071 eq=True, 

3072 order=False, 

3073 hash=True, 

3074 init=True, 

3075 inherited=False, 

3076 alias=_default_init_alias_for(name), 

3077 ) 

3078 for name in ClassProps.__slots__ 

3079] 

3080 

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

3082 

3083 

3084class Factory: 

3085 """ 

3086 Stores a factory callable. 

3087 

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

3089 generate a new value. 

3090 

3091 Args: 

3092 factory (typing.Callable): 

3093 A callable that takes either none or exactly one mandatory 

3094 positional argument depending on *takes_self*. 

3095 

3096 takes_self (bool): 

3097 Pass the partially initialized instance that is being initialized 

3098 as a positional argument. 

3099 

3100 .. versionadded:: 17.1.0 *takes_self* 

3101 """ 

3102 

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

3104 

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

3106 self.factory = factory 

3107 self.takes_self = takes_self 

3108 

3109 def __getstate__(self): 

3110 """ 

3111 Play nice with pickle. 

3112 """ 

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

3114 

3115 def __setstate__(self, state): 

3116 """ 

3117 Play nice with pickle. 

3118 """ 

3119 for name, value in zip(self.__slots__, state, strict=True): 

3120 setattr(self, name, value) 

3121 

3122 

3123_f = [ 

3124 Attribute( 

3125 name=name, 

3126 default=NOTHING, 

3127 validator=None, 

3128 repr=True, 

3129 cmp=None, 

3130 eq=True, 

3131 order=False, 

3132 hash=True, 

3133 init=True, 

3134 inherited=False, 

3135 ) 

3136 for name in Factory.__slots__ 

3137] 

3138 

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

3140 

3141 

3142class Converter: 

3143 """ 

3144 Stores a converter callable. 

3145 

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

3147 arguments are passed in the order they are documented. 

3148 

3149 Args: 

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

3151 

3152 takes_self (bool): 

3153 Pass the partially initialized instance that is being initialized 

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

3155 

3156 takes_field (bool): 

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

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

3159 

3160 .. versionadded:: 24.1.0 

3161 """ 

3162 

3163 __slots__ = ( 

3164 "__call__", 

3165 "_first_param_type", 

3166 "_global_name", 

3167 "converter", 

3168 "takes_field", 

3169 "takes_self", 

3170 ) 

3171 

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

3173 self.converter = converter 

3174 self.takes_self = takes_self 

3175 self.takes_field = takes_field 

3176 

3177 ex = _AnnotationExtractor(converter) 

3178 self._first_param_type = ex.get_first_param_type() 

3179 

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

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

3182 elif self.takes_self and not self.takes_field: 

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

3184 value, instance 

3185 ) 

3186 elif not self.takes_self and self.takes_field: 

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

3188 value, field 

3189 ) 

3190 else: 

3191 self.__call__ = self.converter 

3192 

3193 rt = ex.get_return_type() 

3194 if rt is not None: 

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

3196 

3197 @staticmethod 

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

3199 """ 

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

3201 would have. 

3202 """ 

3203 return f"__attr_converter_{attr_name}" 

3204 

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

3206 """ 

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

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

3209 `self.takes_self` and `self.takes_field`. 

3210 """ 

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

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

3213 

3214 if self.takes_self and self.takes_field: 

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

3216 

3217 if self.takes_self: 

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

3219 

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

3221 

3222 def __getstate__(self): 

3223 """ 

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

3225 computed when loading. 

3226 """ 

3227 return { 

3228 "converter": self.converter, 

3229 "takes_self": self.takes_self, 

3230 "takes_field": self.takes_field, 

3231 } 

3232 

3233 def __setstate__(self, state): 

3234 """ 

3235 Load instance from state. 

3236 """ 

3237 self.__init__(**state) 

3238 

3239 

3240_f = [ 

3241 Attribute( 

3242 name=name, 

3243 default=NOTHING, 

3244 validator=None, 

3245 repr=True, 

3246 cmp=None, 

3247 eq=True, 

3248 order=False, 

3249 hash=True, 

3250 init=True, 

3251 inherited=False, 

3252 ) 

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

3254] 

3255 

3256Converter = _add_hash( 

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

3258) 

3259 

3260 

3261def make_class( 

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

3263): 

3264 r""" 

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

3266 

3267 .. note:: 

3268 

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

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

3271 

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

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

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

3275 

3276 .. warning:: 

3277 

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

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

3280 you. 

3281 

3282 Args: 

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

3284 

3285 attrs (list | dict): 

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

3287 s / `attrs.field`\ s. 

3288 

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

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

3291 attributes is used. 

3292 

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

3294 

3295 class_body (dict): 

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

3297 

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

3299 

3300 Returns: 

3301 type: A new class with *attrs*. 

3302 

3303 .. versionadded:: 17.1.0 *bases* 

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

3305 .. versionchanged:: 23.2.0 *class_body* 

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

3307 """ 

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

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

3310 

3311 if isinstance(attrs, dict): 

3312 cls_dict = attrs 

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

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

3315 else: 

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

3317 raise TypeError(msg) 

3318 

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

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

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

3322 

3323 body = {} 

3324 if class_body is not None: 

3325 body.update(class_body) 

3326 if pre_init is not None: 

3327 body["__attrs_pre_init__"] = pre_init 

3328 if post_init is not None: 

3329 body["__attrs_post_init__"] = post_init 

3330 if user_init is not None: 

3331 body["__init__"] = user_init 

3332 

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

3334 

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

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

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

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

3339 with contextlib.suppress(AttributeError, ValueError): 

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

3341 "__name__", "__main__" 

3342 ) 

3343 

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

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

3346 ( 

3347 attributes_arguments["eq"], 

3348 attributes_arguments["order"], 

3349 ) = _determine_attrs_eq_order( 

3350 cmp, 

3351 attributes_arguments.get("eq"), 

3352 attributes_arguments.get("order"), 

3353 True, 

3354 ) 

3355 

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

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

3358 cls.__annotations__ = { 

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

3360 } 

3361 return cls 

3362 

3363 

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

3365# import into .validators / .converters. 

3366 

3367 

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

3369class _AndValidator: 

3370 """ 

3371 Compose many validators to a single one. 

3372 """ 

3373 

3374 _validators = attrib() 

3375 

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

3377 for v in self._validators: 

3378 v(inst, attr, value) 

3379 

3380 

3381def and_(*validators): 

3382 """ 

3383 A validator that composes multiple validators into one. 

3384 

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

3386 

3387 Args: 

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

3389 Arbitrary number of validators. 

3390 

3391 .. versionadded:: 17.1.0 

3392 """ 

3393 vals = [] 

3394 for validator in validators: 

3395 vals.extend( 

3396 validator._validators 

3397 if isinstance(validator, _AndValidator) 

3398 else [validator] 

3399 ) 

3400 

3401 return _AndValidator(tuple(vals)) 

3402 

3403 

3404def pipe(*converters): 

3405 """ 

3406 A converter that composes multiple converters into one. 

3407 

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

3409 *last* value. 

3410 

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

3412 have any. 

3413 

3414 Args: 

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

3416 Arbitrary number of converters. 

3417 

3418 .. versionadded:: 20.1.0 

3419 """ 

3420 

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

3422 

3423 if return_instance: 

3424 

3425 def pipe_converter(val, inst, field): 

3426 for c in converters: 

3427 val = ( 

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

3429 ) 

3430 

3431 return val 

3432 

3433 else: 

3434 

3435 def pipe_converter(val): 

3436 for c in converters: 

3437 val = c(val) 

3438 

3439 return val 

3440 

3441 if not converters: 

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

3443 A = TypeVar("A") 

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

3445 else: 

3446 # Get parameter type from first converter. 

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

3448 if t: 

3449 pipe_converter.__annotations__["val"] = t 

3450 

3451 last = converters[-1] 

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

3453 last = last.__call__ 

3454 

3455 # Get return type from last converter. 

3456 rt = _AnnotationExtractor(last).get_return_type() 

3457 if rt: 

3458 pipe_converter.__annotations__["return"] = rt 

3459 

3460 if return_instance: 

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

3462 return pipe_converter