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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1126 statements  

1# SPDX-License-Identifier: MIT 

2 

3from __future__ import annotations 

4 

5import abc 

6import contextlib 

7import copy 

8import enum 

9import inspect 

10import itertools 

11import linecache 

12import sys 

13import types 

14import unicodedata 

15import weakref 

16 

17from collections.abc import Callable, Mapping 

18from functools import cached_property 

19from typing import Any, NamedTuple, TypeVar 

20 

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

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

23from . import _compat, _config, setters 

24from ._compat import ( 

25 PY_3_10_PLUS, 

26 PY_3_11_PLUS, 

27 PY_3_13_PLUS, 

28 _AnnotationExtractor, 

29 _get_annotations, 

30 get_generic_base, 

31) 

32from .exceptions import ( 

33 DefaultAlreadySetError, 

34 FrozenInstanceError, 

35 NotAnAttrsClassError, 

36 UnannotatedAttributeError, 

37) 

38 

39 

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

41_OBJ_SETATTR = object.__setattr__ 

42_INIT_FACTORY_PAT = "__attr_factory_%s" 

43_CLASSVAR_PREFIXES = ( 

44 "typing.ClassVar", 

45 "t.ClassVar", 

46 "ClassVar", 

47 "typing_extensions.ClassVar", 

48) 

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

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

51# (when slots=True) 

52_HASH_CACHE_FIELD = "_attrs_cached_hash" 

53 

54_EMPTY_METADATA_SINGLETON = types.MappingProxyType({}) 

55 

56# Unique object for unequivocal getattr() defaults. 

57_SENTINEL = object() 

58 

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

60 

61 

62class _Nothing(enum.Enum): 

63 """ 

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

65 

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

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

68 

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

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

71 """ 

72 

73 NOTHING = enum.auto() 

74 

75 def __repr__(self): 

76 return "NOTHING" 

77 

78 def __bool__(self): 

79 return False 

80 

81 

82NOTHING = _Nothing.NOTHING 

83""" 

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

85 

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

87""" 

88 

89 

90class _CacheHashWrapper(int): 

91 """ 

92 An integer subclass that pickles / copies as None 

93 

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

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

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

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

98 

99 See GH #613 for more details. 

100 """ 

101 

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

103 return _none_constructor, _args 

104 

105 

106def attrib( 

107 default=NOTHING, 

108 validator=None, 

109 repr=True, 

110 cmp=None, 

111 hash=None, 

112 init=True, 

113 metadata=None, 

114 type=None, 

115 converter=None, 

116 factory=None, 

117 kw_only=None, 

118 eq=None, 

119 order=None, 

120 on_setattr=None, 

121 alias=None, 

122): 

123 """ 

124 Create a new field / attribute on a class. 

125 

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

127 

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

129 though). 

130 

131 .. warning:: 

132 

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

134 `attr.s` (or similar)! 

135 

136 

137 .. versionadded:: 15.2.0 *convert* 

138 .. versionadded:: 16.3.0 *metadata* 

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

140 .. versionchanged:: 17.1.0 

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

142 .. versionadded:: 17.3.0 *type* 

143 .. deprecated:: 17.4.0 *convert* 

144 .. versionadded:: 17.4.0 

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

146 consistency with other noun-based arguments. 

147 .. versionadded:: 18.1.0 

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

149 .. versionadded:: 18.2.0 *kw_only* 

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

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

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

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

154 .. versionadded:: 20.1.0 *on_setattr* 

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

156 .. versionchanged:: 21.1.0 

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

158 .. versionchanged:: 21.1.0 *cmp* undeprecated 

159 .. versionadded:: 22.2.0 *alias* 

160 .. versionchanged:: 25.4.0 

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

162 None. 

163 """ 

164 eq, eq_key, order, order_key = _determine_attrib_eq_order( 

165 cmp, eq, order, True 

166 ) 

167 

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

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

170 raise TypeError(msg) 

171 

172 if factory is not None: 

173 if default is not NOTHING: 

174 msg = ( 

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

176 ) 

177 raise ValueError(msg) 

178 if not callable(factory): 

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

180 raise ValueError(msg) 

181 default = Factory(factory) 

182 

183 if metadata is None: 

184 metadata = {} 

185 

186 # Apply syntactic sugar by auto-wrapping. 

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

188 on_setattr = setters.pipe(*on_setattr) 

189 

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

191 validator = and_(*validator) 

192 

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

194 converter = pipe(*converter) 

195 

196 return _CountingAttr( 

197 default=default, 

198 validator=validator, 

199 repr=repr, 

200 cmp=None, 

201 hash=hash, 

202 init=init, 

203 converter=converter, 

204 metadata=metadata, 

205 type=type, 

206 kw_only=kw_only, 

207 eq=eq, 

208 eq_key=eq_key, 

209 order=order, 

210 order_key=order_key, 

211 on_setattr=on_setattr, 

212 alias=alias, 

213 ) 

214 

215 

216def _compile_and_eval( 

217 script: str, 

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

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

220 filename: str = "", 

221) -> None: 

222 """ 

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

224 variables. 

225 """ 

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

227 eval(bytecode, globs, locs) 

228 

229 

230def _linecache_and_compile( 

231 script: str, 

232 filename: str, 

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

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

235) -> dict[str, Any]: 

236 """ 

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

238 """ 

239 

240 locs = {} if locals is None else locals 

241 

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

243 # we add a fake linecache entry. 

244 count = 1 

245 base_filename = filename 

246 while True: 

247 linecache_tuple = ( 

248 len(script), 

249 None, 

250 script.splitlines(True), 

251 filename, 

252 ) 

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

254 if old_val == linecache_tuple: 

255 break 

256 

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

258 count += 1 

259 

260 _compile_and_eval(script, globs, locs, filename) 

261 

262 return locs 

263 

264 

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

266 """ 

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

268 

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

270 

271 class MyClassAttributes(tuple): 

272 __slots__ = () 

273 x = property(itemgetter(0)) 

274 """ 

275 attr_class_name = f"{cls_name}Attributes" 

276 body = {} 

277 for i, attr_name in enumerate(attr_names): 

278 

279 def getter(self, i=i): 

280 return self[i] 

281 

282 body[attr_name] = property(getter) 

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

284 

285 

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

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

288class _Attributes(NamedTuple): 

289 attrs: type 

290 base_attrs: list[Attribute] 

291 base_attrs_map: dict[str, type] 

292 

293 

294def _is_class_var(annot): 

295 """ 

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

297 

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

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

300 disadvantage compared to plain old classes. 

301 """ 

302 annot = str(annot) 

303 

304 # Annotation can be quoted. 

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

306 annot = annot[1:-1] 

307 

308 return annot.startswith(_CLASSVAR_PREFIXES) 

309 

310 

311def _has_own_attribute(cls, attrib_name): 

312 """ 

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

314 """ 

315 return attrib_name in cls.__dict__ 

316 

317 

318def _collect_base_attrs( 

319 cls, taken_attr_names 

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

321 """ 

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

323 """ 

324 base_attrs = [] 

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

326 

327 # Traverse the MRO and collect attributes. 

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

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

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

331 continue 

332 

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

334 base_attrs.append(a) 

335 base_attr_map[a.name] = base_cls 

336 

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

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

339 # instance. 

340 filtered = [] 

341 seen = set() 

342 for a in reversed(base_attrs): 

343 if a.name in seen: 

344 continue 

345 filtered.insert(0, a) 

346 seen.add(a.name) 

347 

348 return filtered, base_attr_map 

349 

350 

351def _collect_base_attrs_broken(cls, taken_attr_names): 

352 """ 

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

354 

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

356 

357 Adhere to the old incorrect behavior. 

358 

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

360 leads to the buggy behavior reported in #428. 

361 """ 

362 base_attrs = [] 

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

364 

365 # Traverse the MRO and collect attributes. 

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

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

368 if a.name in taken_attr_names: 

369 continue 

370 

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

372 taken_attr_names.add(a.name) 

373 base_attrs.append(a) 

374 base_attr_map[a.name] = base_cls 

375 

376 return base_attrs, base_attr_map 

377 

378 

379def _transform_attrs( 

380 cls, 

381 these, 

382 auto_attribs, 

383 kw_only, 

384 collect_by_mro, 

385 field_transformer, 

386) -> _Attributes: 

387 """ 

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

389 

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

391 

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

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

394 

395 Return an `_Attributes`. 

396 """ 

397 cd = cls.__dict__ 

398 anns = _get_annotations(cls) 

399 

400 if these is not None: 

401 ca_list = list(these.items()) 

402 elif auto_attribs is True: 

403 ca_names = { 

404 name 

405 for name, attr in cd.items() 

406 if attr.__class__ is _CountingAttr 

407 } 

408 ca_list = [] 

409 annot_names = set() 

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

411 if _is_class_var(type): 

412 continue 

413 annot_names.add(attr_name) 

414 a = cd.get(attr_name, NOTHING) 

415 

416 if a.__class__ is not _CountingAttr: 

417 a = attrib(a) 

418 ca_list.append((attr_name, a)) 

419 

420 unannotated = ca_names - annot_names 

421 if unannotated: 

422 raise UnannotatedAttributeError( 

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

424 + ", ".join( 

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

426 ) 

427 + "." 

428 ) 

429 else: 

430 ca_list = sorted( 

431 ( 

432 (name, attr) 

433 for name, attr in cd.items() 

434 if attr.__class__ is _CountingAttr 

435 ), 

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

437 ) 

438 

439 fca = Attribute.from_counting_attr 

440 no = ClassProps.KeywordOnly.NO 

441 own_attrs = [ 

442 fca( 

443 attr_name, 

444 ca, 

445 kw_only is not no, 

446 anns.get(attr_name), 

447 ) 

448 for attr_name, ca in ca_list 

449 ] 

450 

451 if collect_by_mro: 

452 base_attrs, base_attr_map = _collect_base_attrs( 

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

454 ) 

455 else: 

456 base_attrs, base_attr_map = _collect_base_attrs_broken( 

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

458 ) 

459 

460 if kw_only is ClassProps.KeywordOnly.FORCE: 

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

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

463 

464 attrs = base_attrs + own_attrs 

465 

466 if field_transformer is not None: 

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

468 

469 # Check attr order after executing the field_transformer. 

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

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

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

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

474 had_default = False 

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

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

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

478 raise ValueError(msg) 

479 

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

481 had_default = True 

482 

483 # Resolve default field alias after executing field_transformer. 

484 # This allows field_transformer to differentiate between explicit vs 

485 # default aliases and supply their own defaults. 

486 for a in attrs: 

487 if not a.alias: 

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

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

490 

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

492 # add or remove attributes! 

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

494 AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) 

495 

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

497 

498 

499def _make_cached_property_getattr(cached_properties, original_getattr, cls): 

500 lines = [ 

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

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

503 "def wrapper(_cls):", 

504 " __class__ = _cls", 

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

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

507 " if func is not None:", 

508 " result = func(self)", 

509 " _setter = _cached_setattr_get(self)", 

510 " _setter(item, result)", 

511 " return result", 

512 ] 

513 if original_getattr is not None: 

514 lines.append( 

515 " return original_getattr(self, item)", 

516 ) 

517 else: 

518 lines.extend( 

519 [ 

520 " try:", 

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

522 " except AttributeError:", 

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

524 " raise", 

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

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

527 " raise AttributeError(original_error)", 

528 ] 

529 ) 

530 

531 lines.extend( 

532 [ 

533 " return __getattr__", 

534 "__getattr__ = wrapper(_cls)", 

535 ] 

536 ) 

537 

538 unique_filename = _generate_unique_filename(cls, "getattr") 

539 

540 glob = { 

541 "cached_properties": cached_properties, 

542 "_cached_setattr_get": _OBJ_SETATTR.__get__, 

543 "original_getattr": original_getattr, 

544 } 

545 

546 return _linecache_and_compile( 

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

548 )["__getattr__"] 

549 

550 

551def _frozen_setattrs(self, name, value): 

552 """ 

553 Attached to frozen classes as __setattr__. 

554 """ 

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

556 "__cause__", 

557 "__context__", 

558 "__traceback__", 

559 "__suppress_context__", 

560 "__notes__", 

561 ): 

562 BaseException.__setattr__(self, name, value) 

563 return 

564 

565 raise FrozenInstanceError 

566 

567 

568def _frozen_delattrs(self, name): 

569 """ 

570 Attached to frozen classes as __delattr__. 

571 """ 

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

573 BaseException.__delattr__(self, name) 

574 return 

575 

576 raise FrozenInstanceError 

577 

578 

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

580 """ 

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

582 *changes* applied. 

583 

584 .. tip:: 

585 

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

587 

588 Args: 

589 

590 inst: 

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

592 as a positional argument. 

593 

594 changes: 

595 Keyword changes in the new copy. 

596 

597 Returns: 

598 A copy of inst with *changes* incorporated. 

599 

600 Raises: 

601 TypeError: 

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

603 

604 attrs.exceptions.NotAnAttrsClassError: 

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

606 

607 .. versionadded:: 17.1.0 

608 .. deprecated:: 23.1.0 

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

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

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

612 argument. 

613 .. versionchanged:: 24.1.0 

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

615 """ 

616 try: 

617 (inst,) = args 

618 except ValueError: 

619 msg = ( 

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

621 ) 

622 raise TypeError(msg) from None 

623 

624 cls = inst.__class__ 

625 attrs = fields(cls) 

626 for a in attrs: 

627 if not a.init: 

628 continue 

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

630 init_name = a.alias 

631 if init_name not in changes: 

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

633 

634 return cls(**changes) 

635 

636 

637class _ClassBuilder: 

638 """ 

639 Iteratively build *one* class. 

640 """ 

641 

642 __slots__ = ( 

643 "_add_method_dunders", 

644 "_attr_names", 

645 "_attrs", 

646 "_base_attr_map", 

647 "_base_names", 

648 "_cache_hash", 

649 "_cls", 

650 "_cls_dict", 

651 "_delete_attribs", 

652 "_frozen", 

653 "_has_custom_setattr", 

654 "_has_post_init", 

655 "_has_pre_init", 

656 "_is_exc", 

657 "_on_setattr", 

658 "_pre_init_has_args", 

659 "_repr_added", 

660 "_script_snippets", 

661 "_slots", 

662 "_weakref_slot", 

663 "_wrote_own_setattr", 

664 ) 

665 

666 def __init__( 

667 self, 

668 cls: type, 

669 these, 

670 auto_attribs: bool, 

671 props: ClassProps, 

672 has_custom_setattr: bool, 

673 ): 

674 attrs, base_attrs, base_map = _transform_attrs( 

675 cls, 

676 these, 

677 auto_attribs, 

678 props.kw_only, 

679 props.collected_fields_by_mro, 

680 props.field_transformer, 

681 ) 

682 

683 self._cls = cls 

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

685 self._attrs = attrs 

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

687 self._base_attr_map = base_map 

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

689 self._slots = props.is_slotted 

690 self._frozen = props.is_frozen 

691 self._weakref_slot = props.has_weakref_slot 

692 self._cache_hash = ( 

693 props.hashability is ClassProps.Hashability.HASHABLE_CACHED 

694 ) 

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

696 self._pre_init_has_args = False 

697 if self._has_pre_init: 

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

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

700 pre_init_func = cls.__attrs_pre_init__ 

701 pre_init_signature = inspect.signature(pre_init_func) 

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

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

704 self._delete_attribs = not bool(these) 

705 self._is_exc = props.is_exception 

706 self._on_setattr = props.on_setattr_hook 

707 

708 self._has_custom_setattr = has_custom_setattr 

709 self._wrote_own_setattr = False 

710 

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

712 self._cls_dict["__attrs_props__"] = props 

713 

714 if props.is_frozen: 

715 self._cls_dict["__setattr__"] = _frozen_setattrs 

716 self._cls_dict["__delattr__"] = _frozen_delattrs 

717 

718 self._wrote_own_setattr = True 

719 elif self._on_setattr in ( 

720 _DEFAULT_ON_SETATTR, 

721 setters.validate, 

722 setters.convert, 

723 ): 

724 has_validator = has_converter = False 

725 for a in attrs: 

726 if a.validator is not None: 

727 has_validator = True 

728 if a.converter is not None: 

729 has_converter = True 

730 

731 if has_validator and has_converter: 

732 break 

733 if ( 

734 ( 

735 self._on_setattr == _DEFAULT_ON_SETATTR 

736 and not (has_validator or has_converter) 

737 ) 

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

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

740 ): 

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

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

743 # no on_setattr. 

744 self._on_setattr = None 

745 

746 if props.added_pickling: 

747 ( 

748 self._cls_dict["__getstate__"], 

749 self._cls_dict["__setstate__"], 

750 ) = self._make_getstate_setstate() 

751 

752 # tuples of script, globs, hook 

753 self._script_snippets: list[ 

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

755 ] = [] 

756 self._repr_added = False 

757 

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

759 # exist. 

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

761 self._cls, "__qualname__" 

762 ): 

763 self._add_method_dunders = self._add_method_dunders_safe 

764 else: 

765 self._add_method_dunders = self._add_method_dunders_unsafe 

766 

767 def __repr__(self): 

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

769 

770 def _eval_snippets(self) -> None: 

771 """ 

772 Evaluate any registered snippets in one go. 

773 """ 

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

775 globs = {} 

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

777 globs.update(snippet_globs) 

778 

779 locs = _linecache_and_compile( 

780 script, 

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

782 globs, 

783 ) 

784 

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

786 hook(self._cls_dict, locs) 

787 

788 def build_class(self): 

789 """ 

790 Finalize class based on the accumulated configuration. 

791 

792 Builder cannot be used after calling this method. 

793 """ 

794 self._eval_snippets() 

795 if self._slots is True: 

796 cls = self._create_slots_class() 

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

798 else: 

799 cls = self._patch_original_class() 

800 if PY_3_10_PLUS: 

801 cls = abc.update_abstractmethods(cls) 

802 

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

804 # _has_own_attribute does NOT work properly for classmethods. 

805 if ( 

806 getattr(cls, "__attrs_init_subclass__", None) 

807 and "__attrs_init_subclass__" not in cls.__dict__ 

808 ): 

809 cls.__attrs_init_subclass__() 

810 

811 return cls 

812 

813 def _patch_original_class(self): 

814 """ 

815 Apply accumulated methods and return the class. 

816 """ 

817 cls = self._cls 

818 base_names = self._base_names 

819 

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

821 if self._delete_attribs: 

822 for name in self._attr_names: 

823 if ( 

824 name not in base_names 

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

826 ): 

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

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

829 # same name by using only a type annotation. 

830 with contextlib.suppress(AttributeError): 

831 delattr(cls, name) 

832 

833 # Attach our dunder methods. 

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

835 setattr(cls, name, value) 

836 

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

838 # reset it to object's. 

839 if not self._wrote_own_setattr and getattr( 

840 cls, "__attrs_own_setattr__", False 

841 ): 

842 cls.__attrs_own_setattr__ = False 

843 

844 if not self._has_custom_setattr: 

845 cls.__setattr__ = _OBJ_SETATTR 

846 

847 return cls 

848 

849 def _create_slots_class(self): 

850 """ 

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

852 """ 

853 cd = { 

854 k: v 

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

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

857 } 

858 

859 # 3.14.0rc2+ 

860 if hasattr(sys, "_clear_type_descriptors"): 

861 sys._clear_type_descriptors(self._cls) 

862 

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

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

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

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

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

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

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

870 # XXX: OK with us. 

871 if not self._wrote_own_setattr: 

872 cd["__attrs_own_setattr__"] = False 

873 

874 if not self._has_custom_setattr: 

875 for base_cls in self._cls.__bases__: 

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

877 cd["__setattr__"] = _OBJ_SETATTR 

878 break 

879 

880 # Traverse the MRO to collect existing slots 

881 # and check for an existing __weakref__. 

882 existing_slots = {} 

883 weakref_inherited = False 

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

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

886 weakref_inherited = True 

887 existing_slots.update( 

888 { 

889 name: getattr(base_cls, name) 

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

891 } 

892 ) 

893 

894 base_names = set(self._base_names) 

895 

896 names = self._attr_names 

897 if ( 

898 self._weakref_slot 

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

900 and "__weakref__" not in names 

901 and not weakref_inherited 

902 ): 

903 names += ("__weakref__",) 

904 

905 cached_properties = { 

906 name: cached_prop.func 

907 for name, cached_prop in cd.items() 

908 if isinstance(cached_prop, cached_property) 

909 } 

910 

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

912 # To know to update them. 

913 additional_closure_functions_to_update = [] 

914 if cached_properties: 

915 class_annotations = _get_annotations(self._cls) 

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

917 # Add cached properties to names for slotting. 

918 names += (name,) 

919 # Clear out function from class to avoid clashing. 

920 del cd[name] 

921 additional_closure_functions_to_update.append(func) 

922 annotation = inspect.signature(func).return_annotation 

923 if annotation is not inspect.Parameter.empty: 

924 class_annotations[name] = annotation 

925 

926 original_getattr = cd.get("__getattr__") 

927 if original_getattr is not None: 

928 additional_closure_functions_to_update.append(original_getattr) 

929 

930 cd["__getattr__"] = _make_cached_property_getattr( 

931 cached_properties, original_getattr, self._cls 

932 ) 

933 

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

935 # Setting __slots__ to inherited attributes wastes memory. 

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

937 

938 # There are slots for attributes from current class 

939 # that are defined in parent classes. 

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

941 # we collect them here and update the class dict 

942 reused_slots = { 

943 slot: slot_descriptor 

944 for slot, slot_descriptor in existing_slots.items() 

945 if slot in slot_names 

946 } 

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

948 cd.update(reused_slots) 

949 if self._cache_hash: 

950 slot_names.append(_HASH_CACHE_FIELD) 

951 

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

953 

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

955 

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

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

958 

959 # The following is a fix for 

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

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

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

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

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

965 for item in itertools.chain( 

966 cls.__dict__.values(), additional_closure_functions_to_update 

967 ): 

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

969 # Class- and staticmethods hide their functions inside. 

970 # These might need to be rewritten as well. 

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

972 elif isinstance(item, property): 

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

974 # There is no universal way for other descriptors. 

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

976 else: 

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

978 

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

980 continue 

981 for cell in closure_cells: 

982 try: 

983 match = cell.cell_contents is self._cls 

984 except ValueError: # noqa: PERF203 

985 # ValueError: Cell is empty 

986 pass 

987 else: 

988 if match: 

989 cell.cell_contents = cls 

990 return cls 

991 

992 def add_repr(self, ns): 

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

994 

995 def _attach_repr(cls_dict, globs): 

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

997 

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

999 self._repr_added = True 

1000 return self 

1001 

1002 def add_str(self): 

1003 if not self._repr_added: 

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

1005 raise ValueError(msg) 

1006 

1007 def __str__(self): 

1008 return self.__repr__() 

1009 

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

1011 return self 

1012 

1013 def _make_getstate_setstate(self): 

1014 """ 

1015 Create custom __setstate__ and __getstate__ methods. 

1016 """ 

1017 # __weakref__ is not writable. 

1018 state_attr_names = tuple( 

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

1020 ) 

1021 

1022 def slots_getstate(self): 

1023 """ 

1024 Automatically created by attrs. 

1025 """ 

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

1027 

1028 hash_caching_enabled = self._cache_hash 

1029 

1030 def slots_setstate(self, state): 

1031 """ 

1032 Automatically created by attrs. 

1033 """ 

1034 __bound_setattr = _OBJ_SETATTR.__get__(self) 

1035 if isinstance(state, tuple): 

1036 # Backward compatibility with attrs instances pickled with 

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

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

1039 __bound_setattr(name, value) 

1040 else: 

1041 for name in state_attr_names: 

1042 if name in state: 

1043 __bound_setattr(name, state[name]) 

1044 

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

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

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

1048 # miss. 

1049 if hash_caching_enabled: 

1050 __bound_setattr(_HASH_CACHE_FIELD, None) 

1051 

1052 return slots_getstate, slots_setstate 

1053 

1054 def make_unhashable(self): 

1055 self._cls_dict["__hash__"] = None 

1056 return self 

1057 

1058 def add_hash(self): 

1059 script, globs = _make_hash_script( 

1060 self._cls, 

1061 self._attrs, 

1062 frozen=self._frozen, 

1063 cache_hash=self._cache_hash, 

1064 ) 

1065 

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

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

1068 

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

1070 

1071 return self 

1072 

1073 def add_init(self): 

1074 script, globs, annotations = _make_init_script( 

1075 self._cls, 

1076 self._attrs, 

1077 self._has_pre_init, 

1078 self._pre_init_has_args, 

1079 self._has_post_init, 

1080 self._frozen, 

1081 self._slots, 

1082 self._cache_hash, 

1083 self._base_attr_map, 

1084 self._is_exc, 

1085 self._on_setattr, 

1086 attrs_init=False, 

1087 ) 

1088 

1089 def _attach_init(cls_dict, globs): 

1090 init = globs["__init__"] 

1091 init.__annotations__ = annotations 

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

1093 

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

1095 

1096 return self 

1097 

1098 def add_replace(self): 

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

1100 return self 

1101 

1102 def add_match_args(self): 

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

1104 field.name 

1105 for field in self._attrs 

1106 if field.init and not field.kw_only 

1107 ) 

1108 

1109 def add_attrs_init(self): 

1110 script, globs, annotations = _make_init_script( 

1111 self._cls, 

1112 self._attrs, 

1113 self._has_pre_init, 

1114 self._pre_init_has_args, 

1115 self._has_post_init, 

1116 self._frozen, 

1117 self._slots, 

1118 self._cache_hash, 

1119 self._base_attr_map, 

1120 self._is_exc, 

1121 self._on_setattr, 

1122 attrs_init=True, 

1123 ) 

1124 

1125 def _attach_attrs_init(cls_dict, globs): 

1126 init = globs["__attrs_init__"] 

1127 init.__annotations__ = annotations 

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

1129 

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

1131 

1132 return self 

1133 

1134 def add_eq(self): 

1135 cd = self._cls_dict 

1136 

1137 script, globs = _make_eq_script(self._attrs) 

1138 

1139 def _attach_eq(cls_dict, globs): 

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

1141 

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

1143 

1144 cd["__ne__"] = __ne__ 

1145 

1146 return self 

1147 

1148 def add_order(self): 

1149 cd = self._cls_dict 

1150 

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

1152 self._add_method_dunders(meth) 

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

1154 ) 

1155 

1156 return self 

1157 

1158 def add_setattr(self): 

1159 sa_attrs = {} 

1160 for a in self._attrs: 

1161 on_setattr = a.on_setattr or self._on_setattr 

1162 if on_setattr and on_setattr is not setters.NO_OP: 

1163 sa_attrs[a.name] = a, on_setattr 

1164 

1165 if not sa_attrs: 

1166 return self 

1167 

1168 if self._has_custom_setattr: 

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

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

1171 raise ValueError(msg) 

1172 

1173 # docstring comes from _add_method_dunders 

1174 def __setattr__(self, name, val): 

1175 try: 

1176 a, hook = sa_attrs[name] 

1177 except KeyError: 

1178 nval = val 

1179 else: 

1180 nval = hook(self, a, val) 

1181 

1182 _OBJ_SETATTR(self, name, nval) 

1183 

1184 self._cls_dict["__attrs_own_setattr__"] = True 

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

1186 self._wrote_own_setattr = True 

1187 

1188 return self 

1189 

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

1191 """ 

1192 Add __module__ and __qualname__ to a *method*. 

1193 """ 

1194 method.__module__ = self._cls.__module__ 

1195 

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

1197 

1198 method.__doc__ = ( 

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

1200 ) 

1201 

1202 return method 

1203 

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

1205 """ 

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

1207 """ 

1208 with contextlib.suppress(AttributeError): 

1209 method.__module__ = self._cls.__module__ 

1210 

1211 with contextlib.suppress(AttributeError): 

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

1213 

1214 with contextlib.suppress(AttributeError): 

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

1216 

1217 return method 

1218 

1219 

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

1221 """ 

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

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

1224 """ 

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

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

1227 raise ValueError(msg) 

1228 

1229 # cmp takes precedence due to bw-compatibility. 

1230 if cmp is not None: 

1231 return cmp, cmp 

1232 

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

1234 # mirrors equality. 

1235 if eq is None: 

1236 eq = default_eq 

1237 

1238 if order is None: 

1239 order = eq 

1240 

1241 if eq is False and order is True: 

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

1243 raise ValueError(msg) 

1244 

1245 return eq, order 

1246 

1247 

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

1249 """ 

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

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

1252 """ 

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

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

1255 raise ValueError(msg) 

1256 

1257 def decide_callable_or_boolean(value): 

1258 """ 

1259 Decide whether a key function is used. 

1260 """ 

1261 if callable(value): 

1262 value, key = True, value 

1263 else: 

1264 key = None 

1265 return value, key 

1266 

1267 # cmp takes precedence due to bw-compatibility. 

1268 if cmp is not None: 

1269 cmp, cmp_key = decide_callable_or_boolean(cmp) 

1270 return cmp, cmp_key, cmp, cmp_key 

1271 

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

1273 # mirrors equality. 

1274 if eq is None: 

1275 eq, eq_key = default_eq, None 

1276 else: 

1277 eq, eq_key = decide_callable_or_boolean(eq) 

1278 

1279 if order is None: 

1280 order, order_key = eq, eq_key 

1281 else: 

1282 order, order_key = decide_callable_or_boolean(order) 

1283 

1284 if eq is False and order is True: 

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

1286 raise ValueError(msg) 

1287 

1288 return eq, eq_key, order, order_key 

1289 

1290 

1291def _determine_whether_to_implement( 

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

1293): 

1294 """ 

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

1296 

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

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

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

1300 

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

1302 """ 

1303 if flag is True or flag is False: 

1304 return flag 

1305 

1306 if flag is None and auto_detect is False: 

1307 return default 

1308 

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

1310 for dunder in dunders: 

1311 if _has_own_attribute(cls, dunder): 

1312 return False 

1313 

1314 return default 

1315 

1316 

1317def attrs( 

1318 maybe_cls=None, 

1319 these=None, 

1320 repr_ns=None, 

1321 repr=None, 

1322 cmp=None, 

1323 hash=None, 

1324 init=None, 

1325 slots=False, 

1326 frozen=False, 

1327 weakref_slot=True, 

1328 str=False, 

1329 auto_attribs=False, 

1330 kw_only=False, 

1331 cache_hash=False, 

1332 auto_exc=False, 

1333 eq=None, 

1334 order=None, 

1335 auto_detect=False, 

1336 collect_by_mro=False, 

1337 getstate_setstate=None, 

1338 on_setattr=None, 

1339 field_transformer=None, 

1340 match_args=True, 

1341 unsafe_hash=None, 

1342 force_kw_only=True, 

1343): 

1344 r""" 

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

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

1347 

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

1349 *never* go away, though). 

1350 

1351 Args: 

1352 repr_ns (str): 

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

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

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

1356 pointless in Python 3 and is therefore deprecated. 

1357 

1358 .. caution:: 

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

1360 can have different defaults. 

1361 

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

1363 

1364 .. versionadded:: 16.0.0 *slots* 

1365 .. versionadded:: 16.1.0 *frozen* 

1366 .. versionadded:: 16.3.0 *str* 

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

1368 .. versionchanged:: 17.1.0 

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

1370 .. versionadded:: 17.3.0 *auto_attribs* 

1371 .. versionchanged:: 18.1.0 

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

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

1374 .. versionadded:: 18.2.0 *weakref_slot* 

1375 .. deprecated:: 18.2.0 

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

1377 `DeprecationWarning` if the classes compared are subclasses of 

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

1379 to each other. 

1380 .. versionchanged:: 19.2.0 

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

1382 subclasses comparable anymore. 

1383 .. versionadded:: 18.2.0 *kw_only* 

1384 .. versionadded:: 18.2.0 *cache_hash* 

1385 .. versionadded:: 19.1.0 *auto_exc* 

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

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

1388 .. versionadded:: 20.1.0 *auto_detect* 

1389 .. versionadded:: 20.1.0 *collect_by_mro* 

1390 .. versionadded:: 20.1.0 *getstate_setstate* 

1391 .. versionadded:: 20.1.0 *on_setattr* 

1392 .. versionadded:: 20.3.0 *field_transformer* 

1393 .. versionchanged:: 21.1.0 

1394 ``init=False`` injects ``__attrs_init__`` 

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

1396 .. versionchanged:: 21.1.0 *cmp* undeprecated 

1397 .. versionadded:: 21.3.0 *match_args* 

1398 .. versionadded:: 22.2.0 

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

1400 .. deprecated:: 24.1.0 *repr_ns* 

1401 .. versionchanged:: 24.1.0 

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

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

1404 uncomparable values like `math.nan`. 

1405 .. versionadded:: 24.1.0 

1406 If a class has an *inherited* classmethod called 

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

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

1409 .. versionchanged:: 25.4.0 

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

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

1412 .. versionadded:: 25.4.0 *force_kw_only* 

1413 """ 

1414 if repr_ns is not None: 

1415 import warnings 

1416 

1417 warnings.warn( 

1418 DeprecationWarning( 

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

1420 ), 

1421 stacklevel=2, 

1422 ) 

1423 

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

1425 

1426 # unsafe_hash takes precedence due to PEP 681. 

1427 if unsafe_hash is not None: 

1428 hash = unsafe_hash 

1429 

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

1431 on_setattr = setters.pipe(*on_setattr) 

1432 

1433 def wrap(cls): 

1434 nonlocal hash 

1435 is_frozen = frozen or _has_frozen_base_class(cls) 

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

1437 has_own_setattr = auto_detect and _has_own_attribute( 

1438 cls, "__setattr__" 

1439 ) 

1440 

1441 if has_own_setattr and is_frozen: 

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

1443 raise ValueError(msg) 

1444 

1445 eq = not is_exc and _determine_whether_to_implement( 

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

1447 ) 

1448 

1449 Hashability = ClassProps.Hashability 

1450 

1451 if is_exc: 

1452 hashability = Hashability.LEAVE_ALONE 

1453 elif hash is True: 

1454 hashability = ( 

1455 Hashability.HASHABLE_CACHED 

1456 if cache_hash 

1457 else Hashability.HASHABLE 

1458 ) 

1459 elif hash is False: 

1460 hashability = Hashability.LEAVE_ALONE 

1461 elif hash is None: 

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

1463 hashability = Hashability.LEAVE_ALONE 

1464 elif eq is True and is_frozen is True: 

1465 hashability = ( 

1466 Hashability.HASHABLE_CACHED 

1467 if cache_hash 

1468 else Hashability.HASHABLE 

1469 ) 

1470 elif eq is False: 

1471 hashability = Hashability.LEAVE_ALONE 

1472 else: 

1473 hashability = Hashability.UNHASHABLE 

1474 else: 

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

1476 raise TypeError(msg) 

1477 

1478 KeywordOnly = ClassProps.KeywordOnly 

1479 if kw_only: 

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

1481 else: 

1482 kwo = KeywordOnly.NO 

1483 

1484 props = ClassProps( 

1485 is_exception=is_exc, 

1486 is_frozen=is_frozen, 

1487 is_slotted=slots, 

1488 collected_fields_by_mro=collect_by_mro, 

1489 added_init=_determine_whether_to_implement( 

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

1491 ), 

1492 added_repr=_determine_whether_to_implement( 

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

1494 ), 

1495 added_eq=eq, 

1496 added_ordering=not is_exc 

1497 and _determine_whether_to_implement( 

1498 cls, 

1499 order_, 

1500 auto_detect, 

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

1502 ), 

1503 hashability=hashability, 

1504 added_match_args=match_args, 

1505 kw_only=kwo, 

1506 has_weakref_slot=weakref_slot, 

1507 added_str=str, 

1508 added_pickling=_determine_whether_to_implement( 

1509 cls, 

1510 getstate_setstate, 

1511 auto_detect, 

1512 ("__getstate__", "__setstate__"), 

1513 default=slots, 

1514 ), 

1515 on_setattr_hook=on_setattr, 

1516 field_transformer=field_transformer, 

1517 ) 

1518 

1519 if not props.is_hashable and cache_hash: 

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

1521 raise TypeError(msg) 

1522 

1523 builder = _ClassBuilder( 

1524 cls, 

1525 these, 

1526 auto_attribs=auto_attribs, 

1527 props=props, 

1528 has_custom_setattr=has_own_setattr, 

1529 ) 

1530 

1531 if props.added_repr: 

1532 builder.add_repr(repr_ns) 

1533 

1534 if props.added_str: 

1535 builder.add_str() 

1536 

1537 if props.added_eq: 

1538 builder.add_eq() 

1539 if props.added_ordering: 

1540 builder.add_order() 

1541 

1542 if not frozen: 

1543 builder.add_setattr() 

1544 

1545 if props.is_hashable: 

1546 builder.add_hash() 

1547 elif props.hashability is Hashability.UNHASHABLE: 

1548 builder.make_unhashable() 

1549 

1550 if props.added_init: 

1551 builder.add_init() 

1552 else: 

1553 builder.add_attrs_init() 

1554 if cache_hash: 

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

1556 raise TypeError(msg) 

1557 

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

1559 builder.add_replace() 

1560 

1561 if ( 

1562 PY_3_10_PLUS 

1563 and match_args 

1564 and not _has_own_attribute(cls, "__match_args__") 

1565 ): 

1566 builder.add_match_args() 

1567 

1568 return builder.build_class() 

1569 

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

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

1572 if maybe_cls is None: 

1573 return wrap 

1574 

1575 return wrap(maybe_cls) 

1576 

1577 

1578_attrs = attrs 

1579""" 

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

1581*attrs*. 

1582""" 

1583 

1584 

1585def _has_frozen_base_class(cls): 

1586 """ 

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

1588 __setattr__. 

1589 """ 

1590 return cls.__setattr__ is _frozen_setattrs 

1591 

1592 

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

1594 """ 

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

1596 """ 

1597 return ( 

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

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

1600 ) 

1601 

1602 

1603def _make_hash_script( 

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

1605) -> tuple[str, dict]: 

1606 attrs = tuple( 

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

1608 ) 

1609 

1610 tab = " " 

1611 

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

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

1614 globs = {} 

1615 

1616 hash_def = "def __hash__(self" 

1617 hash_func = "hash((" 

1618 closing_braces = "))" 

1619 if not cache_hash: 

1620 hash_def += "):" 

1621 else: 

1622 hash_def += ", *" 

1623 

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

1625 hash_func = "_cache_wrapper(" + hash_func 

1626 closing_braces += ")" 

1627 

1628 method_lines = [hash_def] 

1629 

1630 def append_hash_computation_lines(prefix, indent): 

1631 """ 

1632 Generate the code for actually computing the hash code. 

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

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

1635 """ 

1636 

1637 method_lines.extend( 

1638 [ 

1639 indent + prefix + hash_func, 

1640 indent + f" {type_hash},", 

1641 ] 

1642 ) 

1643 

1644 for a in attrs: 

1645 if a.eq_key: 

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

1647 globs[cmp_name] = a.eq_key 

1648 method_lines.append( 

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

1650 ) 

1651 else: 

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

1653 

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

1655 

1656 if cache_hash: 

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

1658 if frozen: 

1659 append_hash_computation_lines( 

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

1661 ) 

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

1663 else: 

1664 append_hash_computation_lines( 

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

1666 ) 

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

1668 else: 

1669 append_hash_computation_lines("return ", tab) 

1670 

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

1672 return script, globs 

1673 

1674 

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

1676 """ 

1677 Add a hash method to *cls*. 

1678 """ 

1679 script, globs = _make_hash_script( 

1680 cls, attrs, frozen=False, cache_hash=False 

1681 ) 

1682 _compile_and_eval( 

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

1684 ) 

1685 cls.__hash__ = globs["__hash__"] 

1686 return cls 

1687 

1688 

1689def __ne__(self, other): 

1690 """ 

1691 Check equality and either forward a NotImplemented or 

1692 return the result negated. 

1693 """ 

1694 result = self.__eq__(other) 

1695 if result is NotImplemented: 

1696 return NotImplemented 

1697 

1698 return not result 

1699 

1700 

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

1702 """ 

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

1704 """ 

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

1706 

1707 lines = [ 

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

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

1710 " return NotImplemented", 

1711 ] 

1712 

1713 globs = {} 

1714 if attrs: 

1715 lines.append(" return (") 

1716 for a in attrs: 

1717 if a.eq_key: 

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

1719 # Add the key function to the global namespace 

1720 # of the evaluated function. 

1721 globs[cmp_name] = a.eq_key 

1722 lines.append( 

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

1724 ) 

1725 else: 

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

1727 if a is not attrs[-1]: 

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

1729 lines.append(" )") 

1730 else: 

1731 lines.append(" return True") 

1732 

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

1734 

1735 return script, globs 

1736 

1737 

1738def _make_order(cls, attrs): 

1739 """ 

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

1741 """ 

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

1743 

1744 def attrs_to_tuple(obj): 

1745 """ 

1746 Save us some typing. 

1747 """ 

1748 return tuple( 

1749 key(value) if key else value 

1750 for value, key in ( 

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

1752 ) 

1753 ) 

1754 

1755 def __lt__(self, other): 

1756 """ 

1757 Automatically created by attrs. 

1758 """ 

1759 if other.__class__ is self.__class__: 

1760 return attrs_to_tuple(self) < attrs_to_tuple(other) 

1761 

1762 return NotImplemented 

1763 

1764 def __le__(self, other): 

1765 """ 

1766 Automatically created by attrs. 

1767 """ 

1768 if other.__class__ is self.__class__: 

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

1770 

1771 return NotImplemented 

1772 

1773 def __gt__(self, other): 

1774 """ 

1775 Automatically created by attrs. 

1776 """ 

1777 if other.__class__ is self.__class__: 

1778 return attrs_to_tuple(self) > attrs_to_tuple(other) 

1779 

1780 return NotImplemented 

1781 

1782 def __ge__(self, other): 

1783 """ 

1784 Automatically created by attrs. 

1785 """ 

1786 if other.__class__ is self.__class__: 

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

1788 

1789 return NotImplemented 

1790 

1791 return __lt__, __le__, __gt__, __ge__ 

1792 

1793 

1794def _add_eq(cls, attrs=None): 

1795 """ 

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

1797 """ 

1798 if attrs is None: 

1799 attrs = cls.__attrs_attrs__ 

1800 

1801 script, globs = _make_eq_script(attrs) 

1802 _compile_and_eval( 

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

1804 ) 

1805 cls.__eq__ = globs["__eq__"] 

1806 cls.__ne__ = __ne__ 

1807 

1808 return cls 

1809 

1810 

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

1812 """ 

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

1814 """ 

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

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

1817 # callable. 

1818 attr_names_with_reprs = tuple( 

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

1820 for a in attrs 

1821 if a.repr is not False 

1822 ) 

1823 globs = { 

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

1825 } 

1826 globs["_compat"] = _compat 

1827 globs["AttributeError"] = AttributeError 

1828 globs["NOTHING"] = NOTHING 

1829 attribute_fragments = [] 

1830 for name, r, i in attr_names_with_reprs: 

1831 accessor = ( 

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

1833 ) 

1834 fragment = ( 

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

1836 if r == repr 

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

1838 ) 

1839 attribute_fragments.append(fragment) 

1840 repr_fragment = ", ".join(attribute_fragments) 

1841 

1842 if ns is None: 

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

1844 else: 

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

1846 

1847 lines = [ 

1848 "def __repr__(self):", 

1849 " try:", 

1850 " already_repring = _compat.repr_context.already_repring", 

1851 " except AttributeError:", 

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

1853 " _compat.repr_context.already_repring = already_repring", 

1854 " else:", 

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

1856 " return '...'", 

1857 " else:", 

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

1859 " try:", 

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

1861 " finally:", 

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

1863 ] 

1864 

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

1866 

1867 

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

1869 """ 

1870 Add a repr method to *cls*. 

1871 """ 

1872 if attrs is None: 

1873 attrs = cls.__attrs_attrs__ 

1874 

1875 script, globs = _make_repr_script(attrs, ns) 

1876 _compile_and_eval( 

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

1878 ) 

1879 cls.__repr__ = globs["__repr__"] 

1880 return cls 

1881 

1882 

1883def fields(cls): 

1884 """ 

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

1886 

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

1888 examples). 

1889 

1890 Args: 

1891 cls (type): Class to introspect. 

1892 

1893 Raises: 

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

1895 

1896 attrs.exceptions.NotAnAttrsClassError: 

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

1898 

1899 Returns: 

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

1901 

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

1903 by name. 

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

1905 """ 

1906 generic_base = get_generic_base(cls) 

1907 

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

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

1910 raise TypeError(msg) 

1911 

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

1913 

1914 if attrs is None: 

1915 if generic_base is not None: 

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

1917 if attrs is not None: 

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

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

1920 # efficient. 

1921 cls.__attrs_attrs__ = attrs 

1922 return attrs 

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

1924 raise NotAnAttrsClassError(msg) 

1925 

1926 return attrs 

1927 

1928 

1929def fields_dict(cls): 

1930 """ 

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

1932 are the attribute names. 

1933 

1934 Args: 

1935 cls (type): Class to introspect. 

1936 

1937 Raises: 

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

1939 

1940 attrs.exceptions.NotAnAttrsClassError: 

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

1942 

1943 Returns: 

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

1945 

1946 .. versionadded:: 18.1.0 

1947 """ 

1948 if not isinstance(cls, type): 

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

1950 raise TypeError(msg) 

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

1952 if attrs is None: 

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

1954 raise NotAnAttrsClassError(msg) 

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

1956 

1957 

1958def validate(inst): 

1959 """ 

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

1961 

1962 Leaves all exceptions through. 

1963 

1964 Args: 

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

1966 """ 

1967 if _config._run_validators is False: 

1968 return 

1969 

1970 for a in fields(inst.__class__): 

1971 v = a.validator 

1972 if v is not None: 

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

1974 

1975 

1976def _is_slot_attr(a_name, base_attr_map): 

1977 """ 

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

1979 """ 

1980 cls = base_attr_map.get(a_name) 

1981 return cls and "__slots__" in cls.__dict__ 

1982 

1983 

1984def _make_init_script( 

1985 cls, 

1986 attrs, 

1987 pre_init, 

1988 pre_init_has_args, 

1989 post_init, 

1990 frozen, 

1991 slots, 

1992 cache_hash, 

1993 base_attr_map, 

1994 is_exc, 

1995 cls_on_setattr, 

1996 attrs_init, 

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

1998 has_cls_on_setattr = ( 

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

2000 ) 

2001 

2002 if frozen and has_cls_on_setattr: 

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

2004 raise ValueError(msg) 

2005 

2006 needs_cached_setattr = cache_hash or frozen 

2007 filtered_attrs = [] 

2008 attr_dict = {} 

2009 for a in attrs: 

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

2011 continue 

2012 

2013 filtered_attrs.append(a) 

2014 attr_dict[a.name] = a 

2015 

2016 if a.on_setattr is not None: 

2017 if frozen is True: 

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

2019 raise ValueError(msg) 

2020 

2021 needs_cached_setattr = True 

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

2023 needs_cached_setattr = True 

2024 

2025 script, globs, annotations = _attrs_to_init_script( 

2026 filtered_attrs, 

2027 frozen, 

2028 slots, 

2029 pre_init, 

2030 pre_init_has_args, 

2031 post_init, 

2032 cache_hash, 

2033 base_attr_map, 

2034 is_exc, 

2035 needs_cached_setattr, 

2036 has_cls_on_setattr, 

2037 "__attrs_init__" if attrs_init else "__init__", 

2038 ) 

2039 if cls.__module__ in sys.modules: 

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

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

2042 

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

2044 

2045 if needs_cached_setattr: 

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

2047 # setattr hooks. 

2048 globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__ 

2049 

2050 return script, globs, annotations 

2051 

2052 

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

2054 """ 

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

2056 """ 

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

2058 

2059 

2060def _setattr_with_converter( 

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

2062) -> str: 

2063 """ 

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

2065 its converter first. 

2066 """ 

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

2068 

2069 

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

2071 """ 

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

2073 relegate to _setattr. 

2074 """ 

2075 if has_on_setattr: 

2076 return _setattr(attr_name, value, True) 

2077 

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

2079 

2080 

2081def _assign_with_converter( 

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

2083) -> str: 

2084 """ 

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

2086 conversion. Otherwise relegate to _setattr_with_converter. 

2087 """ 

2088 if has_on_setattr: 

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

2090 

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

2092 

2093 

2094def _determine_setters( 

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

2096): 

2097 """ 

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

2099 and/or slotted. 

2100 """ 

2101 if frozen is True: 

2102 if slots is True: 

2103 return (), _setattr, _setattr_with_converter 

2104 

2105 # Dict frozen classes assign directly to __dict__. 

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

2107 # class. 

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

2109 

2110 def fmt_setter( 

2111 attr_name: str, value_var: str, has_on_setattr: bool 

2112 ) -> str: 

2113 if _is_slot_attr(attr_name, base_attr_map): 

2114 return _setattr(attr_name, value_var, has_on_setattr) 

2115 

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

2117 

2118 def fmt_setter_with_converter( 

2119 attr_name: str, 

2120 value_var: str, 

2121 has_on_setattr: bool, 

2122 converter: Converter, 

2123 ) -> str: 

2124 if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): 

2125 return _setattr_with_converter( 

2126 attr_name, value_var, has_on_setattr, converter 

2127 ) 

2128 

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

2130 

2131 return ( 

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

2133 fmt_setter, 

2134 fmt_setter_with_converter, 

2135 ) 

2136 

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

2138 return (), _assign, _assign_with_converter 

2139 

2140 

2141def _attrs_to_init_script( 

2142 attrs: list[Attribute], 

2143 is_frozen: bool, 

2144 is_slotted: bool, 

2145 call_pre_init: bool, 

2146 pre_init_has_args: bool, 

2147 call_post_init: bool, 

2148 does_cache_hash: bool, 

2149 base_attr_map: dict[str, type], 

2150 is_exc: bool, 

2151 needs_cached_setattr: bool, 

2152 has_cls_on_setattr: bool, 

2153 method_name: str, 

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

2155 """ 

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

2157 annotations for the initializer. 

2158 

2159 The globals are required by the generated script. 

2160 """ 

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

2162 

2163 if needs_cached_setattr: 

2164 lines.append( 

2165 # Circumvent the __setattr__ descriptor to save one lookup per 

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

2167 # does_cache_hash is True. 

2168 "_setattr = _cached_setattr_get(self)" 

2169 ) 

2170 

2171 extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters( 

2172 is_frozen, is_slotted, base_attr_map 

2173 ) 

2174 lines.extend(extra_lines) 

2175 

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

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

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

2179 attrs_to_validate = [] 

2180 

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

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

2183 names_for_globals = {} 

2184 annotations = {"return": None} 

2185 

2186 for a in attrs: 

2187 if a.validator: 

2188 attrs_to_validate.append(a) 

2189 

2190 attr_name = a.name 

2191 has_on_setattr = a.on_setattr is not None or ( 

2192 a.on_setattr is not setters.NO_OP and has_cls_on_setattr 

2193 ) 

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

2195 # explicitly provided 

2196 arg_name = a.alias 

2197 

2198 has_factory = isinstance(a.default, Factory) 

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

2200 

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

2202 converter = Converter(a.converter) 

2203 else: 

2204 converter = a.converter 

2205 

2206 if a.init is False: 

2207 if has_factory: 

2208 init_factory_name = _INIT_FACTORY_PAT % (a.name,) 

2209 if converter is not None: 

2210 lines.append( 

2211 fmt_setter_with_converter( 

2212 attr_name, 

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

2214 has_on_setattr, 

2215 converter, 

2216 ) 

2217 ) 

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

2219 converter.converter 

2220 ) 

2221 else: 

2222 lines.append( 

2223 fmt_setter( 

2224 attr_name, 

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

2226 has_on_setattr, 

2227 ) 

2228 ) 

2229 names_for_globals[init_factory_name] = a.default.factory 

2230 elif converter is not None: 

2231 lines.append( 

2232 fmt_setter_with_converter( 

2233 attr_name, 

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

2235 has_on_setattr, 

2236 converter, 

2237 ) 

2238 ) 

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

2240 converter.converter 

2241 ) 

2242 else: 

2243 lines.append( 

2244 fmt_setter( 

2245 attr_name, 

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

2247 has_on_setattr, 

2248 ) 

2249 ) 

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

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

2252 if a.kw_only: 

2253 kw_only_args.append(arg) 

2254 else: 

2255 args.append(arg) 

2256 pre_init_args.append(arg_name) 

2257 

2258 if converter is not None: 

2259 lines.append( 

2260 fmt_setter_with_converter( 

2261 attr_name, arg_name, has_on_setattr, converter 

2262 ) 

2263 ) 

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

2265 converter.converter 

2266 ) 

2267 else: 

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

2269 

2270 elif has_factory: 

2271 arg = f"{arg_name}=NOTHING" 

2272 if a.kw_only: 

2273 kw_only_args.append(arg) 

2274 else: 

2275 args.append(arg) 

2276 pre_init_args.append(arg_name) 

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

2278 

2279 init_factory_name = _INIT_FACTORY_PAT % (a.name,) 

2280 if converter is not None: 

2281 lines.append( 

2282 " " 

2283 + fmt_setter_with_converter( 

2284 attr_name, arg_name, has_on_setattr, converter 

2285 ) 

2286 ) 

2287 lines.append("else:") 

2288 lines.append( 

2289 " " 

2290 + fmt_setter_with_converter( 

2291 attr_name, 

2292 init_factory_name + "(" + maybe_self + ")", 

2293 has_on_setattr, 

2294 converter, 

2295 ) 

2296 ) 

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

2298 converter.converter 

2299 ) 

2300 else: 

2301 lines.append( 

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

2303 ) 

2304 lines.append("else:") 

2305 lines.append( 

2306 " " 

2307 + fmt_setter( 

2308 attr_name, 

2309 init_factory_name + "(" + maybe_self + ")", 

2310 has_on_setattr, 

2311 ) 

2312 ) 

2313 names_for_globals[init_factory_name] = a.default.factory 

2314 else: 

2315 if a.kw_only: 

2316 kw_only_args.append(arg_name) 

2317 else: 

2318 args.append(arg_name) 

2319 pre_init_args.append(arg_name) 

2320 

2321 if converter is not None: 

2322 lines.append( 

2323 fmt_setter_with_converter( 

2324 attr_name, arg_name, has_on_setattr, converter 

2325 ) 

2326 ) 

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

2328 converter.converter 

2329 ) 

2330 else: 

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

2332 

2333 if a.init is True: 

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

2335 annotations[arg_name] = a.type 

2336 elif converter is not None and converter._first_param_type: 

2337 # Use the type from the converter if present. 

2338 annotations[arg_name] = converter._first_param_type 

2339 

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

2341 names_for_globals["_config"] = _config 

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

2343 for a in attrs_to_validate: 

2344 val_name = "__attr_validator_" + a.name 

2345 attr_name = "__attr_" + a.name 

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

2347 names_for_globals[val_name] = a.validator 

2348 names_for_globals[attr_name] = a 

2349 

2350 if call_post_init: 

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

2352 

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

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

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

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

2357 # would result in silent bugs. 

2358 if does_cache_hash: 

2359 if is_frozen: 

2360 if is_slotted: 

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

2362 else: 

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

2364 else: 

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

2366 lines.append(init_hash_cache) 

2367 

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

2369 # initialization. 

2370 if is_exc: 

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

2372 

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

2374 

2375 args = ", ".join(args) 

2376 pre_init_args = ", ".join(pre_init_args) 

2377 if kw_only_args: 

2378 # leading comma & kw_only args 

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

2380 pre_init_kw_only_args = ", ".join( 

2381 [ 

2382 f"{kw_arg_name}={kw_arg_name}" 

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

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

2385 ] 

2386 ) 

2387 pre_init_args += ", " if pre_init_args else "" 

2388 pre_init_args += pre_init_kw_only_args 

2389 

2390 if call_pre_init and pre_init_has_args: 

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

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

2393 

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

2395 NL = "\n " 

2396 return ( 

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

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

2399""", 

2400 names_for_globals, 

2401 annotations, 

2402 ) 

2403 

2404 

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

2406 """ 

2407 The default __init__ parameter name for a field. 

2408 

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

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

2411 """ 

2412 

2413 return name.lstrip("_") 

2414 

2415 

2416class Attribute: 

2417 """ 

2418 *Read-only* representation of an attribute. 

2419 

2420 .. warning:: 

2421 

2422 You should never instantiate this class yourself. 

2423 

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

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

2426 

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

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

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

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

2431 from a base class. 

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

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

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

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

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

2437 

2438 Instances of this class are frequently used for introspection purposes 

2439 like: 

2440 

2441 - `fields` returns a tuple of them. 

2442 - Validators get them passed as the first argument. 

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

2444 them. 

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

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

2447 

2448 

2449 .. versionadded:: 20.1.0 *inherited* 

2450 .. versionadded:: 20.1.0 *on_setattr* 

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

2452 equality checks and hashing anymore. 

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

2454 .. versionadded:: 22.2.0 *alias* 

2455 

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

2457 """ 

2458 

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

2460 # instantiation. 

2461 __slots__ = ( # noqa: RUF023 

2462 "name", 

2463 "default", 

2464 "validator", 

2465 "repr", 

2466 "eq", 

2467 "eq_key", 

2468 "order", 

2469 "order_key", 

2470 "hash", 

2471 "init", 

2472 "metadata", 

2473 "type", 

2474 "converter", 

2475 "kw_only", 

2476 "inherited", 

2477 "on_setattr", 

2478 "alias", 

2479 ) 

2480 

2481 def __init__( 

2482 self, 

2483 name, 

2484 default, 

2485 validator, 

2486 repr, 

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

2488 hash, 

2489 init, 

2490 inherited, 

2491 metadata=None, 

2492 type=None, 

2493 converter=None, 

2494 kw_only=False, 

2495 eq=None, 

2496 eq_key=None, 

2497 order=None, 

2498 order_key=None, 

2499 on_setattr=None, 

2500 alias=None, 

2501 ): 

2502 eq, eq_key, order, order_key = _determine_attrib_eq_order( 

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

2504 ) 

2505 

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

2507 bound_setattr = _OBJ_SETATTR.__get__(self) 

2508 

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

2510 # themselves. 

2511 bound_setattr("name", name) 

2512 bound_setattr("default", default) 

2513 bound_setattr("validator", validator) 

2514 bound_setattr("repr", repr) 

2515 bound_setattr("eq", eq) 

2516 bound_setattr("eq_key", eq_key) 

2517 bound_setattr("order", order) 

2518 bound_setattr("order_key", order_key) 

2519 bound_setattr("hash", hash) 

2520 bound_setattr("init", init) 

2521 bound_setattr("converter", converter) 

2522 bound_setattr( 

2523 "metadata", 

2524 ( 

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

2526 if metadata 

2527 else _EMPTY_METADATA_SINGLETON 

2528 ), 

2529 ) 

2530 bound_setattr("type", type) 

2531 bound_setattr("kw_only", kw_only) 

2532 bound_setattr("inherited", inherited) 

2533 bound_setattr("on_setattr", on_setattr) 

2534 bound_setattr("alias", alias) 

2535 

2536 def __setattr__(self, name, value): 

2537 raise FrozenInstanceError 

2538 

2539 @classmethod 

2540 def from_counting_attr( 

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

2542 ): 

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

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

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

2546 if type is None: 

2547 type = ca.type 

2548 elif ca.type is not None: 

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

2550 raise ValueError(msg) 

2551 return cls( 

2552 name, 

2553 ca._default, 

2554 ca._validator, 

2555 ca.repr, 

2556 None, 

2557 ca.hash, 

2558 ca.init, 

2559 False, 

2560 ca.metadata, 

2561 type, 

2562 ca.converter, 

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

2564 ca.eq, 

2565 ca.eq_key, 

2566 ca.order, 

2567 ca.order_key, 

2568 ca.on_setattr, 

2569 ca.alias, 

2570 ) 

2571 

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

2573 def evolve(self, **changes): 

2574 """ 

2575 Copy *self* and apply *changes*. 

2576 

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

2578 with :class:`attrs.Attribute`. 

2579 

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

2581 

2582 .. versionadded:: 20.3.0 

2583 """ 

2584 new = copy.copy(self) 

2585 

2586 new._setattrs(changes.items()) 

2587 

2588 return new 

2589 

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

2591 def __getstate__(self): 

2592 """ 

2593 Play nice with pickle. 

2594 """ 

2595 return tuple( 

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

2597 for name in self.__slots__ 

2598 ) 

2599 

2600 def __setstate__(self, state): 

2601 """ 

2602 Play nice with pickle. 

2603 """ 

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

2605 

2606 def _setattrs(self, name_values_pairs): 

2607 bound_setattr = _OBJ_SETATTR.__get__(self) 

2608 for name, value in name_values_pairs: 

2609 if name != "metadata": 

2610 bound_setattr(name, value) 

2611 else: 

2612 bound_setattr( 

2613 name, 

2614 ( 

2615 types.MappingProxyType(dict(value)) 

2616 if value 

2617 else _EMPTY_METADATA_SINGLETON 

2618 ), 

2619 ) 

2620 

2621 

2622_a = [ 

2623 Attribute( 

2624 name=name, 

2625 default=NOTHING, 

2626 validator=None, 

2627 repr=True, 

2628 cmp=None, 

2629 eq=True, 

2630 order=False, 

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

2632 init=True, 

2633 inherited=False, 

2634 alias=_default_init_alias_for(name), 

2635 ) 

2636 for name in Attribute.__slots__ 

2637] 

2638 

2639Attribute = _add_hash( 

2640 _add_eq( 

2641 _add_repr(Attribute, attrs=_a), 

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

2643 ), 

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

2645) 

2646 

2647 

2648class _CountingAttr: 

2649 """ 

2650 Intermediate representation of attributes that uses a counter to preserve 

2651 the order in which the attributes have been defined. 

2652 

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

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

2655 """ 

2656 

2657 __slots__ = ( 

2658 "_default", 

2659 "_validator", 

2660 "alias", 

2661 "converter", 

2662 "counter", 

2663 "eq", 

2664 "eq_key", 

2665 "hash", 

2666 "init", 

2667 "kw_only", 

2668 "metadata", 

2669 "on_setattr", 

2670 "order", 

2671 "order_key", 

2672 "repr", 

2673 "type", 

2674 ) 

2675 __attrs_attrs__ = ( 

2676 *tuple( 

2677 Attribute( 

2678 name=name, 

2679 alias=_default_init_alias_for(name), 

2680 default=NOTHING, 

2681 validator=None, 

2682 repr=True, 

2683 cmp=None, 

2684 hash=True, 

2685 init=True, 

2686 kw_only=False, 

2687 eq=True, 

2688 eq_key=None, 

2689 order=False, 

2690 order_key=None, 

2691 inherited=False, 

2692 on_setattr=None, 

2693 ) 

2694 for name in ( 

2695 "counter", 

2696 "_default", 

2697 "repr", 

2698 "eq", 

2699 "order", 

2700 "hash", 

2701 "init", 

2702 "on_setattr", 

2703 "alias", 

2704 ) 

2705 ), 

2706 Attribute( 

2707 name="metadata", 

2708 alias="metadata", 

2709 default=None, 

2710 validator=None, 

2711 repr=True, 

2712 cmp=None, 

2713 hash=False, 

2714 init=True, 

2715 kw_only=False, 

2716 eq=True, 

2717 eq_key=None, 

2718 order=False, 

2719 order_key=None, 

2720 inherited=False, 

2721 on_setattr=None, 

2722 ), 

2723 ) 

2724 cls_counter = 0 

2725 

2726 def __init__( 

2727 self, 

2728 default, 

2729 validator, 

2730 repr, 

2731 cmp, 

2732 hash, 

2733 init, 

2734 converter, 

2735 metadata, 

2736 type, 

2737 kw_only, 

2738 eq, 

2739 eq_key, 

2740 order, 

2741 order_key, 

2742 on_setattr, 

2743 alias, 

2744 ): 

2745 _CountingAttr.cls_counter += 1 

2746 self.counter = _CountingAttr.cls_counter 

2747 self._default = default 

2748 self._validator = validator 

2749 self.converter = converter 

2750 self.repr = repr 

2751 self.eq = eq 

2752 self.eq_key = eq_key 

2753 self.order = order 

2754 self.order_key = order_key 

2755 self.hash = hash 

2756 self.init = init 

2757 self.metadata = metadata 

2758 self.type = type 

2759 self.kw_only = kw_only 

2760 self.on_setattr = on_setattr 

2761 self.alias = alias 

2762 

2763 def validator(self, meth): 

2764 """ 

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

2766 

2767 Returns *meth* unchanged. 

2768 

2769 .. versionadded:: 17.1.0 

2770 """ 

2771 if self._validator is None: 

2772 self._validator = meth 

2773 else: 

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

2775 return meth 

2776 

2777 def default(self, meth): 

2778 """ 

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

2780 

2781 Returns *meth* unchanged. 

2782 

2783 Raises: 

2784 DefaultAlreadySetError: If default has been set before. 

2785 

2786 .. versionadded:: 17.1.0 

2787 """ 

2788 if self._default is not NOTHING: 

2789 raise DefaultAlreadySetError 

2790 

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

2792 

2793 return meth 

2794 

2795 

2796_CountingAttr = _add_eq(_add_repr(_CountingAttr)) 

2797 

2798 

2799class ClassProps: 

2800 """ 

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

2802 `define()` decorators. 

2803 

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

2805 to construct the final class. 

2806 

2807 Warning: 

2808 

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

2810 strict backwards-compatibility guarantees. 

2811 

2812 

2813 Attributes: 

2814 is_exception (bool): 

2815 Whether the class is treated as an exception class. 

2816 

2817 is_slotted (bool): 

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

2819 

2820 has_weakref_slot (bool): 

2821 Whether the class has a slot for weak references. 

2822 

2823 is_frozen (bool): 

2824 Whether the class is frozen. 

2825 

2826 kw_only (KeywordOnly): 

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

2828 ``__init__`` method. 

2829 

2830 collected_fields_by_mro (bool): 

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

2832 That is, correctly but unlike `dataclasses`. 

2833 

2834 added_init (bool): 

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

2836 

2837 added_repr (bool): 

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

2839 

2840 added_eq (bool): 

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

2842 

2843 added_ordering (bool): 

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

2845 

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

2847 

2848 added_match_args (bool): 

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

2850 fields. 

2851 

2852 added_str (bool): 

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

2854 

2855 added_pickling (bool): 

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

2857 ``__setstate__`` methods for `pickle`. 

2858 

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

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

2861 

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

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

2864 

2865 .. versionadded:: 25.4.0 

2866 """ 

2867 

2868 class Hashability(enum.Enum): 

2869 """ 

2870 The hashability of a class. 

2871 

2872 .. versionadded:: 25.4.0 

2873 """ 

2874 

2875 HASHABLE = "hashable" 

2876 """Write a ``__hash__``.""" 

2877 HASHABLE_CACHED = "hashable_cache" 

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

2879 UNHASHABLE = "unhashable" 

2880 """Set ``__hash__`` to ``None``.""" 

2881 LEAVE_ALONE = "leave_alone" 

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

2883 

2884 class KeywordOnly(enum.Enum): 

2885 """ 

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

2887 

2888 .. versionadded:: 25.4.0 

2889 """ 

2890 

2891 NO = "no" 

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

2893 YES = "yes" 

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

2895 FORCE = "force" 

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

2897 

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

2899 "is_exception", 

2900 "is_slotted", 

2901 "has_weakref_slot", 

2902 "is_frozen", 

2903 "kw_only", 

2904 "collected_fields_by_mro", 

2905 "added_init", 

2906 "added_repr", 

2907 "added_eq", 

2908 "added_ordering", 

2909 "hashability", 

2910 "added_match_args", 

2911 "added_str", 

2912 "added_pickling", 

2913 "on_setattr_hook", 

2914 "field_transformer", 

2915 ) 

2916 

2917 def __init__( 

2918 self, 

2919 is_exception, 

2920 is_slotted, 

2921 has_weakref_slot, 

2922 is_frozen, 

2923 kw_only, 

2924 collected_fields_by_mro, 

2925 added_init, 

2926 added_repr, 

2927 added_eq, 

2928 added_ordering, 

2929 hashability, 

2930 added_match_args, 

2931 added_str, 

2932 added_pickling, 

2933 on_setattr_hook, 

2934 field_transformer, 

2935 ): 

2936 self.is_exception = is_exception 

2937 self.is_slotted = is_slotted 

2938 self.has_weakref_slot = has_weakref_slot 

2939 self.is_frozen = is_frozen 

2940 self.kw_only = kw_only 

2941 self.collected_fields_by_mro = collected_fields_by_mro 

2942 self.added_init = added_init 

2943 self.added_repr = added_repr 

2944 self.added_eq = added_eq 

2945 self.added_ordering = added_ordering 

2946 self.hashability = hashability 

2947 self.added_match_args = added_match_args 

2948 self.added_str = added_str 

2949 self.added_pickling = added_pickling 

2950 self.on_setattr_hook = on_setattr_hook 

2951 self.field_transformer = field_transformer 

2952 

2953 @property 

2954 def is_hashable(self): 

2955 return ( 

2956 self.hashability is ClassProps.Hashability.HASHABLE 

2957 or self.hashability is ClassProps.Hashability.HASHABLE_CACHED 

2958 ) 

2959 

2960 

2961_cas = [ 

2962 Attribute( 

2963 name=name, 

2964 default=NOTHING, 

2965 validator=None, 

2966 repr=True, 

2967 cmp=None, 

2968 eq=True, 

2969 order=False, 

2970 hash=True, 

2971 init=True, 

2972 inherited=False, 

2973 alias=_default_init_alias_for(name), 

2974 ) 

2975 for name in ClassProps.__slots__ 

2976] 

2977 

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

2979 

2980 

2981class Factory: 

2982 """ 

2983 Stores a factory callable. 

2984 

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

2986 generate a new value. 

2987 

2988 Args: 

2989 factory (typing.Callable): 

2990 A callable that takes either none or exactly one mandatory 

2991 positional argument depending on *takes_self*. 

2992 

2993 takes_self (bool): 

2994 Pass the partially initialized instance that is being initialized 

2995 as a positional argument. 

2996 

2997 .. versionadded:: 17.1.0 *takes_self* 

2998 """ 

2999 

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

3001 

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

3003 self.factory = factory 

3004 self.takes_self = takes_self 

3005 

3006 def __getstate__(self): 

3007 """ 

3008 Play nice with pickle. 

3009 """ 

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

3011 

3012 def __setstate__(self, state): 

3013 """ 

3014 Play nice with pickle. 

3015 """ 

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

3017 setattr(self, name, value) 

3018 

3019 

3020_f = [ 

3021 Attribute( 

3022 name=name, 

3023 default=NOTHING, 

3024 validator=None, 

3025 repr=True, 

3026 cmp=None, 

3027 eq=True, 

3028 order=False, 

3029 hash=True, 

3030 init=True, 

3031 inherited=False, 

3032 ) 

3033 for name in Factory.__slots__ 

3034] 

3035 

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

3037 

3038 

3039class Converter: 

3040 """ 

3041 Stores a converter callable. 

3042 

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

3044 arguments are passed in the order they are documented. 

3045 

3046 Args: 

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

3048 

3049 takes_self (bool): 

3050 Pass the partially initialized instance that is being initialized 

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

3052 

3053 takes_field (bool): 

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

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

3056 

3057 .. versionadded:: 24.1.0 

3058 """ 

3059 

3060 __slots__ = ( 

3061 "__call__", 

3062 "_first_param_type", 

3063 "_global_name", 

3064 "converter", 

3065 "takes_field", 

3066 "takes_self", 

3067 ) 

3068 

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

3070 self.converter = converter 

3071 self.takes_self = takes_self 

3072 self.takes_field = takes_field 

3073 

3074 ex = _AnnotationExtractor(converter) 

3075 self._first_param_type = ex.get_first_param_type() 

3076 

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

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

3079 elif self.takes_self and not self.takes_field: 

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

3081 value, instance 

3082 ) 

3083 elif not self.takes_self and self.takes_field: 

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

3085 value, field 

3086 ) 

3087 else: 

3088 self.__call__ = self.converter 

3089 

3090 rt = ex.get_return_type() 

3091 if rt is not None: 

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

3093 

3094 @staticmethod 

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

3096 """ 

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

3098 would have. 

3099 """ 

3100 return f"__attr_converter_{attr_name}" 

3101 

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

3103 """ 

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

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

3106 `self.takes_self` and `self.takes_field`. 

3107 """ 

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

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

3110 

3111 if self.takes_self and self.takes_field: 

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

3113 

3114 if self.takes_self: 

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

3116 

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

3118 

3119 def __getstate__(self): 

3120 """ 

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

3122 computed when loading. 

3123 """ 

3124 return { 

3125 "converter": self.converter, 

3126 "takes_self": self.takes_self, 

3127 "takes_field": self.takes_field, 

3128 } 

3129 

3130 def __setstate__(self, state): 

3131 """ 

3132 Load instance from state. 

3133 """ 

3134 self.__init__(**state) 

3135 

3136 

3137_f = [ 

3138 Attribute( 

3139 name=name, 

3140 default=NOTHING, 

3141 validator=None, 

3142 repr=True, 

3143 cmp=None, 

3144 eq=True, 

3145 order=False, 

3146 hash=True, 

3147 init=True, 

3148 inherited=False, 

3149 ) 

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

3151] 

3152 

3153Converter = _add_hash( 

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

3155) 

3156 

3157 

3158def make_class( 

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

3160): 

3161 r""" 

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

3163 

3164 .. note:: 

3165 

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

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

3168 

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

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

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

3172 

3173 .. warning:: 

3174 

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

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

3177 you. 

3178 

3179 Args: 

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

3181 

3182 attrs (list | dict): 

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

3184 s / `attrs.field`\ s. 

3185 

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

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

3188 attributes is used. 

3189 

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

3191 

3192 class_body (dict): 

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

3194 

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

3196 

3197 Returns: 

3198 type: A new class with *attrs*. 

3199 

3200 .. versionadded:: 17.1.0 *bases* 

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

3202 .. versionchanged:: 23.2.0 *class_body* 

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

3204 """ 

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

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

3207 

3208 if isinstance(attrs, dict): 

3209 cls_dict = attrs 

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

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

3212 else: 

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

3214 raise TypeError(msg) 

3215 

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

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

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

3219 

3220 body = {} 

3221 if class_body is not None: 

3222 body.update(class_body) 

3223 if pre_init is not None: 

3224 body["__attrs_pre_init__"] = pre_init 

3225 if post_init is not None: 

3226 body["__attrs_post_init__"] = post_init 

3227 if user_init is not None: 

3228 body["__init__"] = user_init 

3229 

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

3231 

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

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

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

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

3236 with contextlib.suppress(AttributeError, ValueError): 

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

3238 "__name__", "__main__" 

3239 ) 

3240 

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

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

3243 ( 

3244 attributes_arguments["eq"], 

3245 attributes_arguments["order"], 

3246 ) = _determine_attrs_eq_order( 

3247 cmp, 

3248 attributes_arguments.get("eq"), 

3249 attributes_arguments.get("order"), 

3250 True, 

3251 ) 

3252 

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

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

3255 cls.__annotations__ = { 

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

3257 } 

3258 return cls 

3259 

3260 

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

3262# import into .validators / .converters. 

3263 

3264 

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

3266class _AndValidator: 

3267 """ 

3268 Compose many validators to a single one. 

3269 """ 

3270 

3271 _validators = attrib() 

3272 

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

3274 for v in self._validators: 

3275 v(inst, attr, value) 

3276 

3277 

3278def and_(*validators): 

3279 """ 

3280 A validator that composes multiple validators into one. 

3281 

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

3283 

3284 Args: 

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

3286 Arbitrary number of validators. 

3287 

3288 .. versionadded:: 17.1.0 

3289 """ 

3290 vals = [] 

3291 for validator in validators: 

3292 vals.extend( 

3293 validator._validators 

3294 if isinstance(validator, _AndValidator) 

3295 else [validator] 

3296 ) 

3297 

3298 return _AndValidator(tuple(vals)) 

3299 

3300 

3301def pipe(*converters): 

3302 """ 

3303 A converter that composes multiple converters into one. 

3304 

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

3306 *last* value. 

3307 

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

3309 have any. 

3310 

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

3312 Arbitrary number of converters. 

3313 

3314 .. versionadded:: 20.1.0 

3315 """ 

3316 

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

3318 

3319 if return_instance: 

3320 

3321 def pipe_converter(val, inst, field): 

3322 for c in converters: 

3323 val = ( 

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

3325 ) 

3326 

3327 return val 

3328 

3329 else: 

3330 

3331 def pipe_converter(val): 

3332 for c in converters: 

3333 val = c(val) 

3334 

3335 return val 

3336 

3337 if not converters: 

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

3339 A = TypeVar("A") 

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

3341 else: 

3342 # Get parameter type from first converter. 

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

3344 if t: 

3345 pipe_converter.__annotations__["val"] = t 

3346 

3347 last = converters[-1] 

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

3349 last = last.__call__ 

3350 

3351 # Get return type from last converter. 

3352 rt = _AnnotationExtractor(last).get_return_type() 

3353 if rt: 

3354 pipe_converter.__annotations__["return"] = rt 

3355 

3356 if return_instance: 

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

3358 return pipe_converter