Conversation
26d050d to
0ec482f
Compare
|
GOOD TO GO — with two items it raised, both now addressed. Relaying an independent cross-review run on a different model (Claude Sonnet) from the one that authored the change, briefed to falsify rather than confirm, read-only. Verdicts are its own; the two follow-ups and one correction below are mine. Claim-by-claim
It specifically attacked claim 3 by chasing On claim 5 it went further than the PR did, checking a generator What it disputed, and what I changedA genuinely broken Sphinx role — its best catch, and a real defect. The new Fixed in the amended commit. Both of this PR's backslash-continued roles are now the One correction to its framing. It reported the identical defect as pre-existing in the same file's pylint, partially disputed — and it is right that the figure was under-specified. It ran a full What it could not verify
It left the working tree exactly as found — |
… code-keyed registrars displaced (#692) Generalises #681, per the ask to "apply what #681 added to other registry as well". - `EnumSchema.register` assigned bare, so the schema half of 14 public registrars silently displaced a built-in while the parser half of the very same call warned. Guard it on presence, naming both schemas. - `EnumSchema.__init_subclass__` reaches the same registry without calling `register`, so `class MyOption(Option, code=...)` stayed silent too. Guard it as well, folding its two branches into one loop so the guard is written once. - The seven code-keyed registrars now name the displaced entry and its replacement. Their presence-only condition is deliberately unchanged: their key is caller-supplied and independent of the value, so #681's "present and a different class" has nothing to fix here, and the `ModuleDescriptor` incumbents these tables ship with would make it undecidable without resolving the descriptor -- forcing the import it exists to defer, just to decide whether to warn. - `ContextRegistry.register` already raises on a duplicate, and the reassembly and ESP registrars are unkeyed lists, so none of those three takes a guard. `import pcapkit` holds at 1 warning and 0 RegistryWarning; mypy 112 errors and pylint 364 messages both unchanged; schema.py coverage 99% with its 5 new statements covered and misses flat at 1. Fixes #692
0ec482f to
634486a
Compare
Fixes #692. Generalises #681, per the ask:
Re-verified inventory
Measured with an AST walk over
pcapkit/, not a grep — a grep range terminated early on the first attempt and gave a false reading. 12register()implementations. Classified by what each does on a key collision, which is a more useful split than warn/silent:protocols/protocol.py:758,internet/internet.py:136,link/link.py:116,misc/pcap/frame.py:123,misc/pcapng.py:857,transport/transport.py:72,transport/sctp.py:593schema/schema.py:1115,schema/misc/pcapng.py:588RegistryErrorcorekit/context.py:125foundation/reassembly/reassembly.py:397,internet/esp.py:916Zero used #681's refined guard. That reconciles with the five-silent framing this work started from, but three of those five turn out not to need a guard, each for its own sufficient reason:
ContextRegistry.registeralready raises —raise RegistryError(f'context already registered for protocol: {index}'). Stricter than a warning; relaxing it would be a regression.Reassembly.registerappends a callback to__callback_fn__, a list. No key, no overwrite.ESPContext.registerappends to__associations__, also a list — and duplicate SPIs are a designed feature: the class documents its associations as supplied "in order of preference for otherwise equal matches", andESPContext.matchscores them. A guard would flag a supported path.schema/misc/pcapng.py:588is the fifth. It is astaticmethodoverride with a different signature that never delegates toEnumSchema.register, so this PR cannot reach it — it is #678's, handled separately.What changed
EnumSchema.register(schema/schema.py) — was a barecls.__enum__[code] = schema. It is the schema half of 14 public registrars, and the defect is an asymmetry within a single call: eachregister_*helper registers a parser class and a schema class, the parser half has warned on an overwrite for as long as it has existed (internet/ipv4.py:482,hopopt.py:367,ipv6_opts.py:378,hip.py:651,mh.py:1483/1496/1509,ipv6_route.py:355,application/httpv2.py:367,transport/sctp.py:618/632/645/658), and the schema half assigned bare. So oneregister_ipv4_optionreplacing a built-in named the parser it displaced and said nothing about the schema.EnumSchema.__init_subclass__(same file) — assignscls.__enum__[code]directly, soclass MyOption(Option, code=...), the documented way to add a schema, bypassedregisterentirely. Guarding only the method would have left that path silent, recreating the very asymmetry being fixed. Its two branches are folded into one loop so the guard is written once. It deliberately does not delegate tocls.register:schema/misc/pcapng.py'sOptionoverridesregisterwith an incompatible signature, socls.registerdoes not mean the same thing for every subclass.Presence is a faithful test here only because of #555 —
_EnumRegistry.__missing__returns a miss without recording it. On a plaindefaultdict, parsing one packet with an unknown code would have made the next legitimate registration for that code warn about an entry nobody asked for._EnumRegistry's own docstring already anticipated this guard in those words. A test pins the composition of the two fixes.The seven code-keyed registrars — message only. They now name the displaced entry and its replacement; they said
'protocol {code} already registered, overwriting'and stopped, so a caller learned something was displaced and never which class.The refinement does not transfer — decided per registrar, and all seven stay presence-only
protocol.py:758ProtocolBase.registerintlink.py:116EtherTypeinternet.py:136TransTypeframe.py:123LinkTypemisc/pcapng.py:857LinkTypetransport.py:72sctp.py:593Three independent reasons, any one sufficient:
cls.__name__.upper()) and it is the single funnel nine registrars end in, so one class under two codes reaches it twice with nothing displaced. All seven take a caller-suppliedcodeindependent of the value, so one class under two codes yields two distinct keys — the spurious-warning case cannot arise. A repeat for one code is a caller mistake worth reporting even when the value is unchanged.__proto__is pre-seeded with unresolvedModuleDescriptorvalues (link.py:76,internet.py:91,frame.py:90,misc/pcapng.py:557,sctp.py:355,tcp.py:314,udp.py:88), so an incumbent may be a two-string descriptor while the replacement is the very class it names. "A different class" would needincumbent.klass— an eager import on the register path, defeating the laziness the descriptor exists for, purely to decide whether to warn.tests/foundation/registry/test_protocols.py:230asserts the siblings still warn on an identical re-registration and says it "fails if anyone ever 'harmonises' the siblings onto the guard used above". Harmonising would break it by construction.So the transferable part of #681 is the message, not the condition. The firing condition of all seven is byte-for-byte unchanged.
Evidence
Exit codes read from files, never from a pipeline — and note one run reported
9 passedon its header line while 8 subtestsSUBFAILED, so the tally was read too.pcapkit.__file__on every measurement:/local/home/jarryx/GitHub/PyPCAPKit/.claude/worktrees/agent-a37137696e200684c/pcapkit/__init__.py, asserted before any other import, with the worktree atsys.path[0]andPYTHONSAFEPATH=1. This matters more than usual here because registry state is process-global, so a shadowed tree makes every observation wrong. One intermediate measurement did resolve to the shadowing install and was discarded.import pcapkitemits zero new warnings. Before and after are byte-identical: 1 warning total, a third-partyDeprecationWarning(dictdumper/vuejs.py:89, "VueJS is deprecated"), and 0RegistryWarning.That was verified twice, because the first attempt was wrong in a way worth recording: a script that imported
pcapkit.protocols.schema.schemain order to patch_EnumRegistryhad already runpcapkit/__init__.py, so every write happened before the patch. It reported0 writes, 0 warnings— and the known 1-warning baseline is what exposed it. Re-done with a meta-path finder that instruments the module in the window after its body executes: 326_EnumRegistrywrites during import, 0 onto an already-present key. That is also what establishes the__init_subclass__guard is safe, since a first-time declaration is the case that governs whether import is noisy.Failing-then-passing, per guard. Each was reverted independently:
EnumSchema.registerguard1__init_subclass__guard1SUBFAILED, one per registrar10Suites.
tests/foundation/registry/13 → 14 tests, 87 → 95 subtests, exit0.tests/protocols/schema/+foundation/registry/49 passed / 100 subtests, exit0. The broader affected areas —protocols/link/,protocols/misc/,protocols/transport/,protocols/schema/,foundation/registry/— 307 passed / 485 subtests, exit0; and the top-level dispatch, registry and round-trip tests 72 passed / 456 subtests, exit0.An earlier run of those areas showed 25 failures, all
FileNotFoundError: sample capture '<name>' not found. Environmental: the captures are generated byexamples/generators/make_samples.py, and CI's ownmake testexcludes*_runtime.pyfor exactly this reason. Generated them, re-ran, all pass.EXPECTED_FAILURESimported rather than grepped (it is built with**unpacking): 44 entries before and after. No entry moved.Static analysis, both at parity. mypy
112errors before and after, over the whole package (the two new[index]errors the first draft introduced on its new subscript reads are gone — the reads were hoisted onto their own lines with the# type: ignore[index]this file already uses on its assignments).pylint 364 messages before and after, exit
30both — scoped to the eight touched modules, with the repo's ownPYLINT_FLAGS, against a pristinegit archiveofHEAD. Stating the scope because the number is meaningless without it: a fullmake pylintover the package is in the 5,900s, and is not a usable before/after metric, becauseR0401(cyclic-import) varies run to run on an unchanged tree — measured at 106 and then 119 on two consecutive runs of the same commit. The eight-module figure is deterministic; it reproduced at 364 on four separate runs.One honest pylint note: within that scope the composition shifted by one in each direction —
C0325down one (folding__init_subclass__'s duplicated(cls)assignment removed a superfluous-parens) andR0801up one, a new duplicate-code cluster spanningInternet.registerandProtocolBase.register. Those two methods were already near-identical; giving them the same guard shape tipped the similar region past the threshold. Varying the seven docstrings to name each registry's own key kind was tried and did not remove it, which confirms the similarity is the method bodies, not the prose. Deduplicating them is#514's territory, not this PR's.Coverage does not go backwards. Each tree measured with its own tests, via
coverage run -m pytest:schema/schema.pymisc/pcapng.pytransport/sctp.pyprotocol.pyinternet.py/link.py/frame.py/transport.pyschema.pygains 5 statements — the guards — with misses flat at 1, so every new statement executes. Its one miss is the pre-existingraise KeyError(key)in_EnumRegistry.__missing__. For the seven, the changed line already executed before, so coverage there is flat by construction and the subtest counts above are the measure.pcapkit.pyandsctp.pyimproved because the new sibling test reaches theirregisterfor the first time; the accompanying rise in partial branches is the expected signature of newly-reached code, not a regression.Labels, and the
breakingquestionLabelled
fix+test, matching #681.The case for
breaking:EnumSchema.registerand the declaration path were silent and now warn. A downstream consumer running under-W errorthat replaces a built-in schema — a supported thing to do — now gets an exception where it previously got silence. That is a behaviour change on a supported path, and it is real.The case against, which is why it is not applied: this repo defines the label as "Alters public API or wire output", and nothing here does. No signature changes, no wire output changes, no registry contents change. The seven keep their firing condition byte-for-byte; only their message text differs, which no test pins (the four references to the old text in the suite are prose in docstrings and comments, and the string
already registered, overwritingis still a substring of the new message, so they stay accurate). #681 faced the identical question for the identical reason and was labelledfix+test. Applyingbreakinghere and not there would be the inconsistency.Worth noting the
-W errorexposure is smaller than it looks: the warning fires only where something was genuinely displaced, andpcapkit.utilities.warnings.warnlogs on thepcapkitlogger before callingwarnings.warn, so a consumer that filters the category still sees the complaint.How this constrains #514
#514's staging names the
register_protocolcollision as its c1 prerequisite, landed by #681. This is not c1 and does not extend it: no key-space change, so #682's finding that every__proto__reader degrades silently on a miss is untouched, and re-keying stays entirely with #514/#682.What it does constrain is c2/c3. It settles with tests that the two guard shapes are deliberately different, and why — so a later re-keying cannot quietly harmonise them on the way past, and does not have to rediscover the
ModuleDescriptorargument. The one place it adds work: if c3's deferred-registration drain ever routes schema registration throughEnumSchema.registermore than once for the same code, that would now warn, so the drain needs to be idempotent or to register once. Worth knowing before the drain is written rather than after.Fixed after cross-review
A cross-review on a different model found a real docs defect, since fixed in the amended commit: the new
Note:inEnumSchema.registerwrapped a long:func:role across two lines with a trailing backslash. That idiom is used widely in this package, but it is only safe inside a raw docstring, where the backslash survives for docutils to treat as its own line-join escape.EnumSchema.register's docstring is a plain""", so Python's compiler consumed the backslash at compile time and left the next line's indentation as literal spaces inside the role target — measured as'~pcapkit.foundation.registry.+ 12 spaces +protocols.register_protocol', which cannot resolve.Both of this PR's backslash-continued roles are now the
text <target>form instead, which is safe regardless of raw-ness because the resolver only reads what is inside the angle brackets. Audited all nine touched docstrings: 0 broken role targets, 48 roles total.One correction to that review's framing, checked rather than taken on trust: it reported the same defect as pre-existing at
schema/schema.py's_EnumRegistrydocstring. It is not the same defect. That role is thetext <target>form, so its backslash join lands in the link text and the target inside the angle brackets is intact on one line. The effect there is one cosmetic space in a displayed name, not a broken reference, so it is left alone.I am not claiming CI green.