Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 147 additions & 35 deletions docs/source/contributing/conventions/registry-protocol.rst

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions pcapkit/const/http/method.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,12 +318,14 @@ 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 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.
``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.

"""
if not isinstance(value, str):
Expand Down
4 changes: 4 additions & 0 deletions pcapkit/const/reg/apptype/apptype.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion pcapkit/corekit/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 8 additions & 6 deletions pcapkit/vendor/http/method.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,12 +223,14 @@ 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 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.
``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.

"""
if not isinstance(value, str):
Expand Down
4 changes: 4 additions & 0 deletions pcapkit/vendor/reg/apptype/apptype.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
30 changes: 16 additions & 14 deletions tests/corekit/test_enum_get_exception_provenance_923_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -80,7 +81,7 @@

__all__ = [
'EnumKeyErrorClassTests', 'BaseGetProvenanceTests', 'BaseGetQuietnessTests',
'MethodStillMintsTests', 'TransportProtocolCaseFoldingTests',
'MethodUnregisteredMemberMintsNothingTests', 'TransportProtocolCaseFoldingTests',
'CriticalityGetDeletionTests', 'MobilityHeaderNameMissTests',
'VendorTemplateParityTests', 'TRANSPORT_GET',
]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading