feat(corekit): add EnumRegistry base for const registries, convert 5 (#775) - #855
Conversation
|
NEEDS CHANGES on 1. The stated blocker for
2. No exception. That contradicts the method's own contract — "register mints new enum … so we don't have to Everything else confirmed, some of it more tightly than the PR claimed:
One caveat on your open question about exception types: the cited precedent |
…#775) Tier 2 of #775, the abstraction half. Per the ruling on #842 -- "get/get_all/register/register_alias should always exist on the const enums - so they're to be moved to the base class" -- the four methods now live once, on `pcapkit.corekit.enums.EnumRegistry`, instead of being written out as generated text in `pcapkit/vendor/default.py`'s template and hand-copied into each of the eleven crawlers that override it. - Add `EnumRegistry`, a plain mix-in carrying `get`, `get_all`, `register`, `register_alias`, `register_aliases` and `_unregistered_member`, with each method's contract as ruled. - `register` now refuses a value that already has a member instead of silently aliasing it via `aenum.extend_enum` under the caller's name -- the inverse of the check `register_alias` already ran on `value`. Both now route through a new shared `_extend`, so `register_alias` keeps the mint-or-alias behaviour it depends on. - Convert six bespoke templates onto the base -- the four `mh/*_flag`, `ipv6/extension_header`, and `tcp/flags` -- and regenerate. `tcp/flags` was scoped out on the claim that it "attaches extra attributes in `__new__`"; measured, it defines none, the same shape as the four `mh/*_flag` templates, so it joins them instead of the five that do. - `get`'s string miss now honours `default` on those six, where their own copy ended in a bare `return NAME[key]`. - Update the template-render regexes in `test_const_enum_lookup.py` and `test_const_enum_builtin_parity.py` for the new class statement. No behaviour change for the other 115 registries, nor for `tcp/flags`'s own range-checking `_missing_`, which still ends in `super()._missing_(value)` unchanged. `tests/const` and `tests/vendor` pass.
02296b5 to
cb7eb0d
Compare
|
NEEDS CHANGES on 1. The call-site claim is wrong, and it hides a repo-wide inconsistency this PR introduces. The body says the So after this PR One correction to the review: it put the residue at 115. I counted 105 — files under 2. An undisclosed observable change in The first is a widening and a fix — What the review confirmed, including by refuting my own premise. I briefed it that Also confirmed: the guard raises
One note for tier 2, no action now: the base |
|
GOOD TO GO on Verified by me on the current body:
What the two rounds cost, for the record. Round 1 caught a factually wrong exclusion rationale —
Two cross-review rounds on opus against a sonnet author. |
|
I'm wondering if we should change the NO_DEFAULT to a sentinel object. Not necessarily this PR. Lmk if you wanna do it here or postpone and I merge as is. |
Postpone — merge as is. The recommendation is measured, not a preference: So there is no correctness bug to fix now — And doing it here would make things worse in the one way this PR already got called out for. Round 2's finding was One wart worth knowing while it stands, since it is the argument for the sentinel and I would rather state it than When it happens, |
|
Okay let's save the sentinel change to an issue (existing or new). |
|
Recorded, verbatim: "Okay let's save the sentinel change to an issue (existing or new)." Filed as #857 — A dedicated issue rather than only the #775 comment, because #775 is an umbrella that stays open across several more Nothing further on this PR — #855 is |
…numRegistry (#775) Tier 3 of #775/#842: mixes pcapkit.corekit.enum.EnumRegistry into the generated const-enum template and every registry it produces, so get, get_all, register and register_alias exist on all 121 const registries rather than 7. Closes the register() two-contract residue #855 disclosed: an already-registered value now raises instead of silently aliasing. - pcapkit/vendor/default.py: LINE now emits class {NAME}(EnumRegistry, IntEnum) and drops the hand-written get, register and _unregistered_member the base now provides; the aenum import line only keeps extend_enum when a registry's own _missing_ still calls it directly. _missing_ itself is untouched. - 105 generated pcapkit/const/*.py files edited to match, mechanically (script-derived from the template's own literal text). Verified byte-identical against a live regeneration of 5 files spanning both import shapes (arp/hardware, sctp/cause_code, hip/parameter, pcapng/record_type, reg/ethertype). - Excluded: the 6 bespoke registries #855 already converted, and 10 files with bespoke, mint-on-lookup get()/_missing_ contracts that don't share this template (ftp/command, ftp/return_code, http/method, http/status_code, pcapng/option_type, reg/apptype/* - AppType is tier 2 of #842, not tier 3). All are StrEnum-based; measured that the base get()'s str-key path never tries the value path, so converting them would silently regress value lookups. - Measured the one real dispatch difference: a key that is neither int nor str now raises ValueError (treated as a value) instead of the old KeyError (treated as a name). Int and str keys are unaffected. - Per owner ruling on #858: renamed pcapkit/corekit/enums.py to pcapkit/corekit/enum.py (git mv, no content change beyond the two self-referencing docstring lines naming the module's own path) and updated all 124 importers, corekit/__init__.py's re-export, and the four test modules that name the path in prose or assertions. No docs/ reference existed to update. - tests/const/test_const_registry_protocol.py: new coverage for the 105-file batch - base declared/inherited, full-corpus sweep, the register() regression pin (fails on prior head, passes here), _missing_ range parity, the get() dispatch matrix, and the StrEnum exclusion's own measurement. - tests/const/test_const_enum_get.py: its vendor-template regression test asserted on the now-removed get() block; rewritten to assert the mix-in and the absence of a local copy instead. Build: mypy/isort/pylint clean on all touched files (pylint duplicate-code findings dropped 108->4 on this batch). tests/const, tests/vendor and tests/test_tier_guard.py pass in full (352 tests, plain unittest).
|
#855 gave
No code implication, and nothing to re-review — this is a label correction on merged work. |
EnumRegistry.register() now raises ValueError naming the existing member and pointing at register_alias(), where it used to silently alias an already-registered value under the caller's new name -- aenum treats a taken value as an alias request, so the old bare extend_enum() minted nothing and raised nothing, contradicting the method's own docstring. pcapkit.corekit.enums.EnumRegistry centralises get/get_all/register/ register_alias/register_aliases/_unregistered_member in one mixin and converts the first six registries with no bespoke __new__. The new guard reaches only those six; the other 105 const registries still carry the old generator's ungated extend_enum, so pcapkit.const ships two contradictory register contracts until #858 migrates the rest -- residue the PR names rather than papers over. Verified independently rather than taken from the PR table: the base's get() dispatch divergence on a non-int/non-str key raises KeyError on the still-unconverted generated template (empirically confirmed against 05468a0, not the TypeError the PR body itself claims for that path).
…rated template
pcapkit/vendor/default.py's generated template now emits
class {NAME}(EnumRegistry, IntEnum) and drops its own get/register/
_unregistered_member entirely, so every const-enum class it produces
inherits #855's guard instead -- e.g. TransType.register(6,
'TOTALLY_NEW_NAME') now raises ValueError naming the existing member and
pointing at register_alias(), where it used to mint nothing and raise
nothing.
Census re-derived independently by AST over the merge commit rather than
taken from the PR table: 121 const modules hold 127 enum classes (three
modules define more than one class each). 6 classes already used
EnumRegistry from #855; of the other 115 modules, 105 share the generated
template byte-for-byte and are converted here, and the remaining 10 keep
their own bespoke __new__ and are left alone -- ftp/command (4 classes),
ftp/return_code (3), http/method, http/status_code, pcapng/option_type,
reg/apptype/apptype.py (2) and its four transport subclasses. Total now
inheriting EnumRegistry: 111 of 127 classes, up from 6. Also renames
pcapkit/corekit/enums.py to enum.py (no -s), the maintainer's ruling.
util/changelog_md.py regenerated CHANGELOG.md for all three entries added
across this and the two preceding commits (#855, #856, #858); --check
exit 0.
…- it was wrong 14c8930's commit message states: "the base's get() dispatch divergence on a non-int/non-str key raises KeyError on the still-unconverted generated template (empirically confirmed against 05468a0, not the TypeError the PR body itself claims for that path)." That claim is false. The test behind it called TransType.get(3.5) -- TransType is a generated registry #855 never touched, so it only ever tells you about #858's population, not #855's. Re-tested properly this round: detached worktrees at 895cde6 (856, immediately before 855) and 05468a0 (855), editable finder evicted from sys.meta_path, pcapkit.__file__ asserted per process, one throwaway process per cell since these registries mint on a fresh value. Flags.get(3.5) at 895cde6 (855's own six, pre-conversion): TypeError: argument of type 'float' is not a container or iterable Flags.get(3.5) at 05468a0 (855's own six, post-conversion): ValueError: 3.5 is not a valid Flags TransType.get(3.5) at both refs (generated, #855 never touches it): KeyError: 3.5 -- unchanged by #855, this is #858's population So #855's PR body was right: TypeError is what the six bespoke registries raised before this PR. KeyError belongs to the still-unconverted 105 and is correctly attributed to #858's entry, not #855's. The #855 entry is corrected back to TypeError, scoped to its own six registries' own before/after rather than compared against "the generated template ... used by the other 105" -- that comparison belongs to #858, which already states it correctly. 14c8930 is left as-is rather than amended: it has already been pushed, and rewriting it would need a force-push, which this branch does not do. This commit is the correction of record instead. util/changelog_md.py regenerated CHANGELOG.md; --check exit 0.
…s tense Two fixes, both cross-review findings on #657. 1. The #855 entry's TypeError claim was generalised from one registry (Flags) to all six #855 converts. The split is on member type, not on converted-vs-not: the pre-855 hand-copied get() ends `return cls[key]`, which aenum treats as a containment test on a Flag subclass (TypeError) but a name lookup on a plain Enum (KeyError). Re-derived per-registry on 895cde6 (immediately before #855), MRO printed per cell: ExtensionHeader [IntEnum, int] get(3.5) -> KeyError: 3.5 BindingACKFlag [IntFlag, int] get(3.5) -> TypeError: argument of type 'float' is not a container or iterable Flags [IntFlag, int] get(3.5) -> TypeError: ... (as above) Confirmed post-855 (05468a0) that ExtensionHeader.get(3.5) now raises ValueError, the same KeyError->ValueError shift #858's entry already attributes to the other 105 -- so for ExtensionHeader specifically there is no new divergence at all, only the same shift arriving six registries early. Scoped the TypeError claim to the five IntFlag registries and named ExtensionHeader's own KeyError->ValueError shift separately. 2. The #584 entry said -1 "was later replaced" by a sentinel object -- completed past tense for something that has not landed. gh pr view 859 -> OPEN, mergedAt null; origin/main:pcapkit/corekit/enum.py:64 still reads NO_DEFAULT = -1. Reworded to present-continuous with an explicit "not yet landed", so the entry does not tell a reader 1.5.0 ships something it does not yet ship. util/changelog_md.py regenerated CHANGELOG.md for both fixes.
EnumRegistry.register() now raises ValueError naming the existing member and pointing at register_alias(), where it used to silently alias an already-registered value under the caller's new name -- aenum treats a taken value as an alias request, so the old bare extend_enum() minted nothing and raised nothing, contradicting the method's own docstring. pcapkit.corekit.enums.EnumRegistry centralises get/get_all/register/ register_alias/register_aliases/_unregistered_member in one mixin and converts the first six registries with no bespoke __new__. The new guard reaches only those six; the other 105 const registries still carry the old generator's ungated extend_enum, so pcapkit.const ships two contradictory register contracts until #858 migrates the rest -- residue the PR names rather than papers over. Verified independently rather than taken from the PR table: the base's get() dispatch divergence on a non-int/non-str key raises KeyError on the still-unconverted generated template (empirically confirmed against 05468a0, not the TypeError the PR body itself claims for that path).
…rated template
pcapkit/vendor/default.py's generated template now emits
class {NAME}(EnumRegistry, IntEnum) and drops its own get/register/
_unregistered_member entirely, so every const-enum class it produces
inherits #855's guard instead -- e.g. TransType.register(6,
'TOTALLY_NEW_NAME') now raises ValueError naming the existing member and
pointing at register_alias(), where it used to mint nothing and raise
nothing.
Census re-derived independently by AST over the merge commit rather than
taken from the PR table: 121 const modules hold 127 enum classes (three
modules define more than one class each). 6 classes already used
EnumRegistry from #855; of the other 115 modules, 105 share the generated
template byte-for-byte and are converted here, and the remaining 10 keep
their own bespoke __new__ and are left alone -- ftp/command (4 classes),
ftp/return_code (3), http/method, http/status_code, pcapng/option_type,
reg/apptype/apptype.py (2) and its four transport subclasses. Total now
inheriting EnumRegistry: 111 of 127 classes, up from 6. Also renames
pcapkit/corekit/enums.py to enum.py (no -s), the maintainer's ruling.
util/changelog_md.py regenerated CHANGELOG.md for all three entries added
across this and the two preceding commits (#855, #856, #858); --check
exit 0.
…- it was wrong 14c8930's commit message states: "the base's get() dispatch divergence on a non-int/non-str key raises KeyError on the still-unconverted generated template (empirically confirmed against 05468a0, not the TypeError the PR body itself claims for that path)." That claim is false. The test behind it called TransType.get(3.5) -- TransType is a generated registry #855 never touched, so it only ever tells you about #858's population, not #855's. Re-tested properly this round: detached worktrees at 895cde6 (856, immediately before 855) and 05468a0 (855), editable finder evicted from sys.meta_path, pcapkit.__file__ asserted per process, one throwaway process per cell since these registries mint on a fresh value. Flags.get(3.5) at 895cde6 (855's own six, pre-conversion): TypeError: argument of type 'float' is not a container or iterable Flags.get(3.5) at 05468a0 (855's own six, post-conversion): ValueError: 3.5 is not a valid Flags TransType.get(3.5) at both refs (generated, #855 never touches it): KeyError: 3.5 -- unchanged by #855, this is #858's population So #855's PR body was right: TypeError is what the six bespoke registries raised before this PR. KeyError belongs to the still-unconverted 105 and is correctly attributed to #858's entry, not #855's. The #855 entry is corrected back to TypeError, scoped to its own six registries' own before/after rather than compared against "the generated template ... used by the other 105" -- that comparison belongs to #858, which already states it correctly. 14c8930 is left as-is rather than amended: it has already been pushed, and rewriting it would need a force-push, which this branch does not do. This commit is the correction of record instead. util/changelog_md.py regenerated CHANGELOG.md; --check exit 0.
…s tense Two fixes, both cross-review findings on #657. 1. The #855 entry's TypeError claim was generalised from one registry (Flags) to all six #855 converts. The split is on member type, not on converted-vs-not: the pre-855 hand-copied get() ends `return cls[key]`, which aenum treats as a containment test on a Flag subclass (TypeError) but a name lookup on a plain Enum (KeyError). Re-derived per-registry on 895cde6 (immediately before #855), MRO printed per cell: ExtensionHeader [IntEnum, int] get(3.5) -> KeyError: 3.5 BindingACKFlag [IntFlag, int] get(3.5) -> TypeError: argument of type 'float' is not a container or iterable Flags [IntFlag, int] get(3.5) -> TypeError: ... (as above) Confirmed post-855 (05468a0) that ExtensionHeader.get(3.5) now raises ValueError, the same KeyError->ValueError shift #858's entry already attributes to the other 105 -- so for ExtensionHeader specifically there is no new divergence at all, only the same shift arriving six registries early. Scoped the TypeError claim to the five IntFlag registries and named ExtensionHeader's own KeyError->ValueError shift separately. 2. The #584 entry said -1 "was later replaced" by a sentinel object -- completed past tense for something that has not landed. gh pr view 859 -> OPEN, mergedAt null; origin/main:pcapkit/corekit/enum.py:64 still reads NO_DEFAULT = -1. Reworded to present-continuous with an explicit "not yet landed", so the entry does not tell a reader 1.5.0 ships something it does not yet ship. util/changelog_md.py regenerated CHANGELOG.md for both fixes.
Please follow the guide below
make pylint,make mypy,make isort)make testpasses, and a test case covers the changeWhat is the purpose of your pull request?
feat— adds a featureRefs #775, #842 — tier 2, phase 1: the shared base class
Implements your ruling on #842 verbatim — "get/get_all/register/register_alias should always exist on the
const enums - so they're to be moved to the base class" — as
EnumRegistryinpcapkit/corekit/enums.py, plus the first six registries converted onto it. Two review findings arefolded into this same commit rather than left for a follow-up:
tcp/flagswas originally scoped out on arationale that didn't hold, and
register()had a silent-alias defect independent of the batch itself. Bothbelow.
This batch is deduplication, not defect reduction, and the numbers say so. AST tally by enclosing
function,
mainvs this branch, as measured for the original five-registry batch:The +1 is the single
extend_enuminsideEnumRegistry.register. The defect count is unchanged at 1026(
_missing_1013 +get13) — these registries never minted.tcp/flagsjoining the batch continues thesame pattern with its own concrete numbers:
grep -c 'def __new__'is0for both its vendor and constmodules (unchanged by this PR — it never had one), and its own hand-copied
getis gone the same way theother five's went. What goes away is 6 duplicated
gets and 6 bespoke template copies; what arrivesis unchanged — 6 inherited methods with one call site between them.
Why
corekit, notconstTwo reasons, both structural. A hand-written module under
pcapkit/const/**breaks the all-generatedinvariant #838's census rests on; and importing
pcapkit.const.<anything>from inside a const module runspcapkit/const/__init__.py's wildcard imports — a cycle.corekitis the reusable-core layer, already ownsthe enum-adjacent
EnumField, and imports nothing fromconst.The three tiers you specified
Tier 1 is this class. Tier 2 (
AppType's sub-base with the dispatching) and tier 3 (the transportsubclasses) are not built —
AppTypekeeps its own methods, untouched. The module docstring names both andwhy.
A design objection that measurement overturned
The worker first built this as a generated template fragment in
pcapkit/vendor/default.py, arguing a mixinmight not survive
aenumtreating a non-Enumbase as the member data type. It then measured, found theobjection false, and reverted
vendor/default.pyentirely — it is not in this commit. Confirmed here:So one base serves
IntEnum,IntFlagandStrEnum— which the fragment could not, since it hardcodedint.__new__.Why these six, and the blocker for the next batch
mh/{binding_ack,binding_update,handover_ack,handover_initiate}_flag,ipv6/extension_header, andtcp/flagsare the only bespoke-template registries with no custom__new__, so the shared_unregistered_member— which sets only_name_/_value_— builds a complete member.tcp/flagswas left out on a rationale a review measurement disproved. The first draft grouped it withthe five below on the claim all six "each attach extra attributes in
__new__" — wrong fortcp/flags:Its vendor template had only a
getstaticmethod and a range-checking_missing_— the same shape as thefour
mh/*_flagmodules already converted. So it joins them:EnumRegistrymixed in,getremoved,_missing_untouched — same range check, samesuper()._missing_(value)tail, which still resolvesthrough
aenum's ownFlagmachinery sinceEnumRegistrynever defines_missing_itself. Pinned byTCPFlagsConversionTests.test_missing_resolves_identically_to_the_pre_conversion_shape, sweeping everymember, boundary and several composites against a byte-for-byte pre-conversion reproduction.
The remaining five —
ftp/command,ftp/return_code,http/method,http/status_code,pcapng/option_type— do each attach extra attributes in__new__(description/kind/group,message,safe/idempotent), so an unregistered member of theirs would be missing them. Unchanged, andstill the blocker for the next batch.
Review finding:
register()silently aliased instead of mintingregister()was a bareextend_enum(cls, name, value).aenumtreats an already-registered value as analias request:
register(existing_value, 'TOTALLY_NEW_NAME')returned the existing member unchanged, made'TOTALLY_NEW_NAME'reachable in__members__pointing at it, and minted nothing — no exception,contradicting the docstring's own "mints ... so we don't have to guess blindly" and a
Raises:sectionnaming only the name-collision case.
register_alias()already guarded the inverse case.Fix:
register()now checksvalue in cls._value2member_map_(same table, same reasonregister_aliasalready checks it) and raisesValueErrornaming the existing member and pointing atregister_alias(). Sinceregister_alias()depends on that value already existing, the two no longercall each other — both route through a new shared, ungated
_extend(), soregister_alias'smint-or-alias mechanism stays intact while
register()refuses it.Checked every
.register(call site (git grep -n '\.register('): none callEnumRegistry.registerwithan already-registered value, and the unrelated ones —
ContextRegistry.register,Protocol.register,Schema.register(Frame.register,TCP.register,Option.register, ...) — share none ofEnumRegistry's contract, so they're out of scope, not residue.The guard does not reach the 105 generated const-enum registries, and that is residue worth naming.
Measured:
def register(appears in 105 files underpcapkit/const(excluding__init__.py) — thetier-1 default-template census from #838.
pcapkit/vendor/default.pyis untouched by this PR, so each stillemits the identical bare
extend_enum(cls, name, value), the same signature(cls, value: 'int', name: 'str'), even the same docstring sentence — "the caller-named entry point thatstill grows the registry". Measured on this head:
TransType.register(6, 'TOTALLY_NEW_NAME')(6isTCP)returns
TransType.TCPunchanged, mints nothing, no exception — unchanged by this PR. Sopcapkit.constnow ships two contradictory
registercontracts:Flags.register(existing, 'X')raises,TransType.register(existing, 'X')silently aliases; before this PR every registry that had aregister()at all shared the same ungated one. Deliberately not widened here — the fix belongs in
pcapkit/vendor/default.py, in a later tier of #775.Not fully closed either way: a bitwise-composite
IntFlagvalue never before resolved still mintsunder the caller's name on
register()—aenum's own flag-composition semantics, already pinned bytest_register_on_a_flag_registry_resolves_by_name_and_value, and narrower than it sounds: once any lookupresolves the composite once, it's in
_value2member_map_and the new guard catches it same as any othervalue. See UNVERIFIED.
get()also changed observable behaviour, not previously disclosedMeasured on both trees (
pcapkit.__file__asserted per run):(Full main-side message:
TypeError: argument of type 'float' is not a container or iterable.)The first is a widening, and a fix, not a regression. The generated
get(pcapkit/vendor/default.py)already catches
KeyErroron its string path and falls back todefault; the bespoke templates' hand-copiedgetnever did, ending in a barereturn {NAME}[key]. The base aligns the bespoke registries with the restof the tree. This applies equally to all five registries converted in round 1, confirmed against
ad0ab97e4:ExtensionHeader.get('NOPE', 0)andBindingACKFlag.get('NOPE', 0)both raised the sameuncaught
KeyErrorpre-conversion — undisclosed then, disclosed now.The second is a new divergence. The base dispatches on
isinstance(key, str); the generatedgetdispatches on
isinstance(key, int). A key that is neither is a value to the base (triescls(key),raises
ValueError) and a name to the generated one (triescls[key], raisesTypeError). Degeneratetoday — no caller passes a float — but it will matter once the remaining 105 migrate with their own callers
unchanged.
Verification
const/arp/hardware.pystayed at0e195aeacb4caececc210ecf040abae9, so no IANA churn rode along.tests/const+tests/vendor: 220 passed, 39780 subtests passed;tests/const/test_const_registry_protocol.pyalone: 31 passed / 133 subtests, re-derived as
Ran 31 tests … OKunder plainunittest.02296b5dd: the six new/extended assertionscovering
tcp/flagsjoiningCONVERTED(test_tcp_flags_now_inherits_the_base,test_tcp_flags_gains_the_protocol_it_never_had, plus the generic sweeps now coveringFlags) fail withAttributeError/AssertionErrorthere, and the two newregister()-guard tests(
test_register_over_a_taken_value_raises_value_error_and_does_not_alias,test_register_over_a_taken_value_on_a_flag_registry_also_refuses) fail withValueError not raised.17 assertions fail on
02296b5ddacross the two touched test files; all pass on this head.pcapkit/corekit/enums.pyretains 100% line and branch coverage.pylint10.00/10,mypyclean,isortclean.
test_const_enum_lookup.py's render regex (already updated for the original five) andtest_const_enum_builtin_parity.py'stest_the_tcp_flags_template_renders_the_committed_module, whichhardcoded
class \w+\(IntFlag\)and@staticmethod, both of which legitimately changed shape fortcp/flagstoo.Contract notes worth your eye
getkeepsdefault=-1as no default, so the other 115 registries migrate with no signature change.get_allreturns(canonical, *distinct members at the same value)— a 1-tuple for a one-to-one registry,since an alias is a second name, not a second member.
register_aliastests_value2member_map_, notcls(value): after tier 1 an unassigned value resolvesto an
_unregistered_memberabsent from that table, so a successful call would prove nothing.registernow tests the same table for the opposite outcome — see the finding above.
ValueError, following the generated-code precedent documented atpcapkit/corekit/fields/numbers.py:600-618(get()'s documented default is ignored on the integer path across the shared const/ enum template #584, the generated enum guards raise a bare ValueError in 113 of 117 const modules, and tcp.flags.Flags has no guard at all #647). This cuts against the house rule that in-libraryexceptions come from
pcapkit.utilities.exceptions— flagging it rather than burying it; say if you wantit changed.
UNVERIFIED
registeron anIntFlagwhose value is a bitwise composite that has never been resolved beforestill registers an
aenumpseudo-member rather than growing_member_names_. Name and value stillresolve; whether that is acceptable for flag registries generally is not established and may want a
registeroverride there. Narrower than it first looked: once a value has been resolved once (by anylookup), it lands in
_value2member_map_and the new guard catches a secondregister()on it same asany other already-registered value.
register_aliasesis not transactional — names before a failing one stay registered.tests/constandtests/vendorwere run, per the OOM constraint.tests/protocolsandtests/toolkitare unverified against the newpcapkit.corekitimport in six const modules; CI is thefirst full check. (Targeted spot checks were run this round against
tests/protocols/transport/test_tcp_udp_unit.py,the two MPTCP flag-ordering/error-message files, and
tests/dumpkit/test_nameless_enum_rendering_unit.py,all of which exercise
pcapkit.const.tcp.flags.Flagsdirectly and pass.)getcarried:meta private:, so the base'sgetmay surface in docs where it previously did not.get's string branch only tries the name table (cls._member_map_[key]) and never falls through tocls(key), so on aStrEnumregistry a valid value that isn't also a member's name raisesKeyErrorrather than resolving — confirmed on an inline
EnumRegistry, StrEnumfixture:.get('known_value')(thevalue) raises
KeyError,.get('known')(the name) resolves. Harmless today, since no committedStrEnumregistry uses this base yet, but the docstring's "given a value it iscls(key)" is untested forthat member type;
test_str_valued_registriesexercises only_unregistered_member.