Repository navigation
feat(reg)!: retype AppType.proto to its own transport, and give register_apptype varargs (#806) - #815
Conversation
|
Cross-review verdict: GOOD TO GO (sonnet; author was opus). No unprompted findings — an unusually clean change. I re-derived the two decisive claims myself, with a self-test so the zeros mean something: The signature binds correctly and Not regenerated — confirmed by a stronger method than I asked for, and the substitution was flagged. Rather than re-invoking The tests were rewritten, not weakened — each one checked: #809 is genuinely folded in, verified per file: The two remaining per-line disables in the generator sit on real The reviewer caught its own probe artefact: its first byte-identity pass used
One housekeeping note: the reviewer's worktree could not be returned to its original branch, because Flipping to |
|
One thing needs your ruling before this merges, and the author flagged it rather than deciding it. Adding
Two separable questions:
My recommendation: reject names, but reject them properly. It matches the "singular and explicit" principle you gave, and Also flagged by the author, and worth its own issue rather than holding this PR: One correction to my own brief: I told the author And the author's own honesty note is worth keeping: its first post-change coverage run read 63% with |
|
I think we can accept the |
|
Ruling recorded, in your words:
So
One thing the coercion has to decide, and I will make it strict unless you say otherwise: a name that does not match any member, and a name like |
4c035d2 to
e683be0
Compare
|
Your ruling is implemented at The author found a real trap, and I reproduced it rather than take its word. The obvious coercion, So it goes through It also correctly declined to reuse the existing which is exactly the behaviour #775 exists to remove, and the opposite of a strict coercion. Tests: 6 new blocks (single str, several str, str+member mix, unknown name, composite, and a str-vs-member equivalence check on Label is |
|
Cross-review verdict: NEEDS CHANGES (opus; author was sonnet). I re-ran the load-bearing probes against 1. This PR removes a defect that ships today. That is the #775 class, live on 2. Required: format the rejected name with 3. Required, and I am correcting the reviewer here: the Because 4. One thing needs your ruling — see the separate comment below. The coercion is case-sensitive; Confirmed and not in dispute: no str/member divergence across 11 adversarial pairs measured on real registry state (including the composite, whose Two testing caveats worth fixing eventually: the six new blocks are sequential inside one test method, so the method dies at the first and the other five never run — "all six fail without the fix" is true per-block but not provable from one invocation. And the equivalence block patches only |
|
Needs your ruling: the coercion is case-sensitive, and Measured side by side,
(a) Keep it case-sensitive — exact member names only, as implemented. Documented in the docstring, and the PR is already (b) Restore case-insensitivity — one line, I lean (b): it is what callers have today, the ruling was framed as "a light lift" rather than a tightening, and it costs nothing in strictness. But it is your API. Related, and probably the same decision: The two required changes from the cross-review ( |
e683be0 to
7b42912
Compare
…ter_apptype varargs - `AppType.proto` was the whole transport protocol set IANA assigned the service, so `TCP['http'].proto` named UDP and SCTP too -- a second copy of what the four per-transport registries already encode, and the copy that could drift. It is now the single transport of the registry the member lives in, generated that way rather than narrowed at runtime because `proto.name` is folded into the member's underlying `str` value and so into the live `_value2member_map_` key. 10,625 of 12,391 members change value. - `register_apptype` took a `proto` kwarg and fanned out across every bit it held, so `register_apptype(TCP.http, Dummy)` displaced the UDP handler for port 80 as well. It now takes `*transport` varargs, named one at a time. `class_` is positional again, at the sibling position ahead of `*transport`, restoring consistency with `register_tcp` and friends; the swallow that once made it keyword-only is disambiguated at runtime by `type(module)` alone, per further maintainer ruling, never by what the value looks like. A `str` `module` means the third argument really is a class name, even one spelling a transport (`register_apptype(80, 'a.b', 'tcp')` registers a class named `'tcp'`); any other `module` means it was never a class name, so it is prepended to `*transport` instead -- order preserved -- and `class_` resets to unset. - Per maintainer ruling, `*transport` also accepts each protocol as its member's `name`, case-insensitively (`'tcp'`, `'TCP'`, `'Tcp'` alike), coerced to the member before any registry work via `__members__` rather than `TransportProtocol[name]`, since aenum's `Flag` getitem parses a `'|'`-joined name into a composite value on its own and lowercasing does not change that. An unresolvable name raises `RegistryError` formatted with `!r`, exactly as the equivalent member does, including for a composite such as `'tcp|udp'`. Anything that is neither a `str` nor a `TransportProtocol` member is rejected the same way before the loop rather than falling through to it: `TransportProtocol` is an `IntFlag`, so an `int` such as `1` would otherwise resolve to `TCP` and register there before erroring on the missing `.name`. - Drops the `consider-using-f-string` pylint disable the `TRANSPORT` template emitted into the four const registries, which have no `%` sites. - `tests/const/test_const_apptype_split_unit.py`'s signature checks are rewritten twice over: the literal-source-text pin on the old annotation is dropped for a live `inspect.signature` check (a brittle snippet re-breaks on every annotation change), and that check itself now pins `class_` as `POSITIONAL_OR_KEYWORD` rather than `KEYWORD_ONLY`, since a bare `.bind()` can only show that a third positional lands in `class_` regardless of `module` -- the `type(module)` branch is a runtime property of the function body and is asserted by actually calling it. Generator and generated output hand-edited rather than regenerated, since `AppType` crawls IANA's live CSV; `BASE`/`TRANSPORT` re-rendered from arguments extracted out of the committed files reproduce all five byte- identically. `tests/const`, `tests/vendor` and `tests/foundation` pass.
4c073e0 to
ee51366
Compare
|
Your ruling is implemented at That last line is the one that matters: The branch is four lines at the top of the body, keyed on if not isinstance(module, str) and class_ is not NULL:
transport = (cast('TransportProtocol | str', class_),) + transport
class_ = NULLA precision worth recording, because it changes how the test can be written: Python's argument binding is purely syntactic, so a third positional always raw-binds to It also caught and fixed a test from round 1 that had relied on the now-reversed convention —
Label is |
|
Delta verdict: GOOD TO GO (opus, same reviewer). Nothing blocking. It also corrected my brief twice, both verified. My diff instruction was wrong. I told it Prepend-not-append proven by asymmetry rather than by reading the code, which is the nicer proof: Direct answer to the question I posed: a class module with The branch never sniffs the value — it keys on On the bare What this delta does change is reachability: under the keyword-only shape Both rewritten tests have teeth against the pre-reversal source, checked on a scratch tree at Nothing from earlier rounds regressed: |
|
For the NULL value in the foundation registry, can we actually use a sentinel rather than a pure string? This is not in scope of this PR tho. File an issue and we can talk further. |
…lError from ModuleDescriptor.klass (#832) (#833) `NULL` was a plain `str` (`'(null)'`) compared by identity, defined independently in `protocols.py` (13 uses) and `foundation.py` (7 uses), so an equal-but-distinct `'(null)'` from a caller took a different branch than the sentinel itself depending on string interning. That let an omitted `class_` reach `ModuleDescriptor.klass`'s bare `getattr` and surface as `AttributeError: module 'X' has no attribute '(null)'`, and the same bare `AttributeError` leaked for any bad class name across all nine `register_*` call sites that build a descriptor. Add `NullType`/`NULL` to `pcapkit.corekit.module`, the module both registries already import `ModuleDescriptor` from, giving the sentinel a type no caller-supplied string can collide with, and retype every `class_` parameter accordingly. `ModuleDescriptor.klass` now raises `ProtocolError` for a class name that resolves to nothing, and, ahead of `getattr` entirely, for a `name` still `NULL` -- an omitted argument rather than a request for a class literally named `'(null)'`. `NullType` is now a genuine singleton rather than a class this module merely instantiated once: `__new__` always hands back the existing instance, and `__copy__`/`__deepcopy__`/`__reduce__` keep `copy.copy`, `copy.deepcopy` and every `pickle` protocol (0 through 5) on that same object too. Without this, deepcopying a `ModuleDescriptor` minted a second, non-identical `NullType` that reached `getattr` as a non-`str` name and downgraded the clean `ProtocolError` above into a bare `TypeError`. Also added `NULL`/`NullType` to `__all__`, fixed an over-indented continuation line, and extended `test_null_sentinel_is_not_a_string` to assert the singleton claim its docstring made rather than only describing it. Updated #815's `AttributeError` assertion in `test_protocols.py` to `ProtocolError`, keeping its `assertIn("'tcp'", ...)` check that a `str` third argument is resolved as a class name, never sniffed as a transport. Added coverage for the omitted-class-name, explicit-`'(null)'`, sentinel-still-means-absent, and singleton-identity cases; `coverage run` shows 100% on `module.py` and `foundation.py`, and no drop in `protocols.py`.
…poses Closes #808. Blocked on #806 (merged as #815), which retyped every member's `proto` to a single transport, leaving nothing that builds or relies on a composite `TransportProtocol` value. - `TransportProtocol` becomes a plain `aenum.IntEnum`. The four transports keep their exact values (tcp=1, udp=2, sctp=4, dccp=8, undefined=0) via `cast(...)` rather than `auto()`, since IntEnum's `auto()` numbers sequentially and would renumber them. - Removed `_missing_`'s composing fallback/range guard (a plain IntEnum's default `_missing_` already rejects anything undeclared) and changed `.get()`'s `extend_enum` from `max_val * 2` to `max_val + 1`, since there are no bits left to keep distinct. - `.get()` now refuses a `|`-containing string outright rather than minting it, mirroring `register_apptype`'s existing refusal of a composite string. - `_dispatch` no longer decodes every `proto` through `show_flag_values` uniformly: a genuine `TransportProtocol` member (real or minted) is looked up directly and never bit-decomposed, since composing is impossible for a member now. A review round caught that decoding minted members' bits too made every value from 9 up (`max_val + 1`'s first mint) misread as a composite of real transports it never meant, e.g. `get('bogus')` minting 9 = tcp|dccp in bits. Only a bare `int` (from `TransportProtocol.a | .b`, which now falls through to `int.__or__`) still takes the bit-decomposition path, and its error message reconstructs `tcp|udp`-style text from the bits rather than relying on Flag repr. - Widened `proto`'s type annotation to include `int` across `_dispatch`/`get`/`get_all`, and cast at the one dict-key site mypy cannot infer, to match the type mypy actually needs to stay clean. - Applied identically to the vendor generator template; verified the generated `TransportProtocol` class and `_dispatch`/`get` bodies are byte-identical between the two by rendering the template directly. - `tests/dumpkit/test_nameless_enum_rendering_unit.py`'s flag-registry sweep drops from 7 to 6 registries (TransportProtocol no longer matches `issubclass(_, aenum.Flag)`) and from 4 to 3 distinct `_missing_` field widths; re-measured and re-pinned rather than assumed, prose updated to match. - Updated tests pinning removed Flag mechanics and two enum-sweep size pins (Flag count 7->6, IntEnum count 111->112). Added tests that fail on stock and on this fix's own first-round head: the minted-member misread as composite, a bare int with a stray non-real bit, and a composite string refused by `.get()`. - Corrected an earlier claim: `list(TransportProtocol)` now yields all five members (4 on stock) since `Flag` hid the zero-valued `undefined` from iteration and plain `IntEnum` does not. Per-member repr/str/name/value are still byte-identical; nothing in-tree iterates the class bare, only through `__members__` (5 either way). `register_apptype` and its own tests needed no change: they already reject anything that is not `isinstance(proto, TransportProtocol)`, which a bare int (what `|` now produces) satisfies identically. Built and tested: `tests/const/`, `tests/foundation/registry/`, `tests/vendor/test_vendor_reg_apptype_generator_unit.py` and `tests/dumpkit/test_nameless_enum_rendering_unit.py` pass in full (104 tests, 39354 subtests via pytest; 89 tests via plain unittest). mypy and isort clean on both touched source files.
…poses Closes #808. Blocked on #806 (merged as #815), which retyped every member's `proto` to a single transport, leaving nothing that builds or relies on a composite `TransportProtocol` value. - `TransportProtocol` becomes a plain `aenum.IntEnum`. The four transports keep their exact values (tcp=1, udp=2, sctp=4, dccp=8, undefined=0) via `cast(...)` rather than `auto()`, since IntEnum's `auto()` numbers sequentially and would renumber them. - Removed `_missing_`'s composing fallback/range guard (a plain IntEnum's default `_missing_` already rejects anything undeclared) and changed `.get()`'s `extend_enum` from `max_val * 2` to `max_val + 1`, since there are no bits left to keep distinct. - `.get()` now refuses a `|`-containing string outright rather than minting it, mirroring `register_apptype`'s existing refusal of a composite string. - `_dispatch` no longer decodes every `proto` through `show_flag_values` uniformly: a genuine `TransportProtocol` member (real or minted) is looked up directly and never bit-decomposed, since composing is impossible for a member now. A review round caught that decoding minted members' bits too made every value from 9 up (`max_val + 1`'s first mint) misread as a composite of real transports it never meant, e.g. `get('bogus')` minting 9 = tcp|dccp in bits. Only a bare `int` (from `TransportProtocol.a | .b`, which now falls through to `int.__or__`) still takes the bit-decomposition path, and its error message reconstructs `tcp|udp`-style text from the bits rather than relying on Flag repr. - Widened `proto`'s type annotation to include `int` across `_dispatch`/`get`/`get_all`, and cast at the one dict-key site mypy cannot infer, to match the type mypy actually needs to stay clean. - Applied identically to the vendor generator template; verified the generated `TransportProtocol` class and `_dispatch`/`get` bodies are byte-identical between the two by rendering the template directly. - `tests/dumpkit/test_nameless_enum_rendering_unit.py`'s flag-registry sweep drops from 7 to 6 registries (TransportProtocol no longer matches `issubclass(_, aenum.Flag)`) and from 4 to 3 distinct `_missing_` field widths; re-measured and re-pinned rather than assumed, prose updated to match. - Updated tests pinning removed Flag mechanics and two enum-sweep size pins (Flag count 7->6, IntEnum count 111->112). Added tests that fail on stock and on this fix's own first-round head: the minted-member misread as composite, a bare int with a stray non-real bit, and a composite string refused by `.get()`. That minted-member test fails on stock too, but only because stock's `max_val * 2` scheme mints a different number (16, not 9) -- stock's doubling keeps every minted value a single bit by construction, so it never exhibits the misread itself; the misread was introduced and caught within this PR's own review. - Corrected an earlier claim: `list(TransportProtocol)` now yields all five members (4 on stock) since `Flag` hid the zero-valued `undefined` from iteration and plain `IntEnum` does not. Per-member repr/str/name/value are still byte-identical; nothing in-tree iterates the class bare, only through `__members__` (5 either way). `register_apptype` and its own tests needed no change: they already reject anything that is not `isinstance(proto, TransportProtocol)`, which a bare int (what `|` now produces) satisfies identically. Built and tested: `tests/const/`, `tests/foundation/registry/`, `tests/vendor/test_vendor_reg_apptype_generator_unit.py` and `tests/dumpkit/test_nameless_enum_rendering_unit.py` pass in full (104 tests, 39354 subtests via pytest; 104 tests via plain unittest, 77+15+6+6 across the four selections). mypy and isort clean on both touched source files.
…lError from ModuleDescriptor.klass (#832) (#833) `NULL` was a plain `str` (`'(null)'`) compared by identity, defined independently in `protocols.py` (13 uses) and `foundation.py` (7 uses), so an equal-but-distinct `'(null)'` from a caller took a different branch than the sentinel itself depending on string interning. That let an omitted `class_` reach `ModuleDescriptor.klass`'s bare `getattr` and surface as `AttributeError: module 'X' has no attribute '(null)'`, and the same bare `AttributeError` leaked for any bad class name across all nine `register_*` call sites that build a descriptor. Add `NullType`/`NULL` to `pcapkit.corekit.module`, the module both registries already import `ModuleDescriptor` from, giving the sentinel a type no caller-supplied string can collide with, and retype every `class_` parameter accordingly. `ModuleDescriptor.klass` now raises `ProtocolError` for a class name that resolves to nothing, and, ahead of `getattr` entirely, for a `name` still `NULL` -- an omitted argument rather than a request for a class literally named `'(null)'`. `NullType` is now a genuine singleton rather than a class this module merely instantiated once: `__new__` always hands back the existing instance, and `__copy__`/`__deepcopy__`/`__reduce__` keep `copy.copy`, `copy.deepcopy` and every `pickle` protocol (0 through 5) on that same object too. Without this, deepcopying a `ModuleDescriptor` minted a second, non-identical `NullType` that reached `getattr` as a non-`str` name and downgraded the clean `ProtocolError` above into a bare `TypeError`. Also added `NULL`/`NullType` to `__all__`, fixed an over-indented continuation line, and extended `test_null_sentinel_is_not_a_string` to assert the singleton claim its docstring made rather than only describing it. Updated #815's `AttributeError` assertion in `test_protocols.py` to `ProtocolError`, keeping its `assertIn("'tcp'", ...)` check that a `str` third argument is resolved as a class name, never sniffed as a transport. Added coverage for the omitted-class-name, explicit-`'(null)'`, sentinel-still-means-absent, and singleton-identity cases; `coverage run` shows 100% on `module.py` and `foundation.py`, and no drop in `protocols.py`. Corrected `docs/source/pcapkit/corekit/module.rst`, a hand-written page `autodoc`/`nitpicky` never regenerates or gates: the `name` property's `:type:` still said `str`, and the page had no entry for `NullType`/`NULL` despite eight cross-references into it from `module.py`'s own docstrings. Added an "Auxiliaries" section documenting both, following the `NoValueType`/`NoValue` precedent in `fields/field.rst`. Verified by building the full site locally with `PYTHONPATH` pointed at this tree -- the venv's editable install otherwise shadows it with the unmodified main checkout -- 56 pre-existing warnings, none from this page or naming `NullType`/`NULL`. Also: the pickle-identity test now asserts outside `subTest` too, since this repo's `pytest-subtests` reports the parent test as passed when only a `subTest` failed inside it (reproduced directly to confirm); and `NullType`'s docstring now notes that `importlib.reload` desyncs the sentinel across modules that already imported it -- structural to sharing one module-level binding, true of the old `str` sentinel too, and unreached in-tree.
…poses Closes #808. Blocked on #806 (merged as #815), which retyped every member's `proto` to a single transport, leaving nothing that builds or relies on a composite `TransportProtocol` value. - `TransportProtocol` becomes a plain `aenum.IntEnum`. The four transports keep their exact values (tcp=1, udp=2, sctp=4, dccp=8, undefined=0) via `cast(...)` rather than `auto()`, since IntEnum's `auto()` numbers sequentially and would renumber them. - Removed `_missing_`'s composing fallback/range guard (a plain IntEnum's default `_missing_` already rejects anything undeclared). - `.get()` refuses both a `|`-containing string and any other unrecognised name outright, rather than minting either. The second refusal is the maintainer's own ruling on this file, at `apptype.py:96`: "Do not allow extension of TransportProtocol at all." It used to mint at `max_val + 1`; there is nothing left to walk now, and both refusals raise the identical `ValueError` shape rather than inventing a second style. `.get()` still only case-folds, never strips whitespace -- unchanged, and left alone deliberately: whether to also strip is a separate, still-open question the owner is deciding independently, and is not this change's call to make either way. - `_dispatch` no longer decodes every `proto` through `show_flag_values` uniformly: a genuine `TransportProtocol` member is looked up directly and never bit-decomposed, since composing is impossible for a member and, now that extension is refused, a member is always one of the five declared. An intermediate round of this PR, after minting had moved to `max_val + 1` but before extension was refused outright, decoded every `proto`'s bits uniformly and misread a minted `'bogus'` (value 9) as a `tcp|dccp` composite -- caught in that round's own review, never in stock. Only a bare `int` (from `TransportProtocol.a | .b`, which falls through to `int.__or__`) still takes the bit-decomposition path, and its error message reconstructs `tcp|udp`-style text from the bits rather than relying on Flag repr. - Widened `proto`'s type annotation to include `int` across `_dispatch`/`get`/`get_all`, and cast at the one dict-key site mypy cannot infer, to match the type mypy actually needs to stay clean. - Applied identically to the vendor generator template; verified the generated `TransportProtocol` class and `_dispatch`/`get` bodies are byte-identical between the two by rendering the template's `BASE` lambda directly against text extracted from the committed const file, rather than running the network-dependent vendor crawl. - `tests/dumpkit/test_nameless_enum_rendering_unit.py`'s flag-registry sweep drops from 7 to 6 registries (TransportProtocol no longer matches `issubclass(_, aenum.Flag)`) and from 4 to 3 distinct `_missing_` field widths; re-measured and re-pinned rather than assumed, prose updated to match. - Updated tests pinning removed Flag mechanics and two enum-sweep size pins (Flag count 7->6, IntEnum count 111->112). Converted the two tests whose premise was the old minting -- the `.get('bogus') -> 9` probe and the minted-member-misread-as-composite regression now assert refusal instead -- and a third (`test_transport_protocol_can_no_longer_be_extended_at_runtime`) that used to pin the registration itself now pins its removal. All three fail against this PR's own prior head (`3567359e2`) with `AssertionError: ValueError not raised`, and pass after this change. - Corrected an earlier claim: `list(TransportProtocol)` now yields all five members (4 on stock) since `Flag` hid the zero-valued `undefined` from iteration and plain `IntEnum` does not. Per-member repr/str/name/value are still byte-identical; nothing in-tree iterates the class bare, only through `__members__` (5 either way). `register_apptype` and its own tests needed no change: they already reject anything that is not `isinstance(proto, TransportProtocol)`, which a bare int (what `|` now produces) satisfies identically, and its own no-strip case-fold resolution was already the model this change's refusal-shape follows. Built and tested against current `main` (`ad4805f5f`): `tests/const/` (77), `tests/foundation/registry/` (19), `tests/vendor/test_vendor_reg_apptype_generator_unit.py` (6) and `tests/dumpkit/test_nameless_enum_rendering_unit.py` (6) all pass via plain unittest, 108 total. mypy (114 errors/38 files) is byte-for-byte identical before and after this change -- zero new errors -- and isort is clean on both touched source files.
…poses Closes #808. Blocked on #806 (merged as #815), which retyped every member's `proto` to a single transport, leaving nothing that builds or relies on a composite `TransportProtocol` value. - `TransportProtocol` becomes a plain `aenum.IntEnum`. The four transports keep their exact values (tcp=1, udp=2, sctp=4, dccp=8, undefined=0) via `cast(...)` rather than `auto()`, since IntEnum's `auto()` numbers sequentially and would renumber them. - Removed `_missing_`'s composing fallback/range guard (a plain IntEnum's default `_missing_` already rejects anything undeclared). - `.get()` refuses an unrecognised name outright rather than minting one -- the maintainer's own inline ruling on this method: "Do not allow extension of TransportProtocol at all." It used to mint at `max_val + 1`; there is nothing left to walk now. A `|`-containing string gets its own message distinct from that generic refusal -- naming it a composite and saying to resolve one transport at a time -- rather than sharing the generic text: a caller who typed a composite by hand benefits from being told why and how to fix it. An earlier round of this same change had the two branches raise the byte-for-byte identical message, which made the `if '|' in key` branch dead code -- reachable, but incapable of changing the outcome either way -- caught and fixed before this went up for review; the distinct text is now pinned by `test_get_refuses_a_composite_spelled_string`, which also asserts it differs from the generic refusal's. `.get()` still only case-folds, never strips whitespace -- unchanged, and left alone deliberately: whether to also strip is a separate, still-open question the owner is deciding independently, and is not this change's call to make either way. - `_dispatch` no longer decodes every `proto` through `show_flag_values` uniformly: a genuine `TransportProtocol` member is looked up directly and never bit-decomposed, since composing is impossible for a member and, now that extension is refused, a member is always one of the five declared. An intermediate round of this PR, after minting had moved to `max_val + 1` but before extension was refused outright, decoded every `proto`'s bits uniformly and misread a minted `'bogus'` (value 9) as a `tcp|dccp` composite -- caught in that round's own review, never in stock. Only a bare `int` (from `TransportProtocol.a | .b`, which falls through to `int.__or__`) still takes the bit-decomposition path, and its error message reconstructs `tcp|udp`-style text from the bits rather than relying on Flag repr. - Widened `proto`'s type annotation to include `int` across `_dispatch`/`get`/`get_all`, and cast at the one dict-key site mypy cannot infer, to match the type mypy actually needs to stay clean. - Applied identically to the vendor generator template; verified the generated `TransportProtocol` class and `_dispatch`/`get` bodies are byte-identical between the two by rendering the template's `BASE` lambda directly against text extracted from the committed const file, rather than running the network-dependent vendor crawl. - `tests/dumpkit/test_nameless_enum_rendering_unit.py`'s flag-registry sweep drops from 7 to 6 registries (TransportProtocol no longer matches `issubclass(_, aenum.Flag)`) and from 4 to 3 distinct `_missing_` field widths; re-measured and re-pinned rather than assumed, prose updated to match. - Updated tests pinning removed Flag mechanics and two enum-sweep size pins (Flag count 7->6, IntEnum count 111->112). Converted the two tests whose premise was the old minting -- the `.get('bogus') -> 9` probe and the minted-member-misread-as-composite regression now assert refusal instead -- and a third (`test_transport_protocol_can_no_longer_be_extended_at_runtime`) that used to pin the registration itself now pins its removal. All three fail against this PR's own prior head (`3567359e2`) with `AssertionError: ValueError not raised`, and pass after this change. - Corrected an earlier claim: `list(TransportProtocol)` now yields all five members (4 on stock) since `Flag` hid the zero-valued `undefined` from iteration and plain `IntEnum` does not. Per-member repr/str/name/value are still byte-identical; nothing in-tree iterates the class bare, only through `__members__` (5 either way). `register_apptype` and its own tests needed no change: they already reject anything that is not `isinstance(proto, TransportProtocol)`, which a bare int (what `|` now produces) satisfies identically, and its own no-strip case-fold resolution was already the model this change's refusal-shape follows. Built and tested against current `main` (`ad4805f5f`): `tests/const/` (77), `tests/foundation/registry/` (19), `tests/vendor/test_vendor_reg_apptype_generator_unit.py` (6) and `tests/dumpkit/test_nameless_enum_rendering_unit.py` (6) all pass via plain unittest, 108 total. mypy (114 errors/38 files) is byte-for-byte identical before and after this change -- zero new errors -- and isort is clean on both touched source files.
…poses Closes #808. Blocked on #806 (merged as #815), which retyped every member's `proto` to a single transport, leaving nothing that builds or relies on a composite `TransportProtocol` value. - `TransportProtocol` becomes a plain `aenum.IntEnum`. The four transports keep their exact values (tcp=1, udp=2, sctp=4, dccp=8, undefined=0) via `cast(...)` rather than `auto()`, since IntEnum's `auto()` numbers sequentially and would renumber them. - Removed `_missing_`'s composing fallback/range guard (a plain IntEnum's default `_missing_` already rejects anything undeclared). - `.get()` refuses an unrecognised name outright rather than minting one -- maintainer ruling: "Do not allow extension of TransportProtocol at all." It used to mint at `max_val + 1` (an intermediate revision of this PR; stock still doubles, `max_val * 2`); there is nothing left to walk now. Two earlier rounds of this PR gave a `'|'`-containing string, e.g. `'tcp|udp'`, its own distinct message on the theory that it names a composite rather than merely an unknown name; the owner's final ruling drops that distinction outright rather than refining it -- "since it's no longer a Flag, `|` joined values are no longer parsed and accepted, we will treat it as a whole, instead of splitting" -- so `'|'` gets the identical generic refusal any other unrecognised name does. `.get()` still only case-folds, never strips whitespace, per the same ruling: the owner pointed at engine selection (`extraction.py:922`, lower-only, no `.strip()` anywhere in `pcapkit/foundation/`) as the convention to match, and normalising is the only part of that convention adopted -- engine selection warns and falls back to a default on a miss, `.get()` still raises. - `_dispatch` no longer decodes a bare-int `proto`'s bits at all, matching the same ruling: a composite built by hand, e.g. `TransportProtocol.tcp | TransportProtocol.udp` (a bare `int` since `|` falls through to `int.__or__` now), used to be split via `show_flag_values` into a `ProtocolError` naming every transport whose bit was set -- the GitHub issue #759 fix, present on stock and refined once more in an intermediate round of this PR to tell a clean composite (`3`, real bits only) apart from a stray bit (`17`, one real bit plus one nothing declares). The owner's ruling retires that decoding entirely rather than refining it further: a bare-int composite is now refused exactly like any other value naming no registry -- one plain `ValueError`, whether the int is `3`, `17`, or `TransportProtocol.undefined`. This removes the last use of `show_flag_values` and of `ProtocolError` from this module, so both imports are dropped along with the docstring `Raises:` entries naming `ProtocolError` on `_dispatch`/`get`/`get_all`. User-visible consequence: `AppType.get(80, proto=17)` and `AppType.get(80, proto=3)` were both `ProtocolError` on stock `ad4805f5f` and are both `ValueError` now. Neither is a regression on a *supported* input -- a bare `int` was off-contract until this PR widened `proto`'s annotation to include it -- but the exception type a caller now sees for that input has changed. - Widened `proto`'s type annotation to include `int` across `_dispatch`/`get`/`get_all`, and cast at the one dict-key site mypy cannot infer, to match the type mypy actually needs to stay clean. - Applied identically to the vendor generator template; verified the generated `TransportProtocol` class and `_dispatch`/`get` bodies are byte-identical between the two by rendering the template's `BASE` lambda directly against text extracted from the committed const file, rather than running the network-dependent vendor crawl. - `pcapkit/foundation/registry/protocols.py` (in scope once #835 merged into `ad4805f5f`): corrected `register_apptype`'s own NOTE, which justified resolving a string transport via `__members__` rather than `TransportProtocol[name]` with two claims this PR made false -- that `Flag.__getitem__` parses `'tcp|udp'` into the value `3`, and that `TransportProtocol` is an `IntFlag`. Neither holds once `|` is retired: `TransportProtocol['tcp|udp']` now raises a bare `KeyError`, same as `TransportProtocol['bogus']`, which is the corrected reason `__members__.get(...)` is still used -- this function's own contract is `RegistryError` on a miss, not `KeyError`. The `registries.get(1)` conclusion right after it is unchanged and stays: `hash(TransportProtocol.tcp) == hash(1)` regardless of the base, so an `int` key still hits a `TransportProtocol`-keyed dict entry. No behaviour changed here, only the comment explaining it. - `tests/dumpkit/test_nameless_enum_rendering_unit.py`'s flag-registry sweep drops from 7 to 6 registries (TransportProtocol no longer matches `issubclass(_, aenum.Flag)`) and from 4 to 3 distinct `_missing_` field widths; re-measured and re-pinned rather than assumed, prose updated to match. - Updated tests pinning removed Flag mechanics and two enum-sweep size pins (Flag count 7->6, IntEnum count 111->112). Converted every test whose premise the rulings above removed: the `.get('bogus')` minting probe and its misread-as-composite regression now assert refusal instead; the composite-string test lost its distinct-message assertions; the bare-int composite test (`test_a_bare_int_composite_is_refused_as_a_whole`, renamed from `test_a_proto_naming_two_transports_is_refused_rather_than_resolved`) now asserts the identical plain `ValueError` for `3` that a stray bit and `undefined` already got, through all three entry points; and `test_transport_protocol_can_no_longer_be_extended_at_runtime` pins the registration's removal rather than its shape. Corrected two rounds of stale narration a cross-review caught along the way: three comments/docstrings citing a `TransportProtocol.__getitem__` contrast that no longer has anything to contrast (there is no composite-specific branch left to justify), and two docstrings attributing the `max_val + 1` minting scheme to stock rather than to this PR's own now-superseded intermediate revision -- stock mints at `max_val * 2` (`16` for `'quic'`/`'bogus'`), measured on `ad4805f5f`. Also normalised three `3118ed796` "stock" references to `ad4805f5f` for consistency with the rebased base, since the claims hold at either commit. - Corrected an earlier claim: `list(TransportProtocol)` now yields all five members (4 on stock) since `Flag` hid the zero-valued `undefined` from iteration and plain `IntEnum` does not. Per-member repr/str/name/value are still byte-identical; nothing in-tree iterates the class bare, only through `__members__` (5 either way). `register_apptype` and its own tests needed no change beyond the NOTE above: they already reject anything that is not `isinstance(proto, TransportProtocol)`, which a bare int (what `|` now produces) satisfies identically, and its own no-strip case-fold resolution was already the model `.get()`'s normalisation follows. Built and tested against current `main` (`ad4805f5f`): `tests/const/` (77), `tests/foundation/registry/` (19), `tests/vendor/test_vendor_reg_apptype_generator_unit.py` (6) and `tests/dumpkit/test_nameless_enum_rendering_unit.py` (6) all pass via plain unittest, 108 total. mypy (114 errors/38 files) is identical before and after this change once line-number drift from the new `protocols.py` comment is accounted for -- zero new errors -- and isort is clean on all three touched source files.
…issue (#719) - Nine citations in tests/ called a pull request "GitHub issue" (#921, #764, #906, #815, #936, #721, #501, #983) or lumped PR #428 in with issue #425; each now names the right kind. - test_dispatch_default_resolution_unit: issue #425 reported the registry leak and PR #428 fixed it, so the sentence says "reported" and "fixed" instead of crediting the issue with the fix. - Prose only: docstrings and comments, no assertion or logic touched.
make pylint,make mypy,make isort)make testpasses, 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 defectfeat— adds a feature — breaking,feat(reg)!:perf— changes performance, not behaviourrefactor— changes neither behaviour nor performancetest— tests onlydocs— documentation onlyci— workflows or build toolingchore— anything elseDescription of your pull request and other information
Closes #806. Closes #809. Step 1 of #801's three, per the ruling recorded there.
AppType.protoheld the whole transport protocol set IANA assigned the service, soTCP['http'].protonamed UDP and SCTP as well. That was a second copy of what the four per-transport registries already encode — and the copy that could drift. It is now the single transport of the registry the member lives in. Done in the generated source rather than at runtime:proto.nameis folded into the member's underlyingstrvalue by__new__, and that value is the live key in_value2member_map_, so 10,625 of 12,391 members change.value,repr()andstr().The two halves are one change because
register_apptyperelied on the multi-bit value to fan out: it readproto = code.protoand tested each bit within, soregister_apptype(TCP.http, Dummy)displaced the UDP handler for port 80 too. It now takes*transportvarargs, named one at a time, withclass_keyword-only — a positional-with-default ahead of the varargs binds the first transport toclass_and silently eats it, whichtests/const/test_const_apptype_split_unit.pynow pins with aninspect.signature().bind()check. A member with no explicit transport uses its own (now single-bit)proto; a bareintwith none is aRegistryError, as is a hand-built composite.Not regenerated —
AppTypecrawls IANA's live CSV, so a regeneration is not reproducible. The generator and its output were hand-edited, thenBASE(...)/TRANSPORT(...)re-rendered from arguments extracted out of the committed const files, reproducing all five byte-identically (andorigin/main's five from its own templates, as a control). Also drops the staleconsider-using-f-stringdisable theTRANSPORTtemplate emitted into the four registries, which is #809.