Sample
attrs 26.1.0: Decide whether attrs still buys anything over dataclasses on modern Python, and what changes in each direction
Verified sample for pypi attrs 26.1.0: Decide whether attrs still buys anything over dataclasses on modern Python, and what changes in each direction. The…
sha256:db459e2a5619e5bc96877e0fc6aefd6229be3342cc28685624eacd9e170bf4b7
This network offers one thing: a sample that builds. It ran the sample in a sandbox and kept the signed receipt. It grades nothing and warrants nothing — whether the same code builds where you are is not something it measured.
How many distinct signing keys filed a passing contract receipt. One is the author alone; more than one means somebody else built it too. A key is self-generated with nothing registered behind it, so it counts keys, not people.
MIT-0
Execution evidence
The declared environment and the signed runs are kept apart, so you can see exactly what this sample ran and where.
- Evidence basis
- Signed contract pass
- Verification receipts
- 2
- Signing keys that built it
- 2
Declared environment
python 3.12 linux x64 python 3.12 python pip
Verification-run environments
| Environment | Contract | Stages | Run |
|---|---|---|---|
| python 3.12 · linux alpine/x64 · docker ed25519:a2ec939a4c60e243 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · pypi@1 |
2026-08-14 |
| python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · pypi@1 |
2026-08-18 |
Case
MIGRATION- Goal
- Decide whether attrs still buys anything over dataclasses on modern Python, and what changes in each direction
- Packages
- Symbols
-
- attrs.define
- attrs.field
- attrs.frozen
- attrs.evolve
- attrs.validators.instance_of
- attrs.validators.disabled
- attrs.setters.NO_OP
- attrs.asdict
- attrs.exceptions.FrozenInstanceError
- attr.s
- dataclasses.dataclass
- dataclasses.replace
- dataclasses.FrozenInstanceError
- Environment
- python 3.12
- Created
- 2026-08-14T12:07:55Z
Contract
- assert neither library enforces an annotation: a dataclass stores a str in an int field and so does an @define class with no validator
- assert an attrs validator runs at __init__, with instance_of raising TypeError and gt raising ValueError, and that ge, lt and le raise ValueError too
- assert the converter runs before the validator, so converter=int and instance_of(int) coexist and "9000" is stored as 9000
- assert @define runs converter and validator again on assignment, where the dataclass keeps whatever was assigned
- assert __post_init__ is not the equivalent hook: it runs at __init__ and again through dataclasses.replace, and never on assignment
- assert on_setattr=NO_OP keeps the __init__ conversion and drops the assignment one, so the field holds either type
- assert attrs.validators.disabled() switches off the validators of every class in the process, so a validator is not a boundary
- assert @define is slotted, so an undeclared attribute raises AttributeError and has no __dict__, while the dataclass accepts it
- assert @dataclass(slots=True) exists and that both decorators return a new class object, not the one that was decorated
- assert attrs puts __weakref__ in __slots__ and @dataclass(slots=True) does not, so weakref.ref works on the attrs class and raises TypeError on the dataclass
- assert the slotted dataclass breaks zero-argument super() on Python 3.12 with TypeError, and attrs' slotted subclass with the same body does not
- assert the cause is the __class__ closure cell, which attrs rebinds to the class it returns and the stdlib leaves on the discarded original, and that super(Child, self) works around it
- assert attrs' FrozenInstanceError reports module attr.exceptions and is a different class from dataclasses.FrozenInstanceError, with neither a subclass of the other
- assert catching dataclasses.FrozenInstanceError does not catch the attrs error and catching the attrs error does not catch the dataclass one
- assert both frozen errors subclass AttributeError, the narrowest single except clause that covers both
- assert a mandatory field after a defaulted one is refused at class definition by both, ValueError from attrs and TypeError from dataclasses
- assert kw_only=True lifts that rule in both libraries
- assert an attrs field named _token takes the init keyword token and refuses _token, while the dataclass field does the reverse
- assert attrs.evolve keys on the init parameter name and re-runs the validator, while dataclasses.replace keys on the field name and runs no validator of its own
- assert an init=False field is refused by replace with ValueError naming the field and by evolve with TypeError for an unknown keyword
- assert the libraries do not recognise each other: is_dataclass and attrs.has are False, dataclasses.fields raises TypeError and attrs.fields raises NotAnAttrsClassError
- assert a fresh interpreter run under -W error imports attr with no warning at all, and attr.__version__ matches the installed attrs
- assert the namespaces moved one way only: define, field, frozen, evolve and fields are the same objects on attr and attrs, while s, ib, attrib and attributes exist only on attr
- assert a shared name is not always a shared object: attr.asdict is not attrs.asdict and only the old one takes retain_collection_types, and attr.exceptions is not attrs.exceptions
- assert an @attr.s class is unslotted and converts at __init__ only, leaving object.__setattr__ in place where @define installs its own
Files
- csx.json
- requirements.txt
- src/__init__.py
- src/models.py
- test/contract.py