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
32 changes: 31 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from sphinx import addnodes
from sphinx.ext.autodoc.preserve_defaults import DefaultValue, update_default_value
from sphinx.transforms.post_transforms import SphinxPostTransform
from sphinx.util.inspect import stringify_signature
from sphinx.util.inspect import safe_getattr, stringify_signature

# NB: private modules of ``sphinx-autodoc-typehints``, used deliberately -- see
# ``bind_type_checking_names``, ``restore_class_signature`` and
Expand Down Expand Up @@ -1088,6 +1088,33 @@ def follow_builtins_alias(app: 'Sphinx', modname: 'Optional[str]', # pylint: di
return modname if getattr(value, '__module__', None) == 'builtins' else None


def read_own_enum_new(obj: type, name: str, *defargs: 'Any') -> 'Any':
"""Answer ``__new__`` on an enumeration with the one its own body defines.

Once the members are built, :mod:`enum` and ``aenum`` both put the enum
machinery's ``__new__`` on the class and keep the class body's own as
``_new_member_``. Without this getter, ``:special-members: __new__`` documents
the machinery's constructor rather than the one a registry row is built with
(:issue:`1940`).

Args:
obj: The enumeration class autodoc reads from.
name: Attribute name.
*defargs: Default, as for :func:`getattr`.

Returns:
The class body's own ``__new__`` for ``name == '__new__'`` when there is one,
and otherwise what autodoc's own attribute getter answers.

"""
if name == '__new__':
own = vars(obj).get('_new_member_')
func = getattr(own, '__func__', own)
if getattr(func, '__qualname__', None) == f'{obj.__qualname__}.__new__':
return func
return safe_getattr(obj, name, *defargs)


# NB: handlers used to sit here -- ``remove_module_docstring``,
# ``process_docstring``, ``process_fields`` and ``source_read`` -- each with its
# ``app.connect`` call commented out in ``setup`` below, so none of them had run
Expand Down Expand Up @@ -1137,3 +1164,6 @@ def setup(app: 'Sphinx') -> None:
# connects at the default 500.
app.connect('autodoc-process-docstring', save_enum_member_docstring, priority=400)
app.connect('autodoc-process-docstring', restore_enum_member_docstring, priority=600)
# NB: ``aenum``'s metaclass derives from the standard library's, and autodoc
# picks a getter by :func:`isinstance` on the class it reads, so one covers both.
app.add_autodoc_attrgetter(enum.EnumMeta, read_own_enum_new)
3 changes: 2 additions & 1 deletion docs/source/contributing/conventions/documentation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ Format and Mechanics
use them, which do not re-export them (:issue:`1744`).
* **A verbatim upstream port is exempt from these conventions.**
:ref:`sentinel-convention` already carries the case that settled it, the
``cached_property`` backport in :file:`pcapkit/utilities/compat.py`: the value of a
:class:`~pcapkit.utilities.compat.cached_property` backport in
:file:`pcapkit/utilities/compat.py`: the value of a
vendored copy is that it can still be diffed against upstream, and a house-style
rewrite destroys that in exchange for nothing a reader ever sees.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ Extension-Header Base Classes
Every IPv6 extension header in this package subclasses
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`. Some name a **second** base
as well, and which ones do is a ruling rather than an accident. The owner ruled, in
review of the rename that made ``IPv6_Ext`` the shared base
(:issue:`917`), that a header usable
*only* as an extension header inherits ``IPv6_Ext`` and nothing else -- ``IPv6_Frag``
being the example -- while one that is usable as a standalone protocol in its own
right inherits both ``IPv6_Ext`` and ``Internet`` (or ``IPsec``), as ``ESP`` does.
review of the rename that made :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` the
shared base (:issue:`917`), that a header usable *only* as an extension header inherits
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` and nothing else --
:class:`~pcapkit.protocols.internet.ipv6_frag.IPv6_Frag` being the example -- while one
that is usable as a standalone protocol in its own right inherits both
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` and
:class:`~pcapkit.protocols.internet.internet.Internet` (or
:class:`~pcapkit.protocols.internet.ipsec.IPsec`), as
:class:`~pcapkit.protocols.internet.esp.ESP` does.

The family as it stands:

Expand All @@ -26,21 +30,23 @@ The family as it stands:
:class:`~pcapkit.protocols.internet.ipv6_frag.IPv6_Frag`,
:class:`~pcapkit.protocols.internet.ipv6_opts.IPv6_Opts`,
:class:`~pcapkit.protocols.internet.mh.MH`
- ``IPv6_Ext``
- :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`
- extension-header only
* - :class:`~pcapkit.protocols.internet.ah.AH`,
:class:`~pcapkit.protocols.internet.esp.ESP`
- ``IPsec``, ``IPv6_Ext``
- :class:`~pcapkit.protocols.internet.ipsec.IPsec`,
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`
- **also standalone**
* - :class:`~pcapkit.protocols.internet.hip.HIP`
- ``IPv6_Ext``, ``Internet``
- :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`,
:class:`~pcapkit.protocols.internet.internet.Internet`
- **also standalone**

:class:`~pcapkit.protocols.internet.ipsec.IPsec` is itself an
:class:`~pcapkit.protocols.internet.internet.Internet` subclass, which is the
parenthetical ``IPsec`` alternative in the ruling: naming it satisfies the
convention, and it is the right second base for a header whose standalone form is an
IPsec one.
parenthetical :class:`~pcapkit.protocols.internet.ipsec.IPsec` alternative in the ruling:
naming it satisfies the convention, and it is the right second base for a header whose
standalone form is an IPsec one.

The Shared-Registry Trap
~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -75,7 +81,7 @@ The IPv4-Payload Test
So the census is read out of the specifications, on the owner's instruction: work
through the RFCs to establish, for each defined IPv6 extension header, whether it is
extension-header-only or a standalone protocol as well, and decide from that whether it
inherits ``IPv6_Ext`` alone or names additional bases.
inherits :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` alone or names additional bases.

And the limb that decides is **whether a primary source shows the header carried
directly as an IPv4 payload**:
Expand Down Expand Up @@ -108,17 +114,19 @@ class for it, so nothing implements the classification, but a future one inherit
whether or not it can appear under IPv4 -- was put to the owner explicitly in
review of :issue:`917` and not
taken, so MH and ``Shim6`` stay extension-only. A header that is a protocol in its
own right but structurally cannot be an IPv4 payload names ``IPv6_Ext`` alone.
own right but structurally cannot be an IPv4 payload names
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` alone.

Explicit Base Declarations
~~~~~~~~~~~~~~~~~~~~~~~~~~

Name the second base **explicitly**, even where it is already in the MRO.
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` derives
:class:`~pcapkit.protocols.internet.internet.Internet`, so every extension header
reaches ``Internet`` transitively and an ``__mro__`` check cannot tell the two groups
apart. The declaration is the only place the classification exists, which is why
``tests/protocols/internet/test_ipv6_ext_unit.py`` pins it against ``__bases__``:
reaches :class:`~pcapkit.protocols.internet.internet.Internet` transitively and an
``__mro__`` check cannot tell the two groups apart. The declaration is the only place the
classification exists, which is why ``tests/protocols/internet/test_ipv6_ext_unit.py``
pins it against ``__bases__``:

.. code-block:: python

Expand Down Expand Up @@ -168,12 +176,14 @@ inside the *encrypted* trailer, so with no key material there is nothing to cont
on: ESP's own data model reports ``next`` as :obj:`None`, and
:meth:`IPv6._decode_next_layer <pcapkit.protocols.internet.ipv6.IPv6._decode_next_layer>`
ends the ordinary way one iteration later. So ESP is absent from
``IPv6.__generic_ext_codes__`` -- not because it lacks a parser, which it has, but
because it cannot hand the walk a successor.
:attr:`IPv6.__generic_ext_codes__ <pcapkit.protocols.internet.ipv6.IPv6.__generic_ext_codes__>`
-- not because it lacks a parser, which it has, but because it cannot hand the walk a
successor.

The full reasoning, including how ESP's terminal case differs from 253/254's, is
written where the code is and is deliberately not restated at length here: see the
``#:`` comment on ``IPv6.__generic_ext_codes__``
``#:`` comment on
:attr:`IPv6.__generic_ext_codes__ <pcapkit.protocols.internet.ipv6.IPv6.__generic_ext_codes__>`
(:file:`pcapkit/protocols/internet/ipv6.py`) and the module docstring of
:mod:`pcapkit.protocols.internet.ipv6_ext`.

Expand Down
91 changes: 54 additions & 37 deletions docs/source/contributing/conventions/mint-criterion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ a ``_missing_`` override, which decides what happens when a value has no member.
MRO, so they carry no declared-but-unassigned range to resolve. The rest define one
themselves, except the transport subclasses of
:class:`~pcapkit.const.reg.apptype.apptype.AppType`, which inherit
``AppType._missing_`` (:file:`pcapkit/const/reg/apptype/apptype.py`). The
behaviours below follow from it, and which one a given range gets is a **design decision, not
a style preference**:
:meth:`AppType._missing_ <pcapkit.const.reg.apptype.apptype.AppType._missing_>`
(:file:`pcapkit/const/reg/apptype/apptype.py`). The behaviours below follow from it, and
which one a given range gets is a **design decision, not a style preference**:

``extend_enum(cls, name, value)`` -- *mint*
Creates a real, permanent member on the class. It is installed in
Expand All @@ -35,31 +35,47 @@ Measured against the tree, by class:

* **Mints**: :class:`~pcapkit.const.mh.cga_type.CGAType` alone, whose values are 128-bit
CGA extension tags rather than an IANA-style range of codes.
* **Unmint**: the bulk of the registries. The transport subclasses (``TCP``, ``UDP``,
``SCTP``, ``DCCP``) inherit the method; every other registry here calls
``_unregistered_member`` itself, though ``AppType`` never gets there: it holds no
* **Unmint**: the bulk of the registries. The transport subclasses
(:class:`~pcapkit.const.reg.apptype.tcp.TCP`,
:class:`~pcapkit.const.reg.apptype.udp.UDP`,
:class:`~pcapkit.const.reg.apptype.sctp.SCTP`,
:class:`~pcapkit.const.reg.apptype.dccp.DCCP`) inherit the method; every other registry
here calls ``_unregistered_member`` itself, though
:class:`~pcapkit.const.reg.apptype.apptype.AppType` never gets there: it holds no
members and raises for every value through its ``cls.__registry__ is None`` guard.

**Some of the rest can never reach the call either**, because every range they declare is
already covered by their own members -- ``ToSDelay`` declares ``0..1`` and has members
``[0, 1]``, so its ``_unregistered_member`` call is dead code. Those found are
``ipv4.option_class``, ``ipv4.tos_del``, ``ipv4.tos_ecn``, ``ipv4.tos_pre``,
``ipv4.tos_rel``, ``ipv4.tos_thr``, ``ipv6.option_action``, ``ipv6.seed_id``,
``ipv6.smf_dpd_mode``, ``l2tp.type``, ``mh.dhcp_support_mode`` and
``vlan.priority_level``. The ``str``-valued ``ftp.command.Command``,
``ftp.command.FEATCode`` and ``http.method.Method`` were not probed, so that list is a
floor.
already covered by their own members -- :class:`~pcapkit.const.ipv4.tos_del.ToSDelay`
declares ``0..1`` and has members ``[0, 1]``, so its ``_unregistered_member`` call is
dead code. Those found are :mod:`ipv4.option_class <pcapkit.const.ipv4.option_class>`,
:mod:`ipv4.tos_del <pcapkit.const.ipv4.tos_del>`,
:mod:`ipv4.tos_ecn <pcapkit.const.ipv4.tos_ecn>`,
:mod:`ipv4.tos_pre <pcapkit.const.ipv4.tos_pre>`,
:mod:`ipv4.tos_rel <pcapkit.const.ipv4.tos_rel>`,
:mod:`ipv4.tos_thr <pcapkit.const.ipv4.tos_thr>`,
:mod:`ipv6.option_action <pcapkit.const.ipv6.option_action>`,
:mod:`ipv6.seed_id <pcapkit.const.ipv6.seed_id>`,
:mod:`ipv6.smf_dpd_mode <pcapkit.const.ipv6.smf_dpd_mode>`,
:mod:`l2tp.type <pcapkit.const.l2tp.type>`,
:mod:`mh.dhcp_support_mode <pcapkit.const.mh.dhcp_support_mode>` and
:mod:`vlan.priority_level <pcapkit.const.vlan.priority_level>`. The ``str``-valued
:class:`ftp.command.Command <pcapkit.const.ftp.command.Command>`,
:class:`ftp.command.FEATCode <pcapkit.const.ftp.command.FEATCode>` and
:class:`http.method.Method <pcapkit.const.http.method.Method>` were not probed, so that
list is a floor.
* **Cache in the value table only**: :class:`~pcapkit.const.tcp.flags.Flags` and the
Mobility Header flag registries, such as
:class:`~pcapkit.const.mh.binding_ack_flag.BindingACKFlag`. They range-check and hand
back to ``super()._missing_``, which for an ``aenum.IntFlag`` reaches
``_create_pseudo_member_`` and ends in
``cls._value2member_map_.setdefault(value, pseudo_member)``.
* **Raise for any unknown value**: ``AppType``, ``ExtensionHeader``, ``TLSKeyLabel``
and :class:`~pcapkit.const.hip.transport.Transport`. ``Transport`` raises
from its own range check: its declared range ``0..3`` is exactly its member set, so
no unassigned value is left for ``super()._missing_`` (the line after the check is
unreachable).
* **Raise for any unknown value**: :class:`~pcapkit.const.reg.apptype.apptype.AppType`,
:class:`~pcapkit.const.ipv6.extension_header.ExtensionHeader`,
:class:`~pcapkit.const.pcapng.tls_key_label.TLSKeyLabel` and
:class:`~pcapkit.const.hip.transport.Transport`.
:class:`~pcapkit.const.hip.transport.Transport` raises from its own range check: its
declared range ``0..3`` is exactly its member set, so no unassigned value is left for
``super()._missing_`` (the line after the check is unreachable).

Note that these are |aenum|_ classes: ``issubclass(Flags, enum.IntFlag)`` is ``False``
against the standard library and ``True`` against ``aenum.IntFlag``, so a probe against
Expand All @@ -68,10 +84,10 @@ against the standard library and ``True`` against ``aenum.IntFlag``, so a probe
The mint criterion below therefore applies only to the registries that unmint, where it
decides the *name* an unregistered member carries -- the registrar's own label suffixed
with the individual code, or the bare block label on its own -- not whether it
registers. In the others it never runs: ``CGAType`` names its tag from the value,
the flag registries compose a name from their members' bits, and those that
raise name nothing. The crawlers that generate the ranges record that narrowing in
their own comments: :file:`pcapkit/vendor/reg/ethertype.py`'s ``UNASSIGNED_ROW_NAMES``
registers. In the others it never runs: :class:`~pcapkit.const.mh.cga_type.CGAType` names
its tag from the value, the flag registries compose a name from their members' bits, and
those that raise name nothing. The crawlers that generate the ranges record that narrowing
in their own comments: :file:`pcapkit/vendor/reg/ethertype.py`'s ``UNASSIGNED_ROW_NAMES``
and :file:`pcapkit/vendor/ipx/socket.py`'s ``UNASSIGNED_RANGE_NAMES``.

.. mermaid::
Expand All @@ -96,10 +112,9 @@ The test, paraphrased from the maintainer's ruling: does the upstream registry t
the label as the final, concrete assigned name, or only as a notation for a human
reading the table?

Settled in review of the ``Socket._missing_`` branch-order fix
(:issue:`841`) and reaffirmed
on :issue:`775` as a core concept of
the ruling.
Settled in review of the
:meth:`Socket._missing_ <pcapkit.const.ipx.socket.Socket._missing_>` branch-order fix
(:issue:`841`) and reaffirmed on :issue:`775` as a core concept of the ruling.

So the question to ask of a range is **what the upstream registry actually did**, not
what the generated code happens to look like:
Expand Down Expand Up @@ -146,10 +161,10 @@ Suffixed Company Names

The ethertype case looks like an exception to the rule and is not. The maintainer's
reasoning, settled on :issue:`775` after
being raised in review of the same ``Socket._missing_`` fix
(:issue:`841`): a proprietary protocol
will never have a public name, so the company name is what serves that purpose in its
place.
being raised in review of the same
:meth:`Socket._missing_ <pcapkit.const.ipx.socket.Socket._missing_>` fix (:issue:`841`): a
proprietary protocol will never have a public name, so the company name is what serves
that purpose in its place.

So the company name is not a note *about* the code -- it is the best name that will ever
exist *for* it, which makes it the final concrete assigned name under the test above.
Expand Down Expand Up @@ -187,12 +202,14 @@ only ``ast.Name`` nodes finds every mint and no unmint at all:

.. note::

The walk reads each file's own ``_missing_``, so it reports ``AppType`` as an
unmint although it raises, prints nothing for the flag registries and
``Transport`` (they call neither function) nor for ``ExtensionHeader`` and
``TLSKeyLabel`` (they define no ``_missing_``), and does not see the
transport subclasses, which inherit theirs. Reconcile it against a runtime probe
for the split in the list above.
The walk reads each file's own ``_missing_``, so it reports
:class:`~pcapkit.const.reg.apptype.apptype.AppType` as an unmint although it raises,
prints nothing for the flag registries and
:class:`~pcapkit.const.hip.transport.Transport` (they call neither function) nor for
:class:`~pcapkit.const.ipv6.extension_header.ExtensionHeader` and
:class:`~pcapkit.const.pcapng.tls_key_label.TLSKeyLabel` (they define no
``_missing_``), and does not see the transport subclasses, which inherit theirs.
Reconcile it against a runtime probe for the split in the list above.

.. warning::

Expand Down
10 changes: 5 additions & 5 deletions docs/source/contributing/conventions/process.rst
Original file line number Diff line number Diff line change
Expand Up @@ -296,13 +296,13 @@ carrying it are that kind:
:issue:`759` raising one where a
single-bit lookup used to return a member;
* a public attribute's meaning --
:issue:`618` swapping ``Frame.len``
and ``Frame.cap_len`` between the PCAP and PCAP-NG readers;
:issue:`618` swapping :attr:`Frame.len <pcapkit.protocols.data.misc.pcap.frame.Frame.len>`
and :attr:`Frame.cap_len <pcapkit.protocols.data.misc.pcap.frame.Frame.cap_len>`
between the PCAP and PCAP-NG readers;
* a signature or a name a caller writes --
:issue:`806` retyping
``AppType.proto`` and giving ``register_apptype`` varargs,
:issue:`778` enforcing ``@final``
at runtime;
:attr:`AppType.proto <pcapkit.const.reg.apptype.apptype.AppType.proto>` and giving
``register_apptype`` varargs, :issue:`778` enforcing ``@final`` at runtime;
* a path a caller or a script depends on -- the change that named the examples
directories apart.

Expand Down
Loading
Loading