assert-every-dataclass-field-is-read
Assert that every field a dataclass declares is read somewhere.
Why
A dataclass field is written on every instance whether or not anything
wants it. The generated __init__ stores what it is handed, so the
write is automatic and guaranteed. Only a read is evidence that the
field is still wanted, and a read is what disappears quietly.
Delete the last line that reads a field and nothing reports it. The program still runs, the field is still declared, every caller still has to supply a value, and the tests covering the field still pass, because they construct their own instance and assert on a value they passed in themselves. The declaration has become a claim about the program that is no longer true, and the test suite is the thing that hides it.
This tool asks the other question. For every field declared by a
@dataclass in the trees you point it at, it asks whether anything
reads the value back, and reports the ones nothing does.
Installation
pip install assert-every-dataclass-field-is-read
Usage
# Every dataclass field in the program must be read by the program
assert-every-dataclass-field-is-read src lib/python scripts
# Search the tests too, but not the tests belonging to the package
# whose field is being checked
assert-every-dataclass-field-is-read lib/python \
--search-in lib/python --search-in test \
--dont-search-in 'test/lib/python/{package}'
The first form is the one to reach for. Leaving the test tree out of the search entirely is both simpler and stricter than excusing a package's own tests, and it is the right default whenever the trees you check are the deployed program rather than test support code.
Reach for --dont-search-in when a tree holds dataclasses that tests are
entitled to read — fixtures, doubles, helpers — and you still want a
field kept alive only by its own tests to be reported.
Options
| Option | Effect |
|---|---|
--search-in PATH |
A tree to search for reads. Repeatable. |
--dont-search-in TEMPLATE |
Template holding {package}, left out. |
--exclude PATTERNS |
Comma-separated globs to leave out of both trees. |
--assume-read-matching PATTERNS |
Globs of field names a runtime reads. |
--quiet |
Print nothing; report through the exit code. |
--verbose |
Print each file scanned, each one skipped, and a summary. |
Exit codes
| Code | Meaning |
|---|---|
| 0 | Every field is read. |
| 1 | At least one field is read by nothing. |
| 2 | A path was missing, unreadable, or would not parse. |
What counts as a read
A field Widget.color is read when the tool can see an expression that
fetches it. It pairs an attribute with the class that owns it, rather
than matching the bare name, so a local variable called color is not
mistaken for a read of the field.
| Shape | Resolved through |
|---|---|
widget.color |
An argument or variable annotated Widget. |
widget.color |
A local assigned from Widget(...). |
widget.color |
A local assigned from a function returning Widget. |
Widget.color |
The class itself. |
self.color |
The body of Widget or of a subclass. |
widget.color += 1 |
An augmented assignment still reads first. |
row.color |
A loop or comprehension over list[Widget]. |
getattr(widget, "color") |
The name given as a string. |
asdict(widget) |
Every field of Widget, and of what it holds. |
Annotations are resolved through X | None, quoted forms, and the
element of a container (list[X], tuple[X, ...], dict[K, X] and
their typing equivalents). Variables are scoped per function, so a name
bound in one function says nothing about the same name in another.
Writes do not count. widget.color = "red" is how the field is filled
in, not evidence that anything wants it, and dataclasses.replace is
treated as a read only when the instance it is handed resolves to a
class.
What it does not see
An attribute read whose base cannot be resolved to a class — a bare parameter, a subscript, a chained attribute — is counted as a read of every field of that name, because reporting it would be a guess. So a field whose name is shared with an attribute read loosely somewhere else is not reported.
A field a runtime reads for you, through a serializer or a template, has
no read to find. Name it with --assume-read-matching rather than
leaving it to be reported.
ClassVar annotations and unannotated class attributes are not
dataclass fields, and are not checked.
GitHub Action
- uses: 10U-Labs/assert-every-dataclass-field-is-read@latest
with:
trees: src lib/python scripts
verbose: "true"
| Input | Effect |
|---|---|
trees |
Trees holding the dataclasses to check. Required. |
search-in |
Trees to search for reads, space-separated. |
dont-search-in |
Template holding {package} to leave out. |
exclude |
Comma-separated globs to exclude files. |
assume-read-matching |
Comma-separated globs of field names. |
quiet |
Suppress output, exit code only. |
verbose |
Show trees read, fields found, findings, summary. |
License
Apache-2.0.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file assert_every_dataclass_field_is_read-20260911130813.tar.gz.
File metadata
- Download URL: assert_every_dataclass_field_is_read-20260911130813.tar.gz
- Upload date:
- Size: 30.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
78023f5e246d131af991c7f187ed76bf6b100e2b87c2947f2a038b9d1af32688
|
|
| MD5 |
b3908a8f749f4286e4a82b5ec0090021
|
|
| BLAKE2b-256 |
1f4698582f99710525d07a84e88a32fee13eccca71dff81a46ed8dfece3de67c
|
Provenance
The following attestation bundles were made for assert_every_dataclass_field_is_read-20260911130813.tar.gz:
Publisher:
release.yml on 10U-Labs/assert-every-dataclass-field-is-read
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
assert_every_dataclass_field_is_read-20260911130813.tar.gz -
Subject digest:
78023f5e246d131af991c7f187ed76bf6b100e2b87c2947f2a038b9d1af32688 - Sigstore transparency entry: 2793544986
- Sigstore integration time:
-
Permalink:
10U-Labs/assert-every-dataclass-field-is-read@c95208f6739827f5fa9119b2968466985afe7753 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/10U-Labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c95208f6739827f5fa9119b2968466985afe7753 -
Trigger Event:
push
-
Statement type:
File details
Details for the file assert_every_dataclass_field_is_read-20260911130813-py3-none-any.whl.
File metadata
- Download URL: assert_every_dataclass_field_is_read-20260911130813-py3-none-any.whl
- Upload date:
- Size: 15.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4cb77adb2800850b7f60767a449c2ff20646a151b377b83d70357b6471612d66
|
|
| MD5 |
7cb1f6e9897f5485ff9ebceedcceba37
|
|
| BLAKE2b-256 |
88cb1653f207592a4bfd159b11db44b52160abb48f66321c5c725d1fbc2cdea3
|
Provenance
The following attestation bundles were made for assert_every_dataclass_field_is_read-20260911130813-py3-none-any.whl:
Publisher:
release.yml on 10U-Labs/assert-every-dataclass-field-is-read
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
assert_every_dataclass_field_is_read-20260911130813-py3-none-any.whl -
Subject digest:
4cb77adb2800850b7f60767a449c2ff20646a151b377b83d70357b6471612d66 - Sigstore transparency entry: 2793545011
- Sigstore integration time:
-
Permalink:
10U-Labs/assert-every-dataclass-field-is-read@c95208f6739827f5fa9119b2968466985afe7753 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/10U-Labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c95208f6739827f5fa9119b2968466985afe7753 -
Trigger Event:
push
-
Statement type: