Repository navigation
fix(corekit,utilities): raise pcapkit exceptions from EnumLookup.get, following stdlib Enum's shape #923
Description
The maintainer's ruling on #921, verbatim:
Either
ValueErrororKeyError, that's depending on how stdlib'sEnumwould raise on these circumstances. And we should raise one frompcapkit.utilities.exceptionsrather builtin exceptions.
What stdlib does, measured on Python 3.14.7:
| call | stdlib Enum |
|---|---|
E['nosuch'] — name miss |
KeyError: 'nosuch' |
E(999) — value miss |
ValueError: 999 is not a valid E |
E(None) |
ValueError: None is not a valid E |
So the shape the ruling selects is: a name miss is KeyError-derived, a value miss is ValueError-derived — and both must come from pcapkit.utilities.exceptions, not from builtins.
Where the library stands against that. EnumLookup.get (pcapkit/corekit/enum.py:395) surfaces a bare builtin KeyError on a name miss and a bare builtin ValueError on a value miss, so it has the right shape and the wrong provenance. Two classes work around it by converting: TransportProtocol.get and Criticality.get both catch the base's KeyError and re-raise ValueError naming the key, which under this ruling is the wrong direction — a name miss should stay KeyError-shaped.
A gap that has to be filled first. pcapkit/utilities/exceptions.py already has the value half: EnumValueError(BaseError, ValueError) at line 374. It has no KeyError-derived class at all — every member of that module derives from TypeError, AttributeError, ValueError, IOError, IndexError, FileExistsError or FileNotFoundError. So the name-miss half needs a new exception, something like EnumKeyError(BaseError, KeyError), before get can comply.
Scope
- Add the missing
KeyError-derived exception topcapkit/utilities/exceptions.pyand its__all__. - Make
EnumLookup.getraise the twopcapkit.utilities.exceptionsclasses instead of builtins, keeping the stdlib shape — name missKeyError-derived, value missValueError-derived. Both stay catchable as the builtins they derive from, so existingexcept KeyError/except ValueErrorcall sites keep working. - Remove the now-wrong conversion from
TransportProtocol.getandCriticality.get.Criticality.getdisappears entirely at that point;TransportProtocol.getreduces to the.lower()call and nothing else. - Update the docstrings and
docs/source/contributing/conventions.rst's registry-protocol section, which documents the current exception contract.
Blast radius is real and not yet fully measured. This changes the contract for every concrete EnumLookup subclass. A census is running to establish how many already surface ValueError versus KeyError on a name miss and what in pcapkit/ and tests/ catches KeyError around a .get( call; I will post the counts here when it lands.
Blocked on #921 and #922 merging. Both touch pcapkit/corekit/enum.py or its subclasses and both are review: good-to-go; starting here first would collide with them.
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Activity
JarryShaw commentedon Sep 29, 2026
Census landed, and it settles the direction: the base already has the right shape. Only its provenance is wrong.
Re-derived by me independently of the agent that ran it, on
3766c3c09. Both counts agree exactly.127 concrete
EnumLookupsubclasses (vendor codegen templates and the two memberless bases excluded — the figure matchespcapkit/corekit/enum.py's own supporting comment). On a string name miss:KeyErrorValueErrorThe 5 are
AppTypeand its four transport subclassesTCP/UDP/SCTP/DCCPinpcapkit/const/reg/apptype/, and they land there only because their ownget()takes anintport and never accepts a string at all — not from a name-miss policy. The 3 that return areCommand,MethodandOptionType, all of which mint.So
ValueError-on-name-miss is a 5-of-127 minority.EnumLookup.getmust not change its exception shape — it already matches stdlib, and converting would overturn 119 classes to match 5. What changes is only that the exceptions come frompcapkit.utilities.exceptions. That narrows this issue's scope, it does not widen it.The one design point that decides the cost. If
EnumKeyErrorderives fromKeyError— asMissingKeyErroralready does atpcapkit/utilities/exceptions.py:459— every existingexcept KeyErrorkeeps catching and the blast radius collapses to nearly nothing. Measured, the sites that would otherwise break:pcapkit/toolkit/scapy.py:342,hopopt.py:806and:1040,ipv6_opts.py:809and:1043, andconst/http/method.py:297. That last one matters most —Method.getcatches the base'sKeyErrorin order to mint, so it is wired directly to the current contract. There are also 28assertRaises(KeyError)assertions across 8 test files, about 20 of them against.get(on a real registry, includingtests/corekit/test_enum_lookup_base_unit.pywhich pins the base's contract directly andtests/const/test_const_registry_protocol.py:1258-1261which states it in prose.One more item for the scope, found by the census.
pcapkit/protocols/internet/mh.py:648and:740already doexcept KeyError: raise EnumValueError(...)on a name miss — the right provenance but the minority shape, and the same conversion this ruling rejects. Those two should becomeKeyError-derived along with the base.A defect worth its own note:
Command.get(pcapkit/const/ftp/command.py:485) unconditionally callskey.upper(), so a non-strkey raisesAttributeErrorrather than any enum exception. On the value-miss path that is simply wrong, and it is in neither shape.Scope caveat: the census measured
origin/main, whereTransportProtocolandCriticalityare not yetEnumLookupsubclasses — #921 is what reparents them. Their two overrides'KeyError→ValueErrorconversion is confirmed as the minority behaviour by these numbers, so deleting it stays in scope here.JarryShaw commentedon Sep 29, 2026
Recording the precise end state of the two overrides, measured on #921's branch, so it is not rediscovered here.
TransportProtocol.getsurvives this issue, reduced to the.lower()call and nothing else. Case folding is not in the base and is live public behaviour —get('TCP')resolves today and raisesKeyErrorwithout the override.Criticality.getcan be deleted outright. Once theKeyError→ValueErrorconversion goes, its body is a pure pass-through. I checked the one thing that could make deletion unsafe —defaulthandling — and the inherited base is identical to the override's forwarding:The only loss is the narrower
int | str | Criticalitykey annotation, a typing nicety rather than behaviour. Deleting it is therefore safe on a closed ASN.1ENUMERATED: a member-valueddefaultresolves through_value2member_map_and never through the constructor, so it still cannot mint a fourth value.JarryShaw commentedon Sep 29, 2026
Half-unblocked: #921 merged as
5a4f8ca59. Still blocked on #922.This issue was gated on both open PRs that touch
pcapkit/corekit/enum.pyor its subclasses. #921 has landed, so theTransportProtocolandCriticalityre-parenting this work builds on is now onmain— verified, all four spot-checked classes haveEnumLookupin their MRO on5a4f8ca59.#922 remains the blocker. It touches
pcapkit/corekit/enum.pydirectly (theNO_DEFAULT/NoDefaultTypedefinitions move out topcapkit/corekit/sentinels.py), which is the same file this issue changes. It isreview: good-to-goat16f3a05fband held only by a reclaimed CI runner needing a re-run.Worth noting for whoever picks this up: once #922 lands,
pcapkit/corekit/sentinels.pyis whereNO_DEFAULTlives, so the newEnumKeyErrorwork here should import it from there rather than fromenum.py— anddocs/source/pcapkit/corekit/enum.rst, added by #919, will need itsautoclass/autodatatargets repointed forNoDefaultTypeandNO_DEFAULT. That doc fix belongs to neither PR and is currently unowned.JarryShaw commentedon Sep 29, 2026
Unblocked — both blockers merged. #921 landed as
5a4f8ca59and #922 asc7342cac1.Two things this issue must now account for, both consequences of #922 rather than new work:
NO_DEFAULTandNoDefaultTypenow live inpcapkit/corekit/sentinels.py, re-exported frompcapkit/corekit/enum.py. The newEnumKeyErrorwork should import from the canonical module, not the shim.docs/source/pcapkit/corekit/enum.rst— added by docs: add pcapkit.__version__ entry and the missing corekit.enum page #919 — still points itsautoclass/autodataatpcapkit.corekit.enumfor those two names. That page needs repointing, it belongs to neither merged PR, and it is currently unowned. Doing it here is the natural home since this issue already changes that module's contract.The census recorded above still stands: 119 of 127 concrete
EnumLookupsubclasses raiseKeyErroron a name miss, so the base keeps its stdlib shape and only its provenance changes. Removingblocked.docs(conventions): harvest the settled rulings, correct the stale pro…
refactor(const,protocols): reparent the last seven non-registry enums…
refactor(const,protocols): reparent the last seven non-registry enums…
refactor(const,protocols): reparent the last seven non-registry enums…
refactor(const,protocols): reparent the last seven non-registry enums…
docs(conventions,const): resolve part B's unresolved cross-references (…
docs(pcapkit,ci): cite the issue a defect belongs to, not the pull re…
docs(pcapkit,ci): cite the issue a defect belongs to, not the pull re…
docs(pcapkit,ci): cite the issue a defect belongs to, not the pull re…
docs(corekit): state the enum.py rulings as statements, not quotations
docs(protocols): state the rulings behind mh, vendor and exceptions i…
docs(corekit): state the enum.py rulings as statements, not quotations
docs(corekit): state the enum.py rulings as statements, not quotations
docs(const,vendor): state the generated files' rulings instead of quo…