Repository navigation
refactor: import each *Base class under its own name, not the public one - #750
Conversation
Library modules imported a *Base class aliased to its public name, so the source read `class PCAP(Engine)` while actually inheriting `EngineBase`. Per the ruling on #514 the split is permanent and library classes inherit the base, so the base is now imported under its own name. * rewrote 28 of the 82 alias imports -- EngineBase 8 of 8, ReassemblyBase 3 of 3, DumperBase 2 of 2, TraceFlowBase 2 of 2, ProtocolBase 13 of 67 * renamed the code and annotation references that followed, including `cast()` targets, `TypeVar(bound=)` and `# type:` comments, but not the string values inside `Literal[...]` * corrected four `docs/source` index pages that named the public class while their own class diagram roots the hierarchy at the base * added `tests/test_base_class_contract.py`, pinning the contract per suite The four non-protocols families are complete. The remaining 54 sites are all `ProtocolBase` ones in paths #726 and #742 own, recorded as prefixes in `PENDING_ALIAS_PATHS` so the entry fails once its pull request lands. No behaviour change: name registry 38 keys before and after, `descendants(Public)` 0 in all five suites, 45 dispatches identical and still identical after clearing the name registry, mypy at its 112-error baseline with none introduced.
|
✅ GOOD TO MERGE @ |
|
✅ GOOD TO MERGE @
Found wrong in the PR body (not in code): "28 sites in 22 files" → I measure 21 files; "20 such modules" checked for Unreproduced (environment, not code): coverage 45%→48% claim — I got 59%→63% (fixtures unbuilt, ✅ GOOD TO MERGE @ |
…st 54 sites (#752) Completes #514 part (c). #750 renamed 28 of 82 `ProtocolBase as Protocol` alias imports and deferred the remaining 54 -- all `ProtocolBase` -- to paths #726 and #742 owned. Both have merged, so the deferral is over. * renamed the alias import at all 54 sites (51 under pcapkit/protocols/, 3 under pcapkit/foundation/) and every in-file reference that used the local alias: class headers, annotations, cast(), isinstance/issubclass checks, # type: comments, and the bracketed part of a handful of Sphinx #: doc comments -- 191 lines changed, no statements added * merged two now-unaliased same-module imports per isort in 4 files (application.py, internet.py, link.py, transport.py), removing 4 statements * left descriptive prose, protocol-name string literals, error-message text, and fully-qualified :class:/:meth:/:rtype: cross-references to the real public Protocol class untouched, matching #750's own precedent * emptied tests/test_base_class_contract.py's PENDING_ALIAS_PATHS, its documented end state, and updated the stale docstring narrative No behaviour change: __mro__/__bases__/__module__ identical across 18 classes spanning every family, __proto__ registry 38 keys before and after, descendants(Protocol) 0 in both. mypy stays at the 112-error baseline.
…ote (#763) - `Extractor.register_engine`'s `# NOTE:` explained its `issubclass(engine, EngineBase)` gate by citing `engines/pcap.py` importing `EngineBase as Engine`. #750 removed that alias: the file now imports `EngineBase` under its own name and declares `class PCAP(EngineBase[Frame])`, so the parenthetical pointed a reader at a shape that is not there. - Cites the class statement rather than a line number, a line citation being the same staleness class. The conclusion is unchanged and still correct: the built-ins subclass the base directly to decline `Engine.__init_subclass__`'s auto-registration, which is why the gate is wide. `See #513.` kept. Comment-only. Verified no `as Engine` survives anywhere under `pcapkit/`, and that the file's AST is identical to `origin/main`'s (434 statements both sides), so coverage is unchanged. tests/foundation 259 passed / 11 skipped / 404 subtests passed, plus tests/test_base_class_contract.py. Fixes #756.
make pylint,make mypy,make isort)make testpasses, and a test case covers the change — targeted runs only, counts belowWhat is the purpose of your pull request?
fix— corrects a defectfeat— adds a featureperf— changes performance, not behaviourrefactor— changes neither behaviour nor performancetest— tests onlydocs— documentation onlyci— workflows or build toolingchore— anything elseDescription of your pull request and other information
Part of #514. Library modules imported a
*Baseclass under its public name —from … import EngineBase as Engine, thenclass PCAP(Engine)— so the source read as inheriting the public class while actually inheriting the base. Per the ruling the split is permanent and library classes inherit the base, so the base is now imported under its own name.Partial, and the boundary is file ownership rather than judgement:
EngineBase as EngineReassemblyBase as ReassemblyDumperBase as DumperTraceFlowBase as TraceFlowProtocolBase as ProtocolAll four non-
protocolsfamilies are complete. Every one of the 54 outstanding is aProtocolBasesite in a path an open PR owns —pcapkit/protocols/andfoundation/registry/protocols.py(#726),foundation/extraction.pyandfoundation/traceflow/traceflow.py(#742) — held as four path prefixes inPENDING_ALIAS_PATHS, which fails once a prefix stops matching, so the promise cancels itself as those land.The issue's 82 is right; I measured 83 first and was wrong —
EngineBase as Engineis 8, not 9. Split: 53TYPE_CHECKING, 24 module-scope, 5 function-local.FieldBase/Fieldexcluded — not registration-motivated, and unlike the five it has 26 descendants, so its alias is abbreviation, not misstatement.No behaviour change, measured not asserted: name registry 38 keys before and after;
descendants(Public)0 in all five suites; 45 real dispatches identical, and still identical after__proto__.clear()— confirming the built-inModuleDescriptortables are populated independently of name registration, as you said; two captures parse byte-identically;mypyat exactly its 112-error baseline andpylintat exactly its message profile, 0 introduced either way.One thing #514 asks for that I did not build, and want a ruling on. The ruling also says
__init_subclass__should check againstProtocolspecifically. Measured, that check is a tautology in all five suites: the registering hook is defined on the public class, so it only runs when the public class is in the new class's MRO —issubclass(cls, Public)is true every time it runs, and it never runs otherwise. Library classes are already excluded by inheritance, which is stronger than a runtime test. And the two hooks that are reachable from library classes must not get the guard:ProtocolBase.__init_subclass__assigns__schema__/__data__for all 43 built-ins, and theReassemblyBase/TraceFlowBasehooks initialise__callback_fn__. So the discrimination is pinned as tests rather than added as dead code — say the word if you want it spelled out anyway.#682 looks dissolved, not merely deferred: all three dispatchable
HTTPclasses areProtocolBasesubclasses, so under a public-only rule none would register, andimport pcapkitemits zeroRegistryWarning(the only warning is third-partyVueJS is deprecated).HTTPis claimed once, by the__all__walk. What survives is narrower — the key is a shared namespace, so a user class namedHTTPsubclassingProtocolwould still displace it.Not
breaking. All five public classes and every registration path are untouched, so an out-of-tree subclass registers exactly as before. The only observable loss is 14 modules no longer binding the public name incidentally, and none of the 24 such modules declared it in__all__— never public API, and it fails as a loudImportErrorrather than quietly meaning something else.Deliberately excluded: 10
RegistryErrormessages say "must be aProtocolsubclass" while checking the base; 8 sit in #726/#742 files, and fixing 2 of 10 splits the wording two ways for one check. Worth one pass once those land. (The issue says eleven; two of the twelveregister_dumpersites correctly mean third-partydictdumper.dumper.Dumper.)On splitting: one PR is right, because this turned out not to be the risky change. The rename produces identical class objects —
class Link(Protocol)whereProtocolisProtocolBasecompiles to exactly whatclass Link(ProtocolBase)does — so the #514 invariant holds at every intermediate step and there is no half-migrated state to protect against. That hazard belongs to the original collapse, not to this. Which leaves reviewability, arguing against splitting: it is one transformation rule, checked once. Per-suite PRs would be four reviews of 2–8 lines each.Also corrected four
docs/source/**/index.rstpages that said "All X are implemented as<Public>subclasses" while their own Mermaid diagram three lines below correctly roots the hierarchy at*Base;corekit/fields/index.rstalready named the base and was the model.Tests.
tests/test_base_class_contract.pyis new — 7 tests / 21 subtests, at thetests/root because the contract spans four suites so no suite directory owns it. Its two alias tests fail on unmodifiedmain(28 source sites in 21 files; 14 runtime bindings) and pass here; the fiveRegistrationGateTestsare regression pins for what parts (a)/(b) already gave, including that a library-style subclass cannot opt in at all —class X(EngineBase, engine=…)is aTypeError, thefinalsubstitute you described. Rantests/{corekit,dumpkit,foundation,interface,utilities},tests/protocols/internet/test_ipv4_unit.pyand the new file: 625 passed, 11 skipped, 2546 subtests, 0 failed (fixtures built withexamples/generators/make_samples.py; without them 34 fixture-dependent tests fail on any tree, and all 43 pass once built). The fivetest_ipv4_unit.pyassertions pinningProtocol is not ProtocolBaseall still pass. Coverage on the same selection rises 45% → 48% (1639 more statements, identical 40383 denominator — the rename adds no library statements).Body corrected after cross-review: 21 files not 22, and 24 module-scope alias sites not 20 (the reviewer's count includes
DumperBase as Dumperindumpkit/null.py:18anddumpkit/pcap.py:16, both plain module scope, which my four-family sweep had excluded). Conclusions unchanged — 0 of all 82 sites put the public name in__all__.