Implementation Patterns#
These patterns help evolve ArgDigest without breaking downstream integrations.
Pattern 1: contract-first evolution#
Define expected behavior before implementation changes.
Keep documentation and tests aligned with contract decisions.
Pattern 2: explicit precedence#
When adding new configuration behaviors, keep precedence stable:
decorator arguments,
explicit config module,
environment config module (
ARGDIGEST_CONFIG),auto-discovery defaults.
Pattern 3: migration-safe defaults#
Prefer non-breaking defaults in minor versions.
Introduce stricter behavior behind explicit options first.
With one standing exception, taken in 0.10.0: ArgDigest must never end up more
permissive than the language it wraps. Plain Python raises TypeError for an unexpected
keyword and a decorated function accepted it, so unknown_argument defaults to error
even though that breaks calls that used to pass. Shipping a stable release on top of that
would have frozen the anomaly into the contract every downstream library depends on.
A default that makes ArgDigest weaker than plain Python is a defect, not a migration-friendly choice, and it should be corrected rather than deferred.
Pattern 4: architecture freedom for adopters#
Do not force one digestion layout. Preserve support for:
package style,
registry style,
decorator style,
mixed mode.
Pattern 5: rules are data, dispatch is lookup#
All three declaration mechanisms — argument digesters, function contracts and alias tables — follow the same shape, and new ones should too:
the consumer declares data, discovered by scanning a package it names in its config;
ArgDigest owns discovery, resolution order, enforcement and diagnostics;
resolution is a lookup, never a chain of
if caller == ....
The reason is concrete. Before function contracts existed, function-dependent rules had
nowhere to live and lodged inside per-argument digesters one branch at a time: 102 of
MolSysMT’s 393 digesters branch on caller, and the argument contract of a function was
never written in one readable place. A mechanism that forces consumers to write dispatch
logic will grow that shape again.
Declaring rules as data also makes them readable: describe_contract and
describe_normalization render them, so what a function accepts can appear in its own
documentation instead of being a consequence of a branch. A callable would enforce just
as well and document nothing.
Pattern 6: observable failures#
Raise structured errors with context.
Keep warning/error messages actionable for integrators.