Repository navigation
fix(foundation): make register_mh_extension take schema keyword-only (#516) - #526
Conversation
…516) - register_mh_extension declared `schema` as POSITIONAL_OR_KEYWORD while its fourteen schema-taking siblings all declare it keyword-only; add the missing `*` so the fifteen share one call shape. - tests: add test_schema_registrars_share_one_signature_contract, which discovers every schema-taking register_* helper from `__all__` and pins `code`/`meth` positional and `schema` keyword-only with a None default across all fifteen, rather than only the one that had regressed. - the new test also calls each helper with a third positional argument and requires the interpreter's own arity TypeError, asserting that error is not a pcapkit BaseError: RegistryError derives from TypeError, so a bare assertRaises(TypeError) there would pass against the unfixed code. Verified against the unfixed signature: tests/foundation/registry/test_protocols.py reports 1 failed / 69 subtests passed (exit 1) without the `*`, and 4 passed / 70 subtests passed (exit 0) with it.
|
✅ GOOD TO MERGE — the |
Detailed review (independent verification, falsify-not-bless)Reviewed at head 1. The actual diff. Exactly one production-code line: 2. The 3. Test rewrite (static signature check → real call) — verified safe for all 15 siblings, not just one. The inherited test only checked 4. Fails-without-fix, reproduced with correct exit-code handling. Full file
Exactly one of fifteen subtests flips — simultaneously the proof of the fix and the proof the other 14 siblings were already correct. Exit codes were captured directly ( 5. Falsification attempt. Tried a 6. All 15 siblings enumerated — no second victim of #516.
7. Full targeted run. What could not be fully ruled outA VerdictNo discrepancies between claimed and reproduced numbers. Recommend merge. |
Closes #516.
Why
register_mh_extensionwas the only one of the fifteen schema-takingregister_*helpers in
pcapkit/foundation/registry/protocols.pythat declaredschemaasPOSITIONAL_OR_KEYWORD. The other fourteen all carry a*before it. Theasymmetry is clearly unintended — nothing about MH CGA extensions distinguishes
them — so this adds the missing
*and makes all fifteen share one call shape.This is technically breaking for anyone passing
schemapositionally, which iswhy it belongs in the prerelease window rather than after it.
The fix
One character,
protocols.py:537:Test
test_schema_registrars_share_one_signature_contractpins the consistencyrather than the single function that broke it, because the defect was an
inconsistency. It discovers every
register_*helper in__all__that takes aschemaparameter and, for each, assertscode/methare positional with nodefault and
schemais keyword-only defaulting toNone. The loop runs overwhat it discovers, so a sibling added later is covered without touching the test;
a
SCHEMA_REGISTRARSfloor list keeps it from passing vacuously if discoveryever stops finding them. That is 15 subtests, one per helper.
Two complementary layers of assertion:
inspect.Signature.bindfor the forms that must be accepted(
(code, meth)and(code, meth, schema=...)). Binding rather than callingkeeps this free of registry side effects.
rejected, so the contract is proven against the functions themselves and not
only against
inspect's model of them. This is side-effect-free too: akeyword-only parameter is rejected during argument binding, before the body
runs.
That second assertion needs a guard, and it is the one non-obvious thing here.
RegistryErroris declaredclass RegistryError(BaseError, TypeError), so abare
assertRaises(TypeError)around the call would be satisfied by a body thataccepted the third positional and then rejected its value — the exact #516
behaviour the test exists to forbid. Measured against the unfixed code, using
the natural string
meththat the sibling test attest_protocols.py:214already uses:
So the test additionally asserts the exception is not a pcapkit
BaseErrorand that its message matches
positional argument, pinning the interpreter'sarity error specifically.
Fails without the fix
Reverting just the
*and re-running, on this PR's exact base (c8fd97bcd):register_mh_extensionsignature*(unfixed)1 failed, 4 passed, 69 subtests passed*(this PR)4 passed, 70 subtests passedExactly one of the 15 subtests flips (69 vs 70), which also confirms the other
fourteen siblings were already correct.
Note the exit codes are quoted deliberately: a failing subtest still reports its
parent test as
PASSED, so the summary line alone is misleading and the exitcode is the signal.
In the full run the
schema.kindassertion fires first and masks the addedreal-call assertion, so that one was also checked in isolation against the
unfixed signature — it escapes
AttributeError: 'object' object has no attribute 'name'rather than theTypeErrorit requires, i.e. it catches #516 on its own.Wider check,
tests/foundation/registry/post-rebase:7 passed, 80 subtests passed, exit 0.Notes
PYTHONSAFEPATH=1,PYTHONDONTWRITEBYTECODE=1andPYTHONPATHset to this worktree, assertingpcapkit.__file__resolved here rather than to the editable install, since thevenv has pcapkit installed editable against a different checkout.
register_extractor_*andregister_dumper_*families, and of the non-schemaregister_*helpers(
register_apptype,register_linktype, …). Only the fifteen schema-takinghelpers named in register_mh_extension takes schema positionally; the other 14 register_* are keyword-only #516 are covered here.