We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Data Modelling and Invariants step 14 of 25
field(): the seven knobs that change generated behaviour
repr=False on a credential field is the difference between a live token sitting
in your log aggregator forever and not. Every logger.info("built %s", creds),
every unhandled-exception frame dump, every pytest assertion diff calls
__repr__. The default @dataclass repr prints every field, verbatim.
field() is the per-field escape hatch. Nine knobs:
| knob | effect |
|---|---|
default |
the value, evaluated once at class-creation time |
default_factory |
a zero-arg callable, invoked on every construction |
init |
include as an __init__ parameter |
repr |
include in the generated __repr__ |
compare |
include in __eq__ and in __lt__/__le__/… under order=True |
hash |
include in __hash__; None (the default) means follow compare |
metadata |
an arbitrary mapping, exposed read-only; ignored by the generator |
kw_only |
make this one field keyword-only |
doc |
per-field docstring (⚠ 3.14) |
Two of those are sharper than they look.
compare=False also removes the field from __hash__. Because hash
defaults to None, and None means “do whatever compare does”. So on a frozen
dataclass, marking a field compare=False silently changes its hash contract as
well as its equality contract. That is usually what you want (a cached derived
value should be in neither), but it is worth knowing it is one decision, not two.
metadata is a MappingProxyType. Not a copied dict you can mutate — a
genuinely read-only view, frozen at class creation. It is also the documented
namespace third-party serializers key off: cattrs, dataclasses-json, and every
in-house codec you will ever write puts its per-field hints there. Conventionally
you namespace your keys so two libraries do not collide.
Why the repr is the right place to enforce this
Not __str__ — nothing calls __str__ in a traceback. Not a to_log() helper —
nobody remembers to call it. __repr__ is the method that fires when your code
is not being careful, which is exactly when you need the redaction.
Your task
Implement:
def redacted_repr(obj: DataclassInstance) -> str:
It renders like the builtin dataclass repr — ClassName(a=1, b='x') — with three
differences:
-
a field whose
metadatacontains{"sensitive": True}renders its value as the literal five characters'***'(an apostrophe, three stars, an apostrophe — i.e. whatrepr("***")would give); -
a field with
repr=Falseis omitted entirely, not redacted; - a field whose value is itself a dataclass instance is rendered by recursing, so nested secrets are redacted too.
Everything else uses repr(value) — including Mapping values, which render
exactly as their own repr does.
solve(sample) looks an instance up in SAMPLES and returns its redacted repr.
Types. Field.metadata is typed Mapping[Any, Any], so f.metadata["sensitive"]
is Any — and Any propagates silently into whatever you assign it to. Convert
at the boundary (bool(f.metadata.get("sensitive", False))) so nothing untyped
escapes. For the recursion you need to narrow object to a dataclass instance;
is_dataclass(x) alone narrows to the union DataclassInstance | type[DataclassInstance],
so the stdlib’s own idiom is is_dataclass(x) and not isinstance(x, type).
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.