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
2 changes: 1 addition & 1 deletion docs/source/pcapkit/corekit/fields/field.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Base Fields
.. versionremoved:: 1.5.0

``pcapkit.corekit.fields.field.NoValueType``, now
:class:`pcapkit.corekit.sentinels.NoValueType` (:issue:`922`,
:class:`pcapkit.corekit.sentinels.NoValueType` (:issue:`911`,
:issue:`1744`), and ``pcapkit.corekit.fields.field.NoValue``, now
:data:`pcapkit.corekit.sentinels.NO_VALUE` (:issue:`937`).

Expand Down
22 changes: 11 additions & 11 deletions pcapkit/const/ftp/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ class FEATCode(EnumRegistry, StrEnum):
minting the per-command ones at import time as an incidental side
effect of building :class:`Command`'s own rows. GitHub issue :issue:`860`:
that import-time mutation was the same defect shape :issue:`775` removed
from :class:`~pcapkit.const.pcapng.filter_type.FilterType`, just not
previously noticed here. ``_missing_`` still unmints for a keyword that
from :class:`~pcapkit.const.pcapng.filter_type.FilterType`. ``_missing_``
still unmints for a keyword that
turns up on the wire but names none of these -- the extendability the
owner conditionally asked to keep on GitHub issue :issue:`860`: unsure what a
FEAT value absent from the RFC's list means in the CSV table, the owner
Expand Down Expand Up @@ -243,16 +243,16 @@ class defines neither ``get`` nor ``_missing_`` of its own to reconcile
class Command(EnumRegistry, StrEnum):
"""[Command] FTP Command

.. note::
.. versionchanged:: 1.5.0

Neither ``_missing_`` nor ``get()`` mints any more. The owner ruled on
Neither ``_missing_`` nor ``get()`` mints a member. The owner ruled on
GitHub issue :issue:`860` that ``get`` should not mint: only IANA-registered
values are legitimate members, a new one is properly created through
:meth:`register`, and ``get`` is not given enough information to create
one. Concretely true here -- a bare wire command word carries no
:attr:`feat`/:attr:`desc`/:attr:`type`/:attr:`conf`, so minting one used
to register a permanent member with all four hollowed out to their
defaults; :meth:`register` is the path that can actually supply them.
:attr:`feat`/:attr:`desc`/:attr:`type`/:attr:`conf`, so a minted member
had all four hollowed out to their defaults; :meth:`register` is the
path that can actually supply them.

"""

Expand Down Expand Up @@ -568,10 +568,10 @@ def get(key: 'str', default: 'Optional[str]' = None) -> 'Command':
# unchanged. Two calls naming the same command in different
# case, e.g. ``get('xyzw')`` and ``get('XYZW')``, therefore build
# results that are *not* equal -- each is exactly what its own
# caller passed, which minting's ``_member_map_`` cache used to
# paper over by returning the *first* casing seen for every
# later call regardless of case. Losing that is the one
# observable behaviour change in GitHub issue #860's conversion.
# caller passed. Minting instead returned, through the
# ``_member_map_`` cache, the *first* casing seen for every later
# call regardless of case; that is the one observable behaviour
# change in GitHub issue #860's conversion.
return Command._unregistered_member(default if default is not None else key, name)
return Command[name] # type: ignore[misc]

Expand Down
92 changes: 40 additions & 52 deletions pcapkit/const/http/method.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ class Method(EnumRegistry, StrEnum):
legitimate members, and a new one is created through :meth:`register`,
which can supply :attr:`safe` and :attr:`idempotent` where a bare wire
method verb carries neither. ``get()`` matches ``key`` case-sensitively,
as :rfc:`9110` makes the method token (:issue:`896`).
as :rfc:`9110` makes the method token (:issue:`896`), and checks it
against member values as well as names (:issue:`908`). A member's
:class:`str` payload is its value, so ``Method.GET == 'GET'``
(:issue:`870`).

"""

Expand Down Expand Up @@ -199,21 +202,13 @@ def _unregistered_member(cls, value: 'str', name: 'str') -> 'Method':
declared, never reformatted. Only :attr:`name` -- the
identifier, not the value -- is canonicalised.

A *registered* member of this class used to carry something
different here: GitHub issue :issue:`870` found that
:meth:`__new__` called ``str.__new__(cls)`` with no
argument at all, so every one of the 40 declared members'
own :class:`str` payload was permanently empty regardless
of ``value`` (``str(Method.GET) == ''``, and
``Method.GET == 'GET'`` was :obj:`False`) -- true on
``main`` at ``60b85e3a4`` as well as when this docstring
was first written. :issue:`870` fixed :meth:`__new__` to
``str.__new__(cls, value)``, mirroring
A *registered* member carries its value the same way:
:meth:`__new__` passes ``value`` to ``str.__new__``, as
:class:`~pcapkit.const.ftp.command.Command`'s own
``__new__``, so a registered member's payload now agrees
with an *unregistered* member built through this method:
both carry their own value as real :class:`str` content
(``str(Method('frob')) == 'frob'``).
``__new__`` does (:issue:`870`), so both kinds of member
carry their own value as real :class:`str` content
(``str(Method.GET) == 'GET'`` and
``str(Method('frob')) == 'frob'``).
name: Bare label for the unregistered member -- here, the
canonical upper-case form of ``value``, matching the name
every *registered* member of this class is looked up by,
Expand All @@ -231,32 +226,31 @@ def get(cls, key: 'str', default: 'Optional[str]' = None) -> 'Method':
"""Backport support for original codes.

Delegates to :meth:`~pcapkit.corekit.enum.EnumLookup.get` for the
lookup itself, per GitHub issue :issue:`908`: the previous override checked
only ``_member_map_`` (names), never ``_value2member_map_``
(values), so the two IANA methods whose member *name* differs from
their *value* -- ``BASELINE_CONTROL`` / ``'BASELINE-CONTROL'`` and
lookup itself, so ``key`` is checked against member values as well
as names (GitHub issue :issue:`908`). That is what resolves the two
IANA methods whose member *name* differs from their *value* --
``BASELINE_CONTROL`` / ``'BASELINE-CONTROL'`` and
``VERSION_CONTROL`` / ``'VERSION-CONTROL'``, a hyphen being unusable
in a Python identifier -- failed to resolve through ``get`` even
though :meth:`_missing_` (and so the constructor) already found
them via that same value-side lookup.

The base is a :class:`classmethod`
(:meth:`~pcapkit.corekit.enum.EnumLookup.get`), and a zero-argument
``super()`` needs a first argument to bind, so this override had to
move from :class:`staticmethod` to :class:`classmethod` to delegate
at all -- see GitHub issue :issue:`908`'s own correction of the fix it
originally proposed. Callers are unaffected by the switch itself:
``Method.get('X')`` binds identically either way.

``default`` keeps its existing meaning here rather than adopting
the base's ``NO_DEFAULT`` sentinel and its value-only fallback --
widening it would be caller-visible, since the base's ``default``
must already name a *registered* value (resolved through
``_value2member_map_``), while this override's ``default`` instead
supplies the *value* of a freshly minted unregistered member.
Nothing in this tree calls ``get`` with a non-``None`` ``default``
to notice today, but widening the signature is a separate change
from this defect and stays out of scope.
in a Python identifier -- which :meth:`_missing_` (and so the
constructor) also finds by value.

It is a :class:`classmethod`, as the base is, because a
zero-argument ``super()`` needs a first argument to bind
(:issue:`908`). ``Method.get('X')`` binds the same as it would on a
:class:`staticmethod`.

``default`` does not follow the base's ``NO_DEFAULT`` sentinel and
value-only fallback: the base's ``default`` must name a
*registered* value (resolved through ``_value2member_map_``), while
this override's ``default`` supplies the *value* of the
unregistered member it builds on a miss.

.. versionchanged:: 1.5.0

A non-:class:`str` ``key`` raises :exc:`ValueError`
(:issue:`908`). ``get(42)`` and ``get(None)`` raised
:exc:`AttributeError` from ``key.upper()``, and ``get(b'GET')``
raised :exc:`TypeError`.

Args:
key: Key to get enum item. Looked up case-**sensitively**,
Expand All @@ -267,26 +261,20 @@ def get(cls, key: 'str', default: 'Optional[str]' = None) -> 'Method':
command codes are not. Checked against both member names
and values, via the base's own precedence -- name before
value -- so a value-only match such as
``'BASELINE-CONTROL'`` now resolves too, closing GitHub
issue :issue:`908`.
``'BASELINE-CONTROL'`` resolves too (:issue:`908`).
default: Value for the unregistered member built when ``key``
matches neither a name nor a value. ``None``, the
default, uses ``key`` itself -- unchanged from before
GitHub issue :issue:`908`.
default, uses ``key`` itself.

Raises:
ValueError: If ``key`` is not a :class:`str`. This reaches the
caller from the base's non-``str`` branch, which calls
``cls(key)`` and so ``_missing_``, and is **not** caught by
the ``except KeyError`` fallback below -- only a failed
*name* lookup is. The non-``str`` surface therefore moved
with GitHub issue :issue:`908`: ``get(42)`` and ``get(None)`` used
to raise :exc:`AttributeError` from ``key.upper()``, and
``get(b'GET')`` used to *return* a member whose name and
value were both the :class:`bytes` object. Raising is the
intended behaviour -- a ``bytes``-valued ``Method`` is not a
thing this registry should hand back -- but it is a change,
so it is stated rather than left to be discovered.
*name* lookup is. Raising is the intended behaviour for
``get(b'GET')`` as well: a member whose name and
value were both the :class:`bytes` object is not a thing
this registry should hand back.

:meta private:
"""
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/const/ipv6/smf_dpd_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

.. module:: pcapkit.const.ipv6.smf_dpd_mode

This module contains the constant enumeration for **Simplified Multicast Forwarding Duplicate Packet Detection (``SMF_DPD``) Options**,
This module contains the constant enumeration for **Simplified Multicast Forwarding Duplicate Packet Detection (SMF_DPD) Options**,
which is automatically generated from :class:`pcapkit.vendor.ipv6.smf_dpd_mode.SMFDPDMode`.

"""
Expand Down
16 changes: 8 additions & 8 deletions pcapkit/const/pcapng/option_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,28 +344,28 @@ def get(key: 'int | str', default: 'int' = -1, *, namespace: 'str' = 'opt') -> '
temp_ns.update(OptionType.__members_ns__.get(namespace, {}))
if key in temp_ns:
return temp_ns[key]
# NOTE: GitHub issue #860. This used to extend_enum(...) a
# NOTE: GitHub issue #860. An extend_enum(...) here would mint a
# permanent member as a side effect of a mere lookup -- exactly
# the ``get``/``_missing_`` minting the owner's ruling forbids,
# here on the wire-facing path
# (:meth:`pcapkit.protocols.misc.pcapng.PCAPNG._make_pcapng_options`
# calls this with raw option-code bytes off the wire).
# ``_unregistered_member`` never touches ``__members_ns__``
# either, so a genuinely unknown option code no longer grows
# either lookup table.
# either, so a genuinely unknown option code grows neither
# lookup table.
return OptionType._unregistered_member(key, '%s_unknown' % namespace)
if key in OptionType.__members__:
return getattr(OptionType, key)
# NOTE: a member's value is its formatted ``'{name} [{value}]'``
# string, so ``get(member.value)`` resolves it as ``cls(value)`` would.
if key in OptionType._value2member_map_: # type: ignore[misc]
return OptionType._value2member_map_[key] # type: ignore[misc,return-value]
# NOTE: same ruling, the str-keyed path: this used to mint ``key``
# itself as the member's name with ``default`` as its value. Neither
# NOTE: same ruling, the str-keyed path: rather than mint ``key``
# itself as the member's name with ``default`` as its value -- neither
# ``get`` nor ``_missing_`` has enough information to register one
# properly -- there is no namespace, description or anything else
# IANA would attach to it -- so this now returns an unregistered
# pseudo-member with the same attributes the old mint would have set.
# properly, as there is no namespace, description or anything else
# IANA would attach to it -- this returns an unregistered
# pseudo-member with the same attributes such a mint would set.
return OptionType._unregistered_member(default, key)

@classmethod
Expand Down
38 changes: 29 additions & 9 deletions pcapkit/const/reg/apptype/apptype.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,18 @@


class TransportProtocol(EnumLookup, IntEnum):
"""Transport layer protocol."""
"""Transport layer protocol.

.. versionchanged:: 1.5.0

An ``aenum.IntEnum`` rather than an ``aenum.IntFlag`` (:issue:`808`),
numbered sequentially: ``sctp`` is ``3`` and ``dccp`` is ``4``, where
they were ``4`` and ``8``, and no :class:`int` is decoded as a ``|``
composite (:issue:`860`). :meth:`get` refuses a name no member
declares, with :exc:`~pcapkit.utilities.exceptions.EnumKeyError`,
rather than minting a member for it (:issue:`808`, :issue:`923`).

"""

# mypy has no aenum plugin, so this class is a plain class to it: every
# member below is cast rather than left as a bare auto()-valued one --
Expand Down Expand Up @@ -222,6 +233,15 @@ class AppType(EnumRegistry, StrEnum):
makes it subclassable at all, since :mod:`aenum` refuses to extend an
enumeration that already has members.

.. versionchanged:: 1.5.0

Split by transport protocol (:issue:`732`): :mod:`pcapkit.const.reg.apptype`
is a package, and every assignment is a member of one of the four
registries above rather than of this class. ``get()`` raises
:exc:`ValueError` for a port outside ``0``-``65535`` (:issue:`758`), and
returns an unregistered member rather than minting one for a port the
registry does not assign (:issue:`860`).

Note:
The rows below are the registry entries whose **transport protocol column
is empty**. A blank transport is not an assignment to anything, so there
Expand Down Expand Up @@ -2528,9 +2548,9 @@ def _dispatch(cls, key: 'int', proto: 'TransportProtocol | str | int') -> 'Type[
of the work for :issue:`808` -- so it is looked up as the one whole
value it already is, exactly like any other bare int: refused
when that whole value names no registry, resolved when it
happens to equal one instead. Since GitHub issue :issue:`860` moved
this class off power-of-two spacing, a hand-built composite
is no longer guaranteed to be the former -- see
happens to equal one instead. With the sequential values of
GitHub issue :issue:`860`, a hand-built composite
is not guaranteed to be the former -- see
:meth:`_dispatch`'s own body for a value that resolves.

Returns:
Expand Down Expand Up @@ -2755,10 +2775,10 @@ def register(cls, port: 'int', name: 'str') -> 'AppType': # pylint: disable=arg
The counterpart of :meth:`register_alias`: that method requires
``port`` to already carry a member and adds a further name for it;
this requires the opposite -- ``port`` unclaimed in this registry --
and mints the first. :meth:`get` can no longer do this itself as of
and mints the first. :meth:`get` does not do this itself, per
GitHub issue :issue:`860` (the owner's ruling there: only IANA-registered values
are legitimate, a new entry is properly created through :meth:`register`,
and ``get`` lacks the information to create one), so this is now the only
and ``get`` lacks the information to create one), so this is the only
way to add a service this registry does not already carry.

Deliberately scoped like :meth:`register_alias` to **this**
Expand Down Expand Up @@ -2900,9 +2920,9 @@ def _unregistered_member(cls, value: 'int', name: 'str',
its :data:`__registry__` side table alike.

Used by :meth:`_missing_` and :meth:`get` for a port neither resolves
to a real, IANA-registered service, so that such a lookup no longer
grows either table -- contrast :meth:`register`, the explicit,
caller-named path that still does.
to a real, IANA-registered service, so that such a lookup grows
neither table -- contrast :meth:`register`, the explicit,
caller-named path that does.

Reconstructs :attr:`svc`, :attr:`port` and :attr:`proto` the same way
:meth:`__new__` would:
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/corekit/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
into, which is :meth:`EnumLookup._validate_value`. Legality is
every enumeration's concern; mutation is only the open registries'.

Supporting measurement, taken on this tree at the time of the split: **0** of the
Supporting measurement, taken when :issue:`877` split the tiers: **0** of the
26 non-registry enumerations define ``register``, ``register_alias``,
``register_aliases`` or ``_unregistered_member``, and there are **0**
:class:`EnumRegistry` subclasses outside :mod:`pcapkit.const`. The mutating half
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/corekit/fields/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
.. versionremoved:: 1.5.0

``pcapkit.corekit.fields.field.NoValueType``, now
:class:`pcapkit.corekit.sentinels.NoValueType` (:issue:`922`,
:class:`pcapkit.corekit.sentinels.NoValueType` (:issue:`911`,
:issue:`1744`), and ``pcapkit.corekit.fields.field.NoValue``, now
:data:`pcapkit.corekit.sentinels.NO_VALUE` (:issue:`937`).

Expand Down
Loading
Loading