Summary
NestedPacketContext (pcapkit/corekit/fields/misc.py:493, added by #457)
overrides __getitem__ via __missing__, plus __contains__ and get, so a
name the nested schema does not declare falls through to the enclosing schema.
Three other dict methods were not overridden and do not honour that
fallback, so they disagree with [], in and .get() on the same key.
All three are latent — nothing under pcapkit/ calls setdefault, pop or
== on a packet context today (grep-confirmed). Filing so the inconsistency is
recorded rather than rediscovered by whoever first reaches for one of them.
Measured
On #457's head aab958b1d, with a context whose enclosing packet is
{'length': 42, 'other': 7} — each on a fresh context, since these
operations mutate:
ctx['length'] -> 42 (fallback honoured)
'length' in ctx -> True (fallback honoured)
ctx.get('length') -> 42 (fallback honoured)
ctx.setdefault('length', 999) -> 999 <- bypasses the fallback and shadows the parent key
ctx.pop('length') -> KeyError <- while ctx['length'] succeeds
ctx == dict(ctx) -> False <- does not equal its own union view
setdefault and pop are dict's C-level implementations, which bypass
__missing__ entirely — the same reason __contains__ and get had to be
overridden in the first place. For __eq__ the asymmetry is visible in one line:
len(ctx) is 3 and ctx.keys() yields ['__packet__', 'length', 'other']
because __len__/__iter__ present the union, while dict.keys(ctx) — the real
local storage — holds only ['__packet__']. So equality compares a different set
of keys from the one iteration reports.
For completeness, **-unpacking is fine: dict(**ctx) returns all three
keys, because overriding __iter__ forces CPython off its dict-merge fast path.
That was checked rather than assumed.
Suggested direction
Either override the three to honour the fallback, or make the class explicitly
refuse them — setdefault and pop raising UnsupportedCall would be
defensible, since a nested context arguably has no business supporting either,
and an explicit refusal is better than a silently divergent answer. Whichever is
chosen, __eq__ and __len__/__iter__ should agree on which key set they
describe.
Worth checking at the same time whether any other dict method presents the same
split: keys, values, items, __len__ and __iter__ are overridden or
inherited consistently today, but update, copy (already overridden — see
pcapkit/protocols/schema/schema.py:751, which needs it) and popitem are worth
a look.
Also, a stale docstring
tests/corekit/test_fields_misc_packet_context.py:24 still describes the
mechanism as collections.ChainMap. That was true of an earlier revision of #457
and stopped being true when it pivoted to a dict subclass to survive Python
3.10. Cosmetic, but misleading to the next reader.
Provenance
Surfaced by the review of #457 as explicitly non-blocking, and verified
independently here — my first measurement of pop was contaminated by a
preceding setdefault on the same context and had to be redone on a fresh one.
Not folded into #457, which was already at a GOOD TO MERGE verdict.
Summary
NestedPacketContext(pcapkit/corekit/fields/misc.py:493, added by #457)overrides
__getitem__via__missing__, plus__contains__andget, so aname the nested schema does not declare falls through to the enclosing schema.
Three other
dictmethods were not overridden and do not honour thatfallback, so they disagree with
[],inand.get()on the same key.All three are latent — nothing under
pcapkit/callssetdefault,popor==on a packet context today (grep-confirmed). Filing so the inconsistency isrecorded rather than rediscovered by whoever first reaches for one of them.
Measured
On #457's head
aab958b1d, with a context whose enclosing packet is{'length': 42, 'other': 7}— each on a fresh context, since theseoperations mutate:
setdefaultandpoparedict's C-level implementations, which bypass__missing__entirely — the same reason__contains__andgethad to beoverridden in the first place. For
__eq__the asymmetry is visible in one line:len(ctx)is 3 andctx.keys()yields['__packet__', 'length', 'other']because
__len__/__iter__present the union, whiledict.keys(ctx)— the reallocal storage — holds only
['__packet__']. So equality compares a different setof keys from the one iteration reports.
For completeness,
**-unpacking is fine:dict(**ctx)returns all threekeys, because overriding
__iter__forces CPython off its dict-merge fast path.That was checked rather than assumed.
Suggested direction
Either override the three to honour the fallback, or make the class explicitly
refuse them —
setdefaultandpopraisingUnsupportedCallwould bedefensible, since a nested context arguably has no business supporting either,
and an explicit refusal is better than a silently divergent answer. Whichever is
chosen,
__eq__and__len__/__iter__should agree on which key set theydescribe.
Worth checking at the same time whether any other
dictmethod presents the samesplit:
keys,values,items,__len__and__iter__are overridden orinherited consistently today, but
update,copy(already overridden — seepcapkit/protocols/schema/schema.py:751, which needs it) andpopitemare wortha look.
Also, a stale docstring
tests/corekit/test_fields_misc_packet_context.py:24still describes themechanism as
collections.ChainMap. That was true of an earlier revision of #457and stopped being true when it pivoted to a
dictsubclass to survive Python3.10. Cosmetic, but misleading to the next reader.
Provenance
Surfaced by the review of #457 as explicitly non-blocking, and verified
independently here — my first measurement of
popwas contaminated by apreceding
setdefaulton the same context and had to be redone on a fresh one.Not folded into #457, which was already at a
GOOD TO MERGEverdict.