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
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ A parsed RARP takes ``DRARP`` as its alias for operation 5, and
``__index__`` of RARP.

A family-inclusive member is selected by any sibling's registry key (a key of
``pcapkit.protocols.__proto__``), because each expands to the shared family name
:attr:`ProtocolBase.__registry__ <pcapkit.protocols.protocol.ProtocolBase.__registry__>`),
because each expands to the shared family name
(:ref:`below <protocol-family-selectors>`). A narrow member is selected by its own name
and by the key of each family whose ``id()`` lists it: ``HTTPv1`` and ``HTTPv2`` by
``HTTP``, and ``AH`` and ``ESP`` by ``IPsec`` and by ``IPv6_Ext`` where IPv4 carries
Expand Down Expand Up @@ -465,8 +466,10 @@ layer packages, :mod:`~pcapkit.protocols.misc.pcap` and
package does (:ref:`const-vendor-pairing`), and :mod:`pcapkit` re-exports
:mod:`pcapkit.protocols` the same way. A protocol's own package keeps the imports
above, since importing its ``protocol`` module imports its ``schema`` and ``data``
anyway. :data:`pcapkit.protocols.__proto__` names every protocol class, so
:mod:`pcapkit.protocols.registry` builds it when it is first reached.
anyway. The by-name registry,
:attr:`ProtocolBase.__registry__ <pcapkit.protocols.protocol.ProtocolBase.__registry__>`,
names each built-in protocol by a :class:`~pcapkit.corekit.module.ModuleDescriptor`
rather than by its class, so it imports none of them (:issue:`2763`).

**A family's shared pieces live in the family base's package**, and its members
import them (:issue:`2534`): :class:`~pcapkit.protocols.link.c_tag.protocol.C_Tag`
Expand Down
35 changes: 30 additions & 5 deletions docs/source/contributing/conventions/registry-protocol.rst
Original file line number Diff line number Diff line change
Expand Up @@ -758,8 +758,10 @@ public wrapper in :mod:`pcapkit.foundation.registry`, carrying the validation th
established ones carry. A map's registration comes in two parts:

1. **A** ``register`` **or** ``register_<kind>`` **classmethod** on the class owning
the map -- ``register`` for ``__proto__``, ``register_<kind>`` for ``__<kind>__``
-- which writes it and warns when it displaces an entry. An inherited one counts
the map -- ``register`` for ``__proto__``, ``register_<kind>`` for ``__<kind>__``,
and ``register_protocol`` for the by-name registry below, named for the class it
registers (:issue:`2763`) -- which writes it and warns when it displaces an
entry. An inherited one counts
only when it is typed and validated for exactly that map's keys, which the owner
chose over an override on every class (:issue:`2511`). So
:class:`~pcapkit.protocols.transport.tcp.protocol.TCP` and
Expand Down Expand Up @@ -838,8 +840,30 @@ depart from it:

:func:`~pcapkit.foundation.registry.protocols.protocol.register_protocol` and
:func:`~pcapkit.foundation.registry.protocols.protocol.register_protocol_code` are
outside the rule: the first writes :data:`pcapkit.protocols.__proto__`, keyed by
class name, and the second whichever maps its ``code`` names.
outside the rule: the first writes the by-name registry, keyed by class name, and the
second whichever maps its ``code`` names.

**The by-name registry is a class-level map as well**, ruled on :issue:`2763`.
:attr:`ProtocolBase.__registry__ <pcapkit.protocols.protocol.ProtocolBase.__registry__>`
maps each upper-cased class name to its class, and lists every built-in protocol as a
:class:`~pcapkit.corekit.module.ModuleDescriptor`, as a ``__proto__`` map names its
targets, so it is whole before any protocol module is imported and
:meth:`ProtocolBase.get_protocol <pcapkit.protocols.protocol.ProtocolBase.get_protocol>`
imports a class only when it resolves its entry, while
:attr:`ProtocolMeta.registry <pcapkit.protocols.protocol.ProtocolMeta.registry>` is a
read-only view of the entries as stored, read as ``AnyProtocol.registry`` and importing
nothing. The map and both readers stay on the internal base, and only the hook that
fills it is on the public one: a concrete subclass of
:class:`~pcapkit.protocols.protocol.Protocol` names itself from
:meth:`Protocol.__init_subclass__ <pcapkit.protocols.protocol.Protocol.__init_subclass__>`,
as the library's other public bases register theirs, and an abstract one, or a class
deriving :class:`~pcapkit.protocols.protocol.ProtocolBase` alone, does not. A name
already held keeps its entry, and the clash is
reported as a :exc:`~pcapkit.utilities.warnings.RegistryWarning`; only
:meth:`ProtocolBase.register_protocol <pcapkit.protocols.protocol.ProtocolBase.register_protocol>`,
which the wrapper calls, replaces it. A built-in meeting its own descriptor there is no
clash. :file:`tests/protocols/test_protocol_registry_2763_unit.py` fails on a shipped
protocol class with no entry, or on an entry naming another module or class.

:func:`~pcapkit.foundation.registry.protocols.transport.register_sctp_chunk` is the
shape for a parser map:
Expand All @@ -861,7 +885,8 @@ shape for a parser map:
every protocol class for such a map, and fails on one that no wrapper's tag names
unless its ``EXCUSED`` table gives the reason. It fails as well on a map whose owner
defines no ``register*`` classmethod of its own, unless its ``INHERITED`` table names
the one typed for that map's keys.
the one typed for that map's keys, and its ``NAMED`` table holds the classmethod named
otherwise than for its map.

**A protocol's table from a code to the layout read under it is such a map as well, and
each layout is a concrete schema.** Ruled in review of :pr:`2657` (:issue:`2576`): the
Expand Down
23 changes: 14 additions & 9 deletions docs/source/pcapkit/protocols/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -196,19 +196,24 @@ from given information. The class hierarchy of :mod:`pcapkit.protocols`:
Protocol Registry
-----------------

.. module:: pcapkit.protocols.registry
The registry of protocol classes by name is
:attr:`ProtocolBase.__registry__ <pcapkit.protocols.protocol.ProtocolBase.__registry__>`.
:attr:`ProtocolMeta.registry <pcapkit.protocols.protocol.ProtocolMeta.registry>` reads it
as ``AnyProtocol.registry``, a read-only view importing nothing, and
:meth:`ProtocolBase.get_protocol <pcapkit.protocols.protocol.ProtocolBase.get_protocol>`
looks a name up in it and returns the class.

.. autodata:: pcapkit.protocols.__proto__
:no-value:
.. versionremoved:: 1.6.0

:mod:`pcapkit.protocols.registry` builds it the first time it is reached, and
imports every protocol class :mod:`pcapkit.protocols` exports to do so
(:issue:`2730`).
``pcapkit.protocols.__proto__``, with no alias: read
:attr:`ProtocolBase.__registry__ <pcapkit.protocols.protocol.ProtocolBase.__registry__>`
through :meth:`ProtocolBase.get_protocol
<pcapkit.protocols.protocol.ProtocolBase.get_protocol>` instead (:issue:`2763`).

.. seealso::
.. seealso::

Please refer to :func:`pcapkit.foundation.registry.protocols.protocol.register_protocol`
for more information.
Please refer to :func:`pcapkit.foundation.registry.protocols.protocol.register_protocol`
for more information.

Header Schema
-------------
Expand Down
7 changes: 7 additions & 0 deletions docs/source/pcapkit/protocols/protocol.rst
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ Internal Definitions

.. automethod:: __init_subclass__

.. autoattribute:: __registry__
:no-value:
.. automethod:: register_protocol
.. automethod:: get_protocol

:class:`Protocol` inherits the other members of this class, and they are
documented under it above; each group below links to its entries.

Expand Down Expand Up @@ -345,6 +350,8 @@ Internal Definitions
:no-members:
:show-inheritance:

.. autoproperty:: registry

.. autodata:: pcapkit.protocols.protocol.OUT_OF_BAND_KEYWORDS
:no-value:

Expand Down
26 changes: 13 additions & 13 deletions pcapkit/corekit/fields/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,10 @@ def protocol(self, protocol: 'Type[_TP] | str') -> 'None':
"""Set payload protocol.

Arguments:
protocol: Payload protocol. A :obj:`str` is resolved against the
:data:`pcapkit.protocols.__proto__` registry, case-insensitively,
and an unresolved name leaves the payload as
protocol: Payload protocol. A :obj:`str` is resolved with
:meth:`ProtocolBase.get_protocol
<pcapkit.protocols.protocol.ProtocolBase.get_protocol>`,
case-insensitively, and an unresolved name leaves the payload as
:class:`~pcapkit.protocols.misc.raw.protocol.Raw`.

Warns:
Expand All @@ -275,18 +276,17 @@ def protocol(self, protocol: 'Type[_TP] | str') -> 'None':

"""
if isinstance(protocol, str):
from pcapkit.protocols import __proto__ # pylint: disable=import-outside-toplevel

# NOTE: The registry is keyed on the upper-cased class name, both
# when it is seeded (the loop over ``__all__`` in
# :mod:`pcapkit.protocols`) and when
# ``pcapkit.foundation.registry.protocols.protocol.register_protocol`` adds to
# it, so a name looked up in any other case would miss every time --
# and a miss leaves ``_protocol`` as :obj:`None`, which the property
# above resolves to :class:`~pcapkit.protocols.misc.raw.protocol.Raw`. So
from pcapkit.protocols.protocol import \
ProtocolBase # pylint: disable=import-outside-toplevel

# NOTE: The registry is keyed on the upper-cased class name, and the
# getter upper-cases the name it is given, so a name in any other
# case still resolves -- where a miss leaves ``_protocol`` as
# :obj:`None`, which the property above resolves to
# :class:`~pcapkit.protocols.misc.raw.protocol.Raw`. So
# ``PayloadField(protocol='http')`` would yield a raw payload instead
# of HTTP, with nothing to say so (#787).
resolved = __proto__.get(protocol.upper())
resolved = ProtocolBase.get_protocol(protocol)

# NOTE: Warned rather than left silent, and warned rather than
# raised. Unlike the registry lookups that dispatch on a code read
Expand Down
3 changes: 2 additions & 1 deletion pcapkit/corekit/sentinels.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,8 @@ def __bool__(self) -> 'Literal[False]':
return False


#: MissingType: Default for reading :data:`pcapkit.protocols.__proto__` in
#: MissingType: Default for reading :attr:`ProtocolBase.__registry__
#: <pcapkit.protocols.protocol.ProtocolBase.__registry__>` in
#: :mod:`pcapkit.foundation.registry.protocols.protocol`, recorded as what a code
#: registration displaced when the name was new, so that handing the code back
#: removes the name rather than restoring an entry. Private by documentation
Expand Down
7 changes: 2 additions & 5 deletions pcapkit/foundation/reassembly/reassembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from typing import TYPE_CHECKING, Generic, Type, TypeVar, cast

from pcapkit.protocols.misc.raw.protocol import Raw
from pcapkit.protocols.protocol import ProtocolBase
from pcapkit.utilities.exceptions import FieldValueError, UnsupportedCall
from pcapkit.utilities.logging import get_logger
from pcapkit.utilities.pickle import ReduceAsProtocol2
Expand All @@ -41,7 +42,6 @@

from pcapkit.corekit.infoclass import Info
from pcapkit.corekit.module import ModuleDescriptor
from pcapkit.protocols.protocol import ProtocolBase

CallbackFn = Callable[[List[_DT]], None]

Expand Down Expand Up @@ -78,10 +78,7 @@ def protocol(cls) -> 'Type[ProtocolBase]':
"""Protocol of current reassembly object."""
if hasattr(cls, '__protocol_type__'):
return cls.__protocol_type__
# NOTE: read here, not on import, which would build it and import every protocol (#2730).
from pcapkit.protocols import \
__proto__ as protocol_registry # pylint: disable=import-outside-toplevel
return protocol_registry.get(cls.name.upper(), Raw)
return ProtocolBase.get_protocol(cls.name, Raw)

@property
def registry(cls) -> 'dict[str, ModuleDescriptor[ReassemblyBase] | Type[ReassemblyBase]]':
Expand Down
Loading
Loading