From 3f525f537feddf9b3ec0faf86f2315f97d68f185 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Sun, 1 Nov 2026 11:17:49 -0500 Subject: [PATCH 1/5] docs(conventions): record registry-protocol case exceptions and the Method get/constructor split (#2551, #2552, #2553, #2556, #2565) - registry-protocol.rst records the AppType crawler's service-name lower-casing as an exception, on RFC 6335 section 5.1 (#2551). - It records Method's constructor fold and the get/constructor split on the #2552 ruling, not an RFC, and drops the stale "to mint" wording; Method._missing_'s docstring cites #2552 instead of #896 (#2552). - It states Command.get and Method.get's name-miss, default and non-str-key behaviours as ruled exceptions, says what default means there, and removes the closing note's contradiction (#2553). - It records the folds outside a registry get with their basis, cites RFC 6335 in TransportProtocol.get's docstring, corrects the str validator sentence and adds _make_reducible to the tier table (#2556). - It records the ContentType, ToS and SeedID re-casings with their basis (#2565). Method's const module regenerated from its crawler (docstring-only diff); the AppType base module edited by hand to match its template. --- .../conventions/registry-protocol.rst | 147 +++++++++++++++--- pcapkit/const/http/method.py | 11 +- pcapkit/const/reg/apptype/apptype.py | 4 + pcapkit/vendor/http/method.py | 11 +- pcapkit/vendor/reg/apptype/apptype.py | 4 + 5 files changed, 143 insertions(+), 34 deletions(-) diff --git a/docs/source/contributing/conventions/registry-protocol.rst b/docs/source/contributing/conventions/registry-protocol.rst index 950a4b6885..f63aca60e1 100644 --- a/docs/source/contributing/conventions/registry-protocol.rst +++ b/docs/source/contributing/conventions/registry-protocol.rst @@ -37,7 +37,7 @@ may *grow*: =================================================== ============================================================== :class:`~pcapkit.corekit.enum.EnumLookup` ``get``, ``get_all``, ``_validate_value`` :class:`~pcapkit.corekit.enum.EnumRegistry` ``register``, ``register_alias``, ``register_aliases``, - ``_extend``, ``_unregistered_member`` + ``_extend``, ``_unregistered_member``, ``_make_reducible`` =================================================== ============================================================== The owner ruled on :issue:`877` that a registry may subclass a bare base enum out of @@ -79,8 +79,14 @@ generated registries spell by hand in ``_missing_``: writes at the top of ``_missing_``, with the same message. That is the ``FLAG`` of most crawlers, but a bespoke template's own range where its ``_missing_`` checks something else: ``0``-``999`` for :class:`~pcapkit.const.http.status_code.StatusCode` and -:class:`~pcapkit.const.ftp.return_code.ReturnCode`, and :class:`str` for the -string-valued registries. +:class:`~pcapkit.const.ftp.return_code.ReturnCode`, and :class:`str` for +:class:`~pcapkit.const.ftp.command.Command`, +:class:`~pcapkit.const.ftp.command.FEATCode` and +:class:`~pcapkit.const.http.method.Method`. +:class:`~pcapkit.const.pcapng.option_type.OptionType` and +:class:`~pcapkit.const.reg.apptype.apptype.AppType` are :class:`str`-valued too, but their +``_missing_`` checks an :class:`int` range, so their overrides check ``0``-``0xFFFF`` +and ``0``-``65535``. :class:`~pcapkit.const.pcapng.tls_key_label.TLSKeyLabel` and :class:`~pcapkit.const.ipv6.extension_header.ExtensionHeader` have no ``_missing_`` to copy from, because their constructor refuses every value no member carries, so the @@ -183,7 +189,8 @@ installed, the interpreter's own unless an earlier loud error installed * **The name miss** is raised with ``quiet=True``. It is in-library control flow at several call sites, and at :meth:`Method.get ` it - is part of a *successful* call -- that override catches it in order to mint. + is part of a *successful* call -- that override catches it and returns an unregistered + member, which mints nothing (:ref:`mint-criterion`). * **The value miss** is quiet with or without a usable ``default``. The owner ruled for that on :issue:`1845`, on the ground that ``get`` is mostly called from protocol parsing, where a value that does not resolve is input for the caller to @@ -211,7 +218,10 @@ overrides outright. They are collected here because each was reached by the same argument: the base's contract is house-wide, so an override that diverges from it is a defect rather than a local judgement about its own callers. The first item below is not a ruling but a language constraint, recorded with them because the rulings -all presuppose it. +all presuppose it. The ruled exceptions, +:meth:`Command.get ` and +:meth:`Method.get `, are listed under the folding +rule in the next section. **An override that delegates is a** ``@classmethod``, and this one is forced by Python rather than decided. Zero-argument :class:`super` binds the enclosing @@ -335,7 +345,9 @@ case-insensitivity applies only to the selected registries where it makes logica -- :class:`~pcapkit.const.reg.apptype.apptype.TransportProtocol` being one -- or where the RFC documentation itself recognises the values as case-insensitive, FTP and HTTP commands being the candidates the owner named. Only FTP survived the audit below: -:rfc:`9110#section-9.1` makes the HTTP method token case-sensitive outright. +:rfc:`9110#section-9.1` makes the HTTP method token case-sensitive outright, so +:meth:`Method.get ` does not fold. Its constructor +does, by the ruling below. So :meth:`~pcapkit.corekit.enum.EnumLookup.get` is **case-sensitive**, and that is the default every enumeration gets. Case-insensitivity is a per-class ``get`` override that @@ -346,6 +358,38 @@ to make a lookup work -- which is what keeps ``R1_COUNTER = 129``, two IANA-registered HIP parameters differing only in case, both resolvable. +**One fold rests on a ruling rather than an RFC.** +:class:`~pcapkit.const.http.method.Method`'s ``_missing_`` upper-cases the token and +matches it against member names, then values, so ``Method('get')`` is +:attr:`Method.GET ` -- while ``Method.get('get')`` +is not (:issue:`896`) -- and both ``Method('baseline-control')`` and +``Method('baseline_control')`` are ``BASELINE_CONTROL`` (:issue:`2284`). The owner kept +this fold on :issue:`2552`: HTTP methods are case-insensitive here, and member names +follow Python's naming conventions, so the token's dash is an underscore in the name. +That departs from :rfc:`9110#section-9.1` on purpose, so the ruling is the citation. + +**Some crawlers re-case the names they generate**, each an exception recorded with its +basis: + +* The :class:`~pcapkit.const.reg.apptype.apptype.AppType` crawler lower-cases the + service name, so IANA's ``CAIlic`` (port 216) is the + :class:`~pcapkit.const.reg.apptype.tcp.TCP` member ``cailic``, valued + ``'cailic [216 - tcp]'``. :rfc:`6335#section-5.1` ignores case when comparing service + names, so the lower-cased name denotes the same service; the owner kept the + lower-casing rather than rename those members (:issue:`2551`). +* :class:`~pcapkit.const.tls.content_type.ContentType` lower-cases IANA's ``ACK`` (26) to + ``ack``, the spelling :rfc:`9147#section-4.1` uses for that content type + (:issue:`2565`). +* :class:`~pcapkit.const.ipv4.tos_del.ToSDelay`, + :class:`~pcapkit.const.ipv4.tos_thr.ToSThroughput`, + :class:`~pcapkit.const.ipv4.tos_rel.ToSReliability` and + :class:`~pcapkit.const.ipv6.seed_id.SeedID` upper-case their labels: ``Normal`` is + ``NORMAL``, ``IPv6 Source Address`` is ``IPV6_SOURCE_ADDRESS``. Each label is typed into + its crawler's own ``DATA`` rather than read from a registrar's CSV, so the casing + changed is the project's own (:issue:`2565`). + :class:`~pcapkit.const.ipv4.tos_pre.ToSPrecedence` keeps its ``DATA`` casing + (``Network_Control``). + And a folding override carries **only** the fold. :meth:`TransportProtocol.get ` is the worked example: it lowers ``key``, forwards ``default`` verbatim and delegates to @@ -360,6 +404,28 @@ to add is deleted, not kept as a pass-through** -- a ``get`` that only calls ``super().get()`` reads as though it were doing something, and the next reader has to diff it against the base to find out that it is not. +:meth:`Command.get ` does more than fold, and +:meth:`Method.get `, which does not fold, does the +same. The owner ruled each of these an exception to this rule and to the obligations +above, not a defect (:issue:`2553`): + +1. **A name miss returns an unregistered member** where the base raises + :exc:`~pcapkit.utilities.exceptions.EnumKeyError`: ``Command.get('xyzzy')`` is an + ``XYZZY`` valued ``'xyzzy'`` that neither lookup table holds. This is :issue:`860`'s + no-mint design. +2. ``default`` **is that member's value**, where the base's names a registered value to + fall back to: ``Method.get('ZZZ', 'GET')`` is an unregistered ``ZZZ`` valued ``'GET'``, + not :attr:`Method.GET `. Settled on + :issue:`2553`, on the basis :issue:`2552` gives above. +3. **A non-**\ :class:`str` **key goes to the base without** ``default``, so the base's + refusal reaches the caller whatever ``default`` is: ``Command.get(5, 'USER')`` raises + :exc:`~pcapkit.utilities.exceptions.EnumValueError` where the base returns + ``Command.USER``. :issue:`1824` set this when it made ``Command.get`` delegate such a + key, matching ``Method.get``, which sends every key to the base without ``default``. + +So on these classes ``default`` is the value an unregistered member carries on a name +miss, and does nothing on any other path. + The Lenient Criterion, in Two Limbs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -410,8 +476,9 @@ is vacuous on it rather than merely unmet.** A registry whose values are numbers nothing for case to apply to; the only way a string reaches :meth:`~pcapkit.corekit.enum.EnumLookup.get` on one is as a member *name*, and a name is the Python identifier :meth:`pcapkit.vendor.default.Vendor.safe_name` derives from -the registry's own name column -- it preserves the registrar's casing exactly, but it -is not itself a value any specification states a comparison rule for. Measured across +the registry's own name column -- it preserves the registrar's casing exactly, though a +crawler may re-case the result as recorded above, and it is not itself a value any +specification states a comparison rule for. Measured across every enumeration: **only** :class:`~pcapkit.const.hip.parameter.Parameter` would collide if names were folded, on ``R1_Counter`` against ``R1_COUNTER`` -- and **no** enumeration anywhere has two ``str`` *values* that @@ -450,7 +517,8 @@ That leaves the classes with something to decide: * - :class:`~pcapkit.const.http.method.Method` - :rfc:`9110#section-9.1` - *"The method token is case-sensitive."* Explicitly the opposite of limb 1. - - **case-sensitive** -- ``get`` does not fold (:issue:`896`) + - **case-sensitive** -- ``get`` does not fold (:issue:`896`); the constructor folds + by ruling (:issue:`2552`) * - :class:`~pcapkit.const.pcapng.option_type.OptionType` - ``draft-tuexen-opsawg-pcapng`` - Nothing states a rule; the draft never discusses option-name case. @@ -485,10 +553,11 @@ That leaves the classes with something to decide: :class:`~pcapkit.const.reg.apptype.dccp.DCCP` - :rfc:`6335#section-5.1` - *"case is ignored for comparison purposes, so both "http" and "HTTP" denote - the same service."* Limb 1, emphatically -- and these registries **derive every - member name from the registry's service-name column**, in the registrar's own - spelling (``tcpmux``); the *value* is a composite that embeds it - (``'tcpmux [1 - tcp]'``), not the bare name. + the same service."* Limb 1, emphatically -- and these registries **derive their + member names from the registry's service-name column**, falling back to the + description where it is blank, and lower-case them as recorded above + (``cailic`` for IANA's ``CAIlic``, :issue:`2551`); the *value* is a composite + that embeds the name (``'cailic [216 - tcp]'``), not the bare name. - **unimplemented** -- no service-name lookup exists to fold; see below * - :class:`~pcapkit.const.ftp.command.CommandType`, :class:`~pcapkit.const.ftp.command.ConformanceRequirement` @@ -574,14 +643,44 @@ wider than a case fix: own override is a design question for whoever writes the first string-keyed caller, not one this audit settles. -One case fold also lives **outside** any ``get``, and so escapes this convention -entirely: ``_resolve`` in :mod:`pcapkit.protocols.internet.esp` upper-cases its -``value`` before matching it against :class:`~pcapkit.const.esp.cipher.Cipher` and -:class:`~pcapkit.const.esp.integrity.Integrity` member names. :rfc:`7296` states no -comparison rule for IKEv2 transform names -- checked, it does not discuss case at all --- so that fold is a convenience with no citation behind it. It is a protocol-level -resolver rather than a registry override, which is why the audit records it here -rather than changing it. +Some case folds live **outside** any registry's ``get``, and so escape this convention. +Each is recorded here with its basis, or the lack of one: + +* :func:`~pcapkit.protocols.internet.esp._resolve` upper-cases its ``value`` before + matching it against :class:`~pcapkit.const.esp.cipher.Cipher` and + :class:`~pcapkit.const.esp.integrity.Integrity` member names, and + :meth:`IntegritySuite.get ` + upper-cases a name before handing it on. :rfc:`7296` states no comparison rule for + IKEv2 transform names -- checked, it does not discuss case at all -- so these are + conveniences with no citation behind them. +* :func:`~pcapkit.foundation.registry.protocols.register_apptype` lower-cases a + :class:`str` transport before matching it against + :class:`~pcapkit.const.reg.apptype.apptype.TransportProtocol`'s member names, so + ``'TCP'`` names ``TransportProtocol.tcp``. Its basis is that class's row above, + :rfc:`6335#section-8.1.1` by limb 2. +* :meth:`TCP.read ` and + :meth:`TCP.make ` upper-case the TCP + schema's flag keys, ``cwr`` to ``fin``, to look up + :class:`~pcapkit.const.tcp.flags.Flags` members. Both spellings are pcapkit's own and + the keys a fixed set, so no caller's text is folded and there is nothing to cite. +* :meth:`TLSKeyLog.post_process ` + and + :meth:`WireGuardKeyLog.post_process ` + upper-case a key log line's label before matching + :class:`~pcapkit.const.pcapng.tls_key_label.TLSKeyLabel` or + :class:`~pcapkit.protocols.misc.pcapng.WireGuardKeyLabel`, so ``client_random`` parses + as ``CLIENT_RANDOM``, and + :func:`~pcapkit.protocols.schema.misc.pcapng._malformed_line_message` folds the same + way to name a malformed line's label. No comparison rule backs them: + :rfc:`9850#section-2` makes only the hexadecimal fields case-insensitive, and the rows + above find none for either label set. +* :func:`~pcapkit.toolkit.scapy.tcp_traceflow` upper-cases a Scapy packet's name to look + up :class:`~pcapkit.const.reg.linktype.LinkType` by member name when the packet carries + no link type, so ``'Ethernet'`` resolves to ``LinkType.ETHERNET``. A Scapy layer name + has no registrar behind it, so there is nothing to cite. + +None of them is a registry override, which is why the audit records them here rather +than changing them. .. note:: @@ -620,9 +719,9 @@ rather than changing it. regardless of case. :meth:`Method.get ` does not fold case, as ruled on :issue:`896`: :rfc:`9110#section-9.1` says the HTTP method token is case-sensitive, so ``Method.get('get')`` does not resolve to - :attr:`Method.GET ` -- it builds its own - unregistered member, preserving the caller's exact casing, the same way an unrecognised - value always does. + :attr:`Method.GET `. It returns an unregistered + ``GET`` valued ``'get'``, the name-miss exception recorded above, while + ``Method('get')`` resolves through the constructor's fold. What does survive is narrower and deliberate: a **declared-but-unassigned** ``str`` value resolves through ``cls(value)`` but not through ``get(value)``, because diff --git a/pcapkit/const/http/method.py b/pcapkit/const/http/method.py index 5c10f58f79..53fd3ee363 100644 --- a/pcapkit/const/http/method.py +++ b/pcapkit/const/http/method.py @@ -319,11 +319,12 @@ def _missing_(cls, value: 'str') -> 'Method': the canonical upper-case member names, then against member values, so ``'baseline-control'`` resolves to ``BASELINE_CONTROL`` (:issue:`2284`) -- deliberately unlike - :meth:`get`, which is case-**sensitive** per - :rfc:`9110#section-9.1`. The split was ruled deliberate on - GitHub issue :issue:`896`; GitHub issue :issue:`908` is the pointer between - the two, so the two rationales are not left contradicting each - other with nothing tying them together. + :meth:`get`, which checks names and values too + (GitHub issue :issue:`908`) but case-**sensitively**, per + :rfc:`9110#section-9.1` (:issue:`896`). The owner kept this + fold on GitHub issue :issue:`2552`, departing from that RFC on + purpose: HTTP methods are case-insensitive here, and member + names follow Python's naming conventions. """ if not isinstance(value, str): diff --git a/pcapkit/const/reg/apptype/apptype.py b/pcapkit/const/reg/apptype/apptype.py index 48867f45c3..4bc93ca316 100644 --- a/pcapkit/const/reg/apptype/apptype.py +++ b/pcapkit/const/reg/apptype/apptype.py @@ -85,6 +85,10 @@ def get(cls, key: 'int | str', default: 'Any' = NO_DEFAULT) -> 'TransportProtoco it, for one behaviour the base does not reproduce on its own: **case folding**. This class matches a name case-insensitively (``key.lower()``); the base's own ``str`` branch is case-sensitive. + The basis is :rfc:`6335#section-8.1.1`, which writes the transport + protocol upper case where IANA's registry writes it lower case: a + spelling disagreement, which GitHub issue :issue:`903` ruled is + enough for case-insensitivity. Lowering ``key`` before delegating reproduces that: every member name here is already lower-case, so a lowered ``key`` still hits the base's exact ``_member_map_`` lookup. diff --git a/pcapkit/vendor/http/method.py b/pcapkit/vendor/http/method.py index 7e173e87e7..8b9da790ce 100644 --- a/pcapkit/vendor/http/method.py +++ b/pcapkit/vendor/http/method.py @@ -224,11 +224,12 @@ def _missing_(cls, value: 'str') -> '{NAME}': the canonical upper-case member names, then against member values, so ``'baseline-control'`` resolves to ``BASELINE_CONTROL`` (:issue:`2284`) -- deliberately unlike - :meth:`get`, which is case-**sensitive** per - :rfc:`9110#section-9.1`. The split was ruled deliberate on - GitHub issue :issue:`896`; GitHub issue :issue:`908` is the pointer between - the two, so the two rationales are not left contradicting each - other with nothing tying them together. + :meth:`get`, which checks names and values too + (GitHub issue :issue:`908`) but case-**sensitively**, per + :rfc:`9110#section-9.1` (:issue:`896`). The owner kept this + fold on GitHub issue :issue:`2552`, departing from that RFC on + purpose: HTTP methods are case-insensitive here, and member + names follow Python's naming conventions. """ if not isinstance(value, str): diff --git a/pcapkit/vendor/reg/apptype/apptype.py b/pcapkit/vendor/reg/apptype/apptype.py index 1af0e01ab1..587937453e 100644 --- a/pcapkit/vendor/reg/apptype/apptype.py +++ b/pcapkit/vendor/reg/apptype/apptype.py @@ -177,6 +177,10 @@ def get(cls, key: 'int | str', default: 'Any' = NO_DEFAULT) -> 'TransportProtoco it, for one behaviour the base does not reproduce on its own: **case folding**. This class matches a name case-insensitively (``key.lower()``); the base's own ``str`` branch is case-sensitive. + The basis is :rfc:`6335#section-8.1.1`, which writes the transport + protocol upper case where IANA's registry writes it lower case: a + spelling disagreement, which GitHub issue :issue:`903` ruled is + enough for case-insensitivity. Lowering ``key`` before delegating reproduces that: every member name here is already lower-case, so a lowered ``key`` still hits the base's exact ``_member_map_`` lookup. From 4964b3752a52530c824fbb13a4f6ee70507a9e24 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Sun, 1 Nov 2026 11:20:34 -0500 Subject: [PATCH 2/5] docs(corekit): list _make_reducible on EnumRegistry and retire the 923 test's "mint" wording (#2552, #2556) - EnumRegistry's class docstring lists _make_reducible among the methods the open tier adds, matching registry-protocol.rst's tier table (#2556). - test_enum_get_exception_provenance_923_unit: Method.get's name miss returns an unregistered member that mints nothing, not a mint (#2552). MethodStillMintsTests is MethodUnregisteredMemberMintsNothingTests, and its test and the quiet-override test are renamed to match. --- pcapkit/corekit/enum.py | 3 +- ..._enum_get_exception_provenance_923_unit.py | 30 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/pcapkit/corekit/enum.py b/pcapkit/corekit/enum.py index 05176a4826..392333023f 100644 --- a/pcapkit/corekit/enum.py +++ b/pcapkit/corekit/enum.py @@ -499,7 +499,8 @@ class EnumRegistry(EnumLookup): :meth:`~EnumLookup.get_all` and :meth:`~EnumLookup._validate_value`, all inherited here unchanged. What this tier adds is the half that makes a registry *open*: :meth:`register`, :meth:`register_alias`, - :meth:`register_aliases`, :meth:`_extend` and :meth:`_unregistered_member`. + :meth:`register_aliases`, :meth:`_extend`, :meth:`_unregistered_member` and + :meth:`_make_reducible`. An enumeration inherits from *here* when it may grow at runtime, and from :class:`EnumLookup` directly when it may not. The owner's ruling on GitHub diff --git a/tests/corekit/test_enum_get_exception_provenance_923_unit.py b/tests/corekit/test_enum_get_exception_provenance_923_unit.py index 7549aa49b5..b0060ce9f6 100644 --- a/tests/corekit/test_enum_get_exception_provenance_923_unit.py +++ b/tests/corekit/test_enum_get_exception_provenance_923_unit.py @@ -20,9 +20,10 @@ is what keeps the blast radius at nothing for the in-library sites that catch the base's name miss -- in :meth:`~pcapkit.const.http.method.Method.get`, :mod:`pcapkit.toolkit.scapy`, :mod:`pcapkit.protocols.internet.hopopt` and -:mod:`pcapkit.protocols.internet.ipv6_opts` -- and ``Method.get`` catches it in -order to **mint**, so for that one a name miss is part of a *successful* call. -:class:`MethodStillMintsTests` pins that one directly. +:mod:`pcapkit.protocols.internet.ipv6_opts` -- and ``Method.get`` catches it to +return an unregistered member, which mints nothing, so for that one a name miss is +part of a *successful* call. :class:`MethodUnregisteredMemberMintsNothingTests` +pins that one directly. Call sites that would otherwise carry the shape the ruling rejects are pinned too: @@ -80,7 +81,7 @@ __all__ = [ 'EnumKeyErrorClassTests', 'BaseGetProvenanceTests', 'BaseGetQuietnessTests', - 'MethodStillMintsTests', 'TransportProtocolCaseFoldingTests', + 'MethodUnregisteredMemberMintsNothingTests', 'TransportProtocolCaseFoldingTests', 'CriticalityGetDeletionTests', 'MobilityHeaderNameMissTests', 'VendorTemplateParityTests', 'TRANSPORT_GET', ] @@ -284,15 +285,15 @@ def test_a_name_miss_leaves_tracebacklimit_alone(self) -> 'None': _Closed.get('nosuch') self.assertFalse(hasattr(sys, 'tracebacklimit')) - def test_the_minting_override_stays_silent_end_to_end(self) -> 'None': + def test_the_unregistered_member_override_stays_silent_end_to_end(self) -> 'None': """The call this is actually for: a ``Method.get`` that misses and builds an unregistered member is a successful call and must emit nothing at all.""" from pcapkit.const.http.method import Method with capture(logger) as recorder: - minted = Method.get('QUIET-923-PROBE') + member = Method.get('QUIET-923-PROBE') self.assertEqual(recorder.messages, []) - self.assertEqual(minted.value, 'QUIET-923-PROBE') + self.assertEqual(member.value, 'QUIET-923-PROBE') def test_the_value_miss_is_quiet_too(self) -> 'None': """GitHub issue #1845: the owner ruled the value miss quiet as well. It @@ -320,25 +321,26 @@ def _restore() -> 'None': self.assertIn('EnumValueError', recorder.messages[0][1]) -class MethodStillMintsTests(unittest.TestCase): +class MethodUnregisteredMemberMintsNothingTests(unittest.TestCase): """The sharpest call site in the blast radius. :meth:`~pcapkit.const.http.method.Method.get` relies on the base's name miss being a :exc:`KeyError`: it catches it *in order to* hand back an - unregistered member. If :exc:`~pcapkit.utilities.exceptions.EnumKeyError` - did not derive from :exc:`KeyError`, this would raise instead of minting. + unregistered member, which mints nothing. If + :exc:`~pcapkit.utilities.exceptions.EnumKeyError` did not derive from + :exc:`KeyError`, this would raise instead of returning that member. """ - def test_an_unregistered_method_token_still_mints(self) -> 'None': + def test_an_unregistered_method_token_returns_a_member_that_mints_nothing(self) -> 'None': from pcapkit.const.http.method import Method before_names = len(Method._member_map_) before_values = len(Method._value2member_map_) - minted = Method.get('FROBNICATE-923') + member = Method.get('FROBNICATE-923') - self.assertEqual(minted.name, 'FROBNICATE-923') - self.assertEqual(minted.value, 'FROBNICATE-923') + self.assertEqual(member.name, 'FROBNICATE-923') + self.assertEqual(member.value, 'FROBNICATE-923') # ``_unregistered_member`` hands back a throwaway, so neither lookup # table grew -- the GitHub issue #860 contract. self.assertEqual(len(Method._member_map_), before_names) From d64114f0212c7cd0137cb43b3f2c513300a759b2 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Sun, 1 Nov 2026 11:21:30 -0500 Subject: [PATCH 3/5] docs(conventions): record HandshakeType's lower-cased Unassigned rows as an exception (#2582) - registry-protocol.rst records that HandshakeType's crawler lower-cases IANA's single-row Unassigned entries to unassigned_7 and unassigned_255, alongside ContentType's re-casing. No member is renamed (#2582). --- docs/source/contributing/conventions/registry-protocol.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/contributing/conventions/registry-protocol.rst b/docs/source/contributing/conventions/registry-protocol.rst index f63aca60e1..24a629e6a5 100644 --- a/docs/source/contributing/conventions/registry-protocol.rst +++ b/docs/source/contributing/conventions/registry-protocol.rst @@ -380,6 +380,10 @@ basis: * :class:`~pcapkit.const.tls.content_type.ContentType` lower-cases IANA's ``ACK`` (26) to ``ack``, the spelling :rfc:`9147#section-4.1` uses for that content type (:issue:`2565`). +* :class:`~pcapkit.const.tls.handshake_type.HandshakeType`'s crawler lower-cases each + cell the same way, which changes only IANA's single-row ``Unassigned`` entries, to + ``unassigned_7`` and ``unassigned_255``; the ranges ``_missing_`` names keep + ``Unassigned`` (:issue:`2582`). * :class:`~pcapkit.const.ipv4.tos_del.ToSDelay`, :class:`~pcapkit.const.ipv4.tos_thr.ToSThroughput`, :class:`~pcapkit.const.ipv4.tos_rel.ToSReliability` and From 60c81a972698f2029b4ac6d2fa36b69d24338db3 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Sun, 1 Nov 2026 11:34:23 -0500 Subject: [PATCH 4/5] docs(conventions): name every register override in Registration Wrappers (#2511) - Part 1 of Registration Wrappers names SCTP, LinuxSLL, FRAD and Netlink beside IPX and Loopback as overriding their parent's register, with the keys each map takes: PPID, ARPHRD type, NLPID and nlmsg_type. --- .../conventions/registry-protocol.rst | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/source/contributing/conventions/registry-protocol.rst b/docs/source/contributing/conventions/registry-protocol.rst index 24a629e6a5..a010fee6c0 100644 --- a/docs/source/contributing/conventions/registry-protocol.rst +++ b/docs/source/contributing/conventions/registry-protocol.rst @@ -761,14 +761,19 @@ established ones carry. A map's registration comes in two parts: :class:`~pcapkit.protocols.transport.tcp.TCP` and :class:`~pcapkit.protocols.transport.udp.UDP` keep :meth:`Transport.register `, - typed for the port their maps are keyed by, while - :class:`~pcapkit.protocols.internet.ipx.IPX` and - :class:`~pcapkit.protocols.link.loopback.Loopback` override their parents' + typed for the port their maps are keyed by. A class whose map takes other keys + overrides its parent's instead: :class:`~pcapkit.protocols.transport.sctp.SCTP` for + payload protocol identifiers rather than ports; + :class:`~pcapkit.protocols.internet.ipx.IPX` for IPX packet types rather than the + :class:`~pcapkit.const.reg.transtype.TransType` :meth:`Internet.register ` - and :meth:`Link.register `: those are - typed for :class:`~pcapkit.const.reg.transtype.TransType` and - :class:`~pcapkit.const.reg.ethertype.EtherType`, not for the IPX packet type and - address family these two maps are keyed by. + takes; and :class:`~pcapkit.protocols.link.loopback.Loopback`, + :class:`~pcapkit.protocols.link.linux_sll.LinuxSLL`, + :class:`~pcapkit.protocols.link.frad.FRAD` and + :class:`~pcapkit.protocols.link.netlink.Netlink` for address families, ARPHRD types, + NLPIDs and ``nlmsg_type`` values rather than the + :class:`~pcapkit.const.reg.ethertype.EtherType` + :meth:`Link.register ` takes. 2. **A** ``register_`` **function** in :mod:`pcapkit.foundation.registry.protocols`, tagged ``# NOTE: `` on the line above its ``def``. It calls the classmethod and logs, and a bad argument From c3da608f4ac4835200f35899cbb14c1f99b78d9f Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Sun, 1 Nov 2026 11:48:39 -0500 Subject: [PATCH 5/5] docs(conventions): settle the review nits on registry-protocol.rst (#2552, #2553) - "A fold can rest on a ruling" replaces an implicit count, and the non-str-key exception states Command.get's delegation and cites #1824 rather than narrating it. - The declared-but-unassigned str paragraph names Command.get and Method.get as the exceptions that return an unregistered member there. - The page and Method._missing_'s docstring say the constructor's value match came from the fix for #2284; the const module was re-rendered offline from its template, docstring-only. --- .../conventions/registry-protocol.rst | 28 +++++++++++-------- pcapkit/const/http/method.py | 9 +++--- pcapkit/vendor/http/method.py | 9 +++--- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/docs/source/contributing/conventions/registry-protocol.rst b/docs/source/contributing/conventions/registry-protocol.rst index a010fee6c0..c705e808b7 100644 --- a/docs/source/contributing/conventions/registry-protocol.rst +++ b/docs/source/contributing/conventions/registry-protocol.rst @@ -358,15 +358,16 @@ to make a lookup work -- which is what keeps ``R1_COUNTER = 129``, two IANA-registered HIP parameters differing only in case, both resolvable. -**One fold rests on a ruling rather than an RFC.** +**A fold can rest on a ruling rather than an RFC.** :class:`~pcapkit.const.http.method.Method`'s ``_missing_`` upper-cases the token and matches it against member names, then values, so ``Method('get')`` is :attr:`Method.GET ` -- while ``Method.get('get')`` -is not (:issue:`896`) -- and both ``Method('baseline-control')`` and -``Method('baseline_control')`` are ``BASELINE_CONTROL`` (:issue:`2284`). The owner kept -this fold on :issue:`2552`: HTTP methods are case-insensitive here, and member names -follow Python's naming conventions, so the token's dash is an underscore in the name. -That departs from :rfc:`9110#section-9.1` on purpose, so the ruling is the citation. +is not (:issue:`896`) -- and ``Method('baseline_control')`` is ``BASELINE_CONTROL`` by +name, as is ``Method('baseline-control')`` by the value match the fix for :issue:`2284` +added. The owner kept this fold on :issue:`2552`: HTTP methods are case-insensitive here, +and member names follow Python's naming conventions, so the token's dash is an underscore +in the name. That departs from :rfc:`9110#section-9.1` on purpose, so the ruling is the +citation. **Some crawlers re-case the names they generate**, each an exception recorded with its basis: @@ -424,8 +425,8 @@ above, not a defect (:issue:`2553`): 3. **A non-**\ :class:`str` **key goes to the base without** ``default``, so the base's refusal reaches the caller whatever ``default`` is: ``Command.get(5, 'USER')`` raises :exc:`~pcapkit.utilities.exceptions.EnumValueError` where the base returns - ``Command.USER``. :issue:`1824` set this when it made ``Command.get`` delegate such a - key, matching ``Method.get``, which sends every key to the base without ``default``. + ``Command.USER``. ``Command.get`` delegates such a key rather than upper-casing it + (:issue:`1824`), and ``Method.get`` delegates every key. So on these classes ``default`` is the value an unregistered member carries on a name miss, and does nothing on any other path. @@ -733,10 +734,13 @@ value resolves through ``cls(value)`` but not through ``get(value)``, because growing either lookup table. ``FEATCode.get('ZZ-NOT-REAL')`` raises :exc:`~pcapkit.utilities.exceptions.EnumKeyError` -- which *is* a :exc:`KeyError`, so an ``except KeyError`` around it is unaffected -- while -``FEATCode('ZZ-NOT-REAL')`` yields an unregistered member. Closing that gap would -mean calling ``cls(key)`` for a ``str`` value too, which reopens the minting hazard -above -- so the asymmetry is intended, and ``get``'s own docstring carries the full -reasoning. +``FEATCode('ZZ-NOT-REAL')`` yields an unregistered member. +:meth:`Command.get ` and +:meth:`Method.get ` are the exceptions: by the +name-miss exception above, ``get`` returns an unregistered member there too. Closing +the gap elsewhere would mean calling ``cls(key)`` for a ``str`` value too, which +reopens the minting hazard above -- so the asymmetry is intended, and ``get``'s own +docstring carries the full reasoning. .. seealso:: diff --git a/pcapkit/const/http/method.py b/pcapkit/const/http/method.py index 53fd3ee363..704aed9326 100644 --- a/pcapkit/const/http/method.py +++ b/pcapkit/const/http/method.py @@ -318,10 +318,11 @@ def _missing_(cls, value: 'str') -> 'Method': value: Value to get enum item. Matched case-insensitively against the canonical upper-case member names, then against member values, so ``'baseline-control'`` resolves to - ``BASELINE_CONTROL`` (:issue:`2284`) -- deliberately unlike - :meth:`get`, which checks names and values too - (GitHub issue :issue:`908`) but case-**sensitively**, per - :rfc:`9110#section-9.1` (:issue:`896`). The owner kept this + ``BASELINE_CONTROL`` by the value match the fix for + :issue:`2284` added -- deliberately unlike :meth:`get`, which + checks names and values too (GitHub issue :issue:`908`) but + case-**sensitively**, per :rfc:`9110#section-9.1` + (:issue:`896`). The owner kept this fold on GitHub issue :issue:`2552`, departing from that RFC on purpose: HTTP methods are case-insensitive here, and member names follow Python's naming conventions. diff --git a/pcapkit/vendor/http/method.py b/pcapkit/vendor/http/method.py index 8b9da790ce..900d7debea 100644 --- a/pcapkit/vendor/http/method.py +++ b/pcapkit/vendor/http/method.py @@ -223,10 +223,11 @@ def _missing_(cls, value: 'str') -> '{NAME}': value: Value to get enum item. Matched case-insensitively against the canonical upper-case member names, then against member values, so ``'baseline-control'`` resolves to - ``BASELINE_CONTROL`` (:issue:`2284`) -- deliberately unlike - :meth:`get`, which checks names and values too - (GitHub issue :issue:`908`) but case-**sensitively**, per - :rfc:`9110#section-9.1` (:issue:`896`). The owner kept this + ``BASELINE_CONTROL`` by the value match the fix for + :issue:`2284` added -- deliberately unlike :meth:`get`, which + checks names and values too (GitHub issue :issue:`908`) but + case-**sensitively**, per :rfc:`9110#section-9.1` + (:issue:`896`). The owner kept this fold on GitHub issue :issue:`2552`, departing from that RFC on purpose: HTTP methods are case-insensitive here, and member names follow Python's naming conventions.