fix(corekit): make EnumRegistry.get's no-default marker an identity sentinel (#857) - #859
Conversation
|
NEEDS CHANGES on 1. 2. Still outstanding —
After this PR that is false for the 111. Both statements would ship in the same unreleased 1.5.0. The changelog 3. PR body corrected. It lumped My own framing was wrong and the review caught it. I wrote that The strongest thing measured in the PR's favour, and it is worth recording: across 20 cells on an Also confirmed: the six new tests fail on base with real output — One census correction to my own numbers: I have been saying "121 modules / 10 bespoke". Mechanically it is 127 Not blocking, recorded for whoever touches this next: |
|
GOOD TO GO on Item 3 is the one to keep an eye on. The #584 entry promises " The code itself is three lines — Ready for you to merge. One non-blocking note recorded on the review comment above, for whoever touches this next: |
…ss-review on #859 #859 (open, breaking, about to land) changes EnumRegistry.get's no-default marker from -1 to an identity sentinel object, so -1 becomes an ordinary default value for 111 of the const registries -- making #584's "-1 ... is what separates 'no default was supplied' from 'a default was supplied'" false for what 1.5.0 actually ships, since both entries land in the same release. Reworded to past tense -- -1 "was what separated" the two cases "at the time" -- and added a forward sentence noting the convention was later replaced by a sentinel, once the shared base class #858 (added below) put get() behind one call site for most of these registries instead of many. Bare-cites #859 as the vehicle without giving it its own entry yet, per instruction -- it is still open and its head may move. The #584 entry itself is not deleted: it records a real integer-path default-handling fix that still shipped, and only the one now-superseded promise about -1 specifically needed correcting.
|
Use dedicated class rather than bare object. Follow the house convention. |
|
Recorded, verbatim: "Use dedicated class rather than bare object. Follow the house convention." Doing it. And the house convention is not a matter of taste here — there are two existing precedents in So the shape is Two consequences worth stating, because they are why this ruling improves the PR rather than just renaming things:
Also: Label back to |
|
There's also NoValue or other sentinels in the house. The naming convention is not settled. Mainly depends on how we need it. |
|
Recorded, verbatim: "There's also NoValue or other sentinels in the house. The naming convention is not settled. Taken, and you are right that it is unsettled — the two existing sentinels disagree with each other on the instance They agree only on the What the need dictates here, which is the part that decides it: So: instance stays Two related choices I have deliberately left to it with the trade-off stated, since "how we need it" cuts both ways:
It will report both decisions with reasons. Say the word if you would rather settle either yourself. |
|
I suppose we should rename NULLType to NullType. And keep the SENTINEL object's type class naming as SentinelType for a house convention. Let's document this house convention somewhere in the docs so that future maintainers and agent workers will understand the rationale and follow. |
…entinel (#857) - Replace `NO_DEFAULT = -1` in `pcapkit/corekit/enum.py` with a dedicated `NoDefaultType` singleton, per the owner's ruling on #859 ("use dedicated class rather than bare object. Follow the house convention"). Change both `get()` comparisons from `==` to `is`. - `-1` compared with `==` let a caller's genuine `-1` default, and worse, a `-1.0` default (`-1.0 == -1` is `True`), collide with the "no default" marker silently. - `NoDefaultType` follows the settled half of the house convention (`<Name>Type` for the class, as `NullType`/`NoValueType` do) but keeps the existing instance name `NO_DEFAULT` -- the instance-naming half is unsettled per the owner, and continuity with `main` decided it here. `__new__` returns a cached singleton, guarding against a caller minting a second, non-identical sentinel; `__bool__` is deliberately omitted, since `NO_DEFAULT` is checked only by `is` and a falsy sentinel would invite the exact truthy/falsy conflation this fix removes. Reasons for both, and for omitting the copy/pickle hooks `NullType` needs and this does not, are in the class docstring. `NO_DEFAULT` and `NoDefaultType` are now both exported in `__all__`. - No call site changes: `default` keeps its own default value, so no new import is needed anywhere. - Pin the new behaviour in `tests/const/test_const_registry_protocol.py`: `-1` and `-1.0` are genuine defaults now (raising for the attempted fallback rather than being read as "no default"), an omitted default is unaffected on both an IntEnum and an IntFlag registry, `NO_DEFAULT` compares unequal to `-1`, `-1.0`, `0`, `''`, `None` and `False`, its `repr` is readable rather than a bare address, and its identity is stable across the singleton guard and an ordinary re-import. - Correct `tests/const/test_const_enum_get.py`'s `test_the_placeholder_still_raises`, which asserted "explicitly passing [-1] is the same as omitting it" -- no longer true once -1 is an ordinary default, so it now pins the corrected, distinguishable behaviour instead of the stale claim. Scope: one file, `pcapkit/corekit/enum.py`, the sole consumer of `NO_DEFAULT`; 111 of 121 const registries inherit `get()` from it. No correctness bug today since no `pcapkit.const` registry's domain reaches -1; this is a clarity fix. Three of the ten bespoke registries that don't inherit this base (`ftp/return_code`, `http/status_code`, `pcapng/option_type`) carry their own, separate `-1`-as-default convention in a hand-written `get()` -- left alone, out of scope here. Build: `tests/const` (168), `tests/vendor` (86) and `tests/test_tier_guard.py` (107) all pass via plain `unittest`. mypy and pylint clean on `pcapkit/corekit/enum.py` (one pre-existing, unchanged mypy finding); isort clean.
81b367d to
f0edbbc
Compare
|
Recorded, verbatim: "I suppose we should rename NULLType to NullType. And keep the SENTINEL object's type class Two of the three are already true, and I would rather show you that than do a no-op rename. 1. So nothing to rename. Say the word if you meant a different spelling somewhere and I have looked in the wrong place. 2. So all three sentinels now agree on the rule as you have stated it — the type class is the instance name in CamelCase 3. Documenting it — doing that now, and folding it into the same page as the mint/unmint criterion you asked for on Docs-only, so it goes straight to One thing I will flag in the page rather than silently resolve: |
…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.
|
NEEDS CHANGES on R1. So the decision is right and the justification is wrong — the R2. R3. The reload caveat understates what actually breaks, and this is the valuable finding. Measured on the head: The bound parameter default holds the pre-reload sentinel while the module global holds the post-reload one, so All three design decisions ACCEPTED, with the semantics confirmed unchanged from round 1: 14 of 20 cells Two corrections to things I said. Round 1's "3 changed in exception class only" was imprecise: only 2 change Two more, not blocking: the PR body ticks |
…entinel (#857) - Replace `NO_DEFAULT = -1` in `pcapkit/corekit/enum.py` with a dedicated `NoDefaultType` singleton, per the owner's ruling on #859 ("use dedicated class rather than bare object. Follow the house convention"). Change both `get()` comparisons from `==` to `is`. - `-1` compared with `==` let a caller's genuine `-1` default, and worse, a `-1.0` default (`-1.0 == -1` is `True`), collide with the "no default" marker silently. - `NoDefaultType` follows the settled half of the house convention (`<Name>Type` for the class, as `NullType`/`NoValueType` do) but keeps the existing instance name `NO_DEFAULT` -- the instance-naming half is unsettled per the owner, and continuity with `main` decided it here. `__new__` returns a cached singleton, guarding against a caller minting a second, non-identical sentinel; `__bool__` is deliberately omitted, since `NO_DEFAULT` is checked only by `is` and a falsy sentinel would invite the exact truthy/falsy conflation this fix removes. `NO_DEFAULT` and `NoDefaultType` are now both exported in `__all__`. - Copy/pickle hooks (`__copy__`/`__deepcopy__`/`__reduce__`) are omitted, correctly reasoned per round-2 cross-review: `__reduce_ex__` at protocol >= 2 already routes through the guarded `__new__` (a `copyreg.__newobj__` reduction), so `copy.copy`/`copy.deepcopy`/ pickle >= 2 already preserve identity with no extra code; only pickle protocol 0/1 bypasses it (`copyreg._reconstructor` -> `object.__new__` directly), and that path is unreachable because the sentinel is never stored in anything this package pickles. The reload caveat is stated precisely too: unlike `-1 == -1` (immune, since it compares by value), a reload leaves `get`'s already-bound parameter default holding the pre-reload singleton while the module global holds the fresh one, so an *omitted* `default` stops raising the original lookup error after a reload -- worse than the marker it replaces, though nothing in this package reloads this module. - No call site changes: `default` keeps its own default value, so no new import is needed anywhere. - Pin the new behaviour in `tests/const/test_const_registry_protocol.py`: `-1` and `-1.0` are genuine defaults now (raising for the attempted fallback rather than being read as "no default"), an omitted default is unaffected on both an IntEnum and an IntFlag registry, `NO_DEFAULT` compares unequal to `-1`, `-1.0`, `0`, `''`, `None` and `False`, its `repr` is readable rather than a bare address, and its identity is stable across the singleton guard and an ordinary re-import. - Correct `tests/const/test_const_enum_get.py`'s `test_the_placeholder_still_raises`, which asserted "explicitly passing [-1] is the same as omitting it" -- no longer true once -1 is an ordinary default, so it now pins the corrected, distinguishable behaviour instead of the stale claim. Scope: one file, `pcapkit/corekit/enum.py`, the sole consumer of `NO_DEFAULT`; 111 of 121 const registries inherit `get()` from it. No correctness bug today since no `pcapkit.const` registry's domain reaches -1; this is a clarity fix. Three of the ten bespoke registries that don't inherit this base (`ftp/return_code`, `http/status_code`, `pcapng/option_type`) carry their own, separate `-1`-as-default convention in a hand-written `get()` -- left alone, out of scope here. Build: `tests/const` (168), `tests/vendor` (86) and `tests/test_tier_guard.py` (107) all pass via plain `unittest`. mypy and pylint clean on `pcapkit/corekit/enum.py` (one pre-existing, unchanged mypy finding); isort clean.
f0edbbc to
899bbdb
Compare
|
GOOD TO GO on Verified by me: R1 now states the real mechanism rather than the false one: R3 is the one worth reading, and the author went further than asked: it verified on a fresh Also done, both optional: the PR body's
Ready for you to merge. Its merge unblocks #860, which in turn gates 781 of #775's 1007 minting branches. |
…registries (#775) - classify every one of the 89 `_missing_` bodies still calling `extend_enum` against the owner's ruling on #775/#847: a final concrete assigned name mints, a notation for the reader (Unassigned/Reserved/Deprecated/etc.) unmints - convert 82 registries wholly and two more (`EtherType`, `Socket`) partially, 172 branches total, from `extend_enum` to `_unregistered_member`, in both the vendor crawler and the generated const file, following #838's/#858's precedent; proved a 5-file sample (Form-A and Form-B crawlers, both mixed registries) regenerates byte-identically - leave 89-82=7 untouched: `CGAType`'s mint is not an IANA-style range at all, and 6 files sit on classes without `EnumRegistry` yet (`AppType` and friends), tracked separately by #860 pending #859 - extend `tests/const/test_const_enum_no_mint.py` with the ruling-derived registry lists and behavioural/source coverage for all 82+2, correct its stale "~92 still mint" docstring to the measured 89, and fix collateral breakage in three sibling test files and `tests/vendor/test_ipx_socket_ unit.py` that pinned the pre-ruling mint behaviour - fix the last two collateral pins the ruling invalidates, in files the first pass missed and CI caught: `tests/corekit/test_fields_numbers_unassigned_ enum.py` expected `BlockType(0x0bad0bad).name == 'Reserved_0bad0bad'`, now `Reserved` with the value asserted explicitly since the name no longer carries it; and `tests/protocols/misc/test_pcapng_unit.py` read `FilterType.Unassigned_0` by attribute, a member that existed only because of the import-time mint at `pcapkit/protocols/misc/pcapng.py:4593` which this change removes Build: plain `unittest` on tests/const (179), tests/vendor (86), tests/corekit/test_fields_numbers_unassigned_enum.py (11) and tests/protocols/misc/test_pcapng_unit.py (92 + 1 skipped) all green; both newly-fixed files fail with their const file reverted to main.
…registries (#775) - classify every one of the 89 `_missing_` bodies still calling `extend_enum` against the owner's ruling on #775/#847: a final concrete assigned name mints, a notation for the reader (Unassigned/Reserved/Deprecated/etc.) unmints - convert 82 registries wholly and two more (`EtherType`, `Socket`) partially, 172 branches total, from `extend_enum` to `_unregistered_member`, in both the vendor crawler and the generated const file, following #838's/#858's precedent; proved a 5-file sample (Form-A and Form-B crawlers, both mixed registries) regenerates byte-identically - leave 89-82=7 untouched: `CGAType`'s mint is not an IANA-style range at all, and 6 files sit on classes without `EnumRegistry` yet (`AppType` and friends), tracked separately by #860 pending #859 - extend `tests/const/test_const_enum_no_mint.py` with the ruling-derived registry lists and behavioural/source coverage for all 82+2, correct its stale "~92 still mint" docstring to the measured 89, and fix collateral breakage in three sibling test files and `tests/vendor/test_ipx_socket_ unit.py` that pinned the pre-ruling mint behaviour - fix the last two collateral pins the ruling invalidates, in files the first pass missed and CI caught: `tests/corekit/test_fields_numbers_unassigned_ enum.py` expected `BlockType(0x0bad0bad).name == 'Reserved_0bad0bad'`, now `Reserved` with the value asserted explicitly since the name no longer carries it; and `tests/protocols/misc/test_pcapng_unit.py` read `FilterType.Unassigned_0` by attribute, a member that existed only because of the import-time mint at `pcapkit/protocols/misc/pcapng.py:4593` which this change removes Build: plain `unittest` on tests/const (179), tests/vendor (86), tests/corekit/test_fields_numbers_unassigned_enum.py (11) and tests/protocols/misc/test_pcapng_unit.py (92 + 1 skipped) all green; both newly-fixed files fail with their const file reverted to main.
…ss-review on #859 #859 (open, breaking, about to land) changes EnumRegistry.get's no-default marker from -1 to an identity sentinel object, so -1 becomes an ordinary default value for 111 of the const registries -- making #584's "-1 ... is what separates 'no default was supplied' from 'a default was supplied'" false for what 1.5.0 actually ships, since both entries land in the same release. Reworded to past tense -- -1 "was what separated" the two cases "at the time" -- and added a forward sentence noting the convention was later replaced by a sentinel, once the shared base class #858 (added below) put get() behind one call site for most of these registries instead of many. Bare-cites #859 as the vehicle without giving it its own entry yet, per instruction -- it is still open and its head may move. The #584 entry itself is not deleted: it records a real integer-path default-handling fix that still shipped, and only the one now-superseded promise about -1 specifically needed correcting.
…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 change — not run as such (it OOMs on this box); ran the targeted suites named below instead, and a test case covers the changedocs/source/changelog/and regeneratedCHANGELOG.md, if the change is user-visible — N/A — changelog centralised in docs(changelog): shared 1.5.0 changelog — long-lived, merges last (#610, #616, #617, #618, #620) #657What is the purpose of your pull request?
fix— corrects a defectDescription of your pull request and other information
Closes #857.
EnumRegistry.get's no default marker was the magic value-1compared with==, so a caller's own-1— or worse,-1.0, since-1.0 == -1— collided with the marker silently.NO_DEFAULTis now compared withisat both sites.Owner ruling on #859 ("use dedicated class rather than bare object. Follow the house convention") replaced the first revision's bare
object()with a dedicatedNoDefaultTypesingleton, matchingNullType(pcapkit/corekit/module.py) andNoValueType(pcapkit/corekit/fields/field.py):<Name>Type. The instance keeps the existing nameNO_DEFAULTrather than being renamed to either precedent's casing — the owner's follow-up says instance naming is unsettled and "depends on how we need it," and the need here is continuity:NO_DEFAULTis already the name onmain, so this change is to what the sentinel is, not what it's called.__new__returns a cached singleton — guards against a caller writingNoDefaultType()themselves and getting a second, non-identical sentinel that would silently failis NO_DEFAULT.__bool__— deliberately, unlike both precedents.NO_DEFAULTis checked only byis, never in boolean context, and a falsy sentinel would invite exactly the truthy/falsy conflation (if not default:) this fix removes.__copy__/__deepcopy__/__reduce__—object.__reduce_ex__at protocol ≥ 2 already routes through the guarded__new__(copyreg.__newobj__→cls.__new__(cls)), socopy.copy/copy.deepcopy/pickle ≥ 2 already preserve identity with no extra code. Only pickle protocol 0/1 bypasses it (copyreg._reconstructor→object.__new__directly), and that path never runs because the sentinel is never stored in anything this package pickles.NO_DEFAULTandNoDefaultTypeare now exported in__all__(previously['EnumRegistry']only).-1 == -1(immune, value comparison), reloading this module leavesget's already-bound parameter default holding the pre-reload singleton while the module global holds the fresh one, so an omitteddefaultstops raising the original lookup error after a reload — worse than the marker it replaces. Nothing in this package reloads it; documented rather than hidden.Full reasoning for each of these is in the
NoDefaultTypeclass docstring, corrected across two rounds of cross-review (the copy/pickle and reload reasoning above reflects the round-2 correction).Scope: one file,
pcapkit/corekit/enum.py— the sole consumer ofNO_DEFAULT. Since #858 landed, 111 of 121 const registries inheritget()from this base, so this one-file change reaches all of them. No live correctness bug: nopcapkit.constregistry's domain reaches-1today, so this is a clarity fix.Of the 10 bespoke registries that don't inherit this base, three (
ftp/return_code,http/status_code,pcapng/option_type) carry their own hand-written-1-as-default convention in a localget()— left untouched, harmonising them is out of this issue's scope.Also corrected
tests/const/test_const_enum_get.py'stest_the_placeholder_still_raises, which asserted "explicitly passing [-1] is the same as omitting it" — no longer true, so it now pins the corrected, distinguishable behaviour.Ran the targeted suites this repo's tooling notes call for instead of the full
make test(which OOMs on this box):tests/const(168 tests),tests/vendor(86) andtests/test_tier_guard.py(107), all green via plainunittest.mypy/pylint/isortclean on the changed source file (one pre-existing, unchanged mypy finding onenum.py'sSelf.nameaccess).