From d1bb31e8c6025540bde1ffd000c97886e4bade47 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Mon, 19 Oct 2026 13:20:59 -0400 Subject: [PATCH 1/2] docs: link library-internal references and document the missing public classes (#1940) - Convert library-internal names written as double-backtick literals in rendered prose and docstrings to Python-domain roles; literals that mean a concept, a layer, a wire field, an extra or a historical name stay literals - Document CommandType, ConformanceRequirement, ResponseKind, GroupingInformation, the HTTP/2 Settings data model, OptionEnumField and OptionAreaField - Give every private helper and special method that rendered text names an entry, with a docstring where it had none (const docstrings in their vendor templates), and repoint or demote the roles those docstrings carry - Read an enumeration's own __new__ in autodoc, so :special-members: __new__ documents the registry row constructor rather than the enum machinery's - Add a project test pinning the entries, the helpers and a curated set of the converted references; update the tests that pinned the old literals Closes #1940 --- docs/source/conf.py | 31 +- .../conventions/documentation.rst | 3 +- .../extension-header-subclassing.rst | 48 ++- .../conventions/mint-criterion.rst | 91 +++-- .../contributing/conventions/process.rst | 10 +- .../conventions/protocol-layer-placement.rst | 45 ++- .../conventions/registry-protocol.rst | 209 ++++++----- .../conventions/sentinel-convention.rst | 25 +- docs/source/contributing/pep.rst | 60 ++-- docs/source/contributing/releasing.rst | 6 +- docs/source/pcapkit/const/ftp.rst | 44 ++- docs/source/pcapkit/const/http.rst | 27 +- docs/source/pcapkit/const/index.rst | 6 +- docs/source/pcapkit/const/pcapng.rst | 6 +- docs/source/pcapkit/const/reg.rst | 1 + docs/source/pcapkit/corekit/enum.rst | 5 +- docs/source/pcapkit/corekit/fields/field.rst | 3 + .../pcapkit/corekit/fields/ipaddress.rst | 6 + .../source/pcapkit/corekit/fields/numbers.rst | 8 + docs/source/pcapkit/corekit/infoclass.rst | 18 + docs/source/pcapkit/corekit/io.rst | 4 + docs/source/pcapkit/corekit/multidict.rst | 4 + docs/source/pcapkit/corekit/packet.rst | 2 + docs/source/pcapkit/corekit/sentinels.rst | 8 + docs/source/pcapkit/dumpkit/pcap.rst | 5 + .../pcapkit/foundation/engines/3rdparty.rst | 11 +- .../pcapkit/foundation/engines/index.rst | 40 ++- docs/source/pcapkit/foundation/extraction.rst | 7 + .../pcapkit/foundation/traceflow/index.rst | 4 + .../pcapkit/foundation/traceflow/tcp.rst | 4 + .../pcapkit/protocols/application/http.rst | 2 + .../pcapkit/protocols/application/httpv2.rst | 6 + .../pcapkit/protocols/application/ngap.rst | 10 + .../pcapkit/protocols/application/ospf.rst | 8 +- .../pcapkit/protocols/application/rarp.rst | 2 +- docs/source/pcapkit/protocols/index.rst | 2 + .../source/pcapkit/protocols/internet/esp.rst | 5 + .../pcapkit/protocols/internet/ipv6.rst | 2 + docs/source/pcapkit/protocols/link/arp.rst | 5 + .../pcapkit/protocols/link/ethernet.rst | 2 + docs/source/pcapkit/protocols/link/index.rst | 4 +- docs/source/pcapkit/protocols/misc/pcap.rst | 2 + docs/source/pcapkit/protocols/misc/pcapng.rst | 26 ++ docs/source/pcapkit/protocols/protocol.rst | 8 + docs/source/pcapkit/toolkit/3rdparty.rst | 37 ++ docs/source/pcapkit/utilities/exceptions.rst | 20 +- docs/source/pcapkit/utilities/index.rst | 20 ++ docs/source/pcapkit/vendor/default.rst | 4 + docs/source/pcapkit/vendor/ftp.rst | 2 +- docs/source/pcapkit/vendor/ngap.rst | 2 +- docs/source/pcapkit/vendor/reg.rst | 2 +- pcapkit/const/ftp/command.py | 15 +- pcapkit/const/ftp/return_code.py | 9 + pcapkit/const/http/method.py | 8 + pcapkit/const/http/status_code.py | 8 + pcapkit/corekit/enum.py | 58 ++-- pcapkit/corekit/fields/collections.py | 4 +- pcapkit/corekit/fields/field.py | 9 +- pcapkit/corekit/fields/ipaddress.py | 16 +- pcapkit/corekit/fields/misc.py | 2 +- pcapkit/corekit/fields/numbers.py | 81 ++--- pcapkit/corekit/infoclass.py | 35 ++ pcapkit/corekit/packet.py | 12 + pcapkit/corekit/sentinels.py | 4 +- pcapkit/foundation/engines/pcapng.py | 12 +- pcapkit/foundation/engines/pypcap.py | 10 +- pcapkit/foundation/engines/pypcapfile.py | 6 + pcapkit/foundation/extraction.py | 25 +- pcapkit/foundation/reassembly/data/tcp.py | 2 +- pcapkit/foundation/registry/protocols.py | 10 +- pcapkit/foundation/traceflow/traceflow.py | 2 +- pcapkit/interface/misc.py | 2 +- pcapkit/protocols/application/http.py | 29 +- pcapkit/protocols/application/httpv2.py | 5 +- pcapkit/protocols/application/ngap.py | 6 +- pcapkit/protocols/application/ospf.py | 13 +- pcapkit/protocols/application/rarp.py | 2 +- pcapkit/protocols/data/application/httpv2.py | 6 +- pcapkit/protocols/internet/ah.py | 2 +- pcapkit/protocols/internet/hip.py | 6 +- pcapkit/protocols/internet/ipv6.py | 25 +- pcapkit/protocols/internet/ipv6_ext.py | 14 +- pcapkit/protocols/link/arp.py | 12 +- pcapkit/protocols/misc/pcapng.py | 1 + pcapkit/protocols/protocol.py | 17 +- .../protocols/schema/internet/ipv6_route.py | 11 +- pcapkit/protocols/schema/misc/pcapng.py | 34 +- pcapkit/protocols/schema/schema.py | 4 +- pcapkit/protocols/schema/transport/tcp.py | 17 +- pcapkit/utilities/config.py | 6 +- pcapkit/utilities/exceptions.py | 16 +- pcapkit/vendor/default.py | 6 +- pcapkit/vendor/ftp/command.py | 17 +- pcapkit/vendor/ftp/return_code.py | 9 + pcapkit/vendor/http/method.py | 8 + pcapkit/vendor/http/status_code.py | 8 + pcapkit/vendor/ngap/procedure_code.py | 4 +- pcapkit/vendor/reg/ethertype.py | 2 +- tests/const/test_const_enum_builtin_parity.py | 3 +- tests/project/test_conventions_doc_claims.py | 6 +- .../test_docs_internal_refs_1940_unit.py | 325 ++++++++++++++++++ .../test_docs_unresolvable_roles_1919_unit.py | 8 - 102 files changed, 1335 insertions(+), 529 deletions(-) create mode 100644 tests/project/test_docs_internal_refs_1940_unit.py diff --git a/docs/source/conf.py b/docs/source/conf.py index d85723d988..545db3ab42 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -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 @@ -1088,6 +1088,32 @@ 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_``, so ``:special-members: __new__`` documented 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 @@ -1137,3 +1163,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) diff --git a/docs/source/contributing/conventions/documentation.rst b/docs/source/contributing/conventions/documentation.rst index a084a80d8a..c906695c17 100644 --- a/docs/source/contributing/conventions/documentation.rst +++ b/docs/source/contributing/conventions/documentation.rst @@ -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. diff --git a/docs/source/contributing/conventions/extension-header-subclassing.rst b/docs/source/contributing/conventions/extension-header-subclassing.rst index 4293943990..a9476281ea 100644 --- a/docs/source/contributing/conventions/extension-header-subclassing.rst +++ b/docs/source/contributing/conventions/extension-header-subclassing.rst @@ -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: @@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -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**: @@ -108,7 +114,8 @@ 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -116,9 +123,10 @@ 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 @@ -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 ` 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__ ` +-- 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__ ` (:file:`pcapkit/protocols/internet/ipv6.py`) and the module docstring of :mod:`pcapkit.protocols.internet.ipv6_ext`. diff --git a/docs/source/contributing/conventions/mint-criterion.rst b/docs/source/contributing/conventions/mint-criterion.rst index fe5d8a5833..a0ae938b04 100644 --- a/docs/source/contributing/conventions/mint-criterion.rst +++ b/docs/source/contributing/conventions/mint-criterion.rst @@ -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_ ` +(: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 @@ -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 `, + :mod:`ipv4.tos_del `, + :mod:`ipv4.tos_ecn `, + :mod:`ipv4.tos_pre `, + :mod:`ipv4.tos_rel `, + :mod:`ipv4.tos_thr `, + :mod:`ipv6.option_action `, + :mod:`ipv6.seed_id `, + :mod:`ipv6.smf_dpd_mode `, + :mod:`l2tp.type `, + :mod:`mh.dhcp_support_mode ` and + :mod:`vlan.priority_level `. The ``str``-valued + :class:`ftp.command.Command `, + :class:`ftp.command.FEATCode ` and + :class:`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 @@ -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:: @@ -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_ ` 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: @@ -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_ ` 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. @@ -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:: diff --git a/docs/source/contributing/conventions/process.rst b/docs/source/contributing/conventions/process.rst index b68e2909d3..d25c64cc81 100644 --- a/docs/source/contributing/conventions/process.rst +++ b/docs/source/contributing/conventions/process.rst @@ -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 ` + and :attr:`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 ` 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. diff --git a/docs/source/contributing/conventions/protocol-layer-placement.rst b/docs/source/contributing/conventions/protocol-layer-placement.rst index 31a795d2b6..a4463886fe 100644 --- a/docs/source/contributing/conventions/protocol-layer-placement.rst +++ b/docs/source/contributing/conventions/protocol-layer-placement.rst @@ -168,11 +168,11 @@ nothing about which registry reaches it, and the move did not touch any dispatch - Dispatched from - Subpackage * - :class:`~pcapkit.protocols.application.ospf.OSPF` - - ``Internet.__proto__`` by + - :attr:`Internet.__proto__ ` by :attr:`~pcapkit.const.reg.transtype.TransType.OSPFIGP` - :mod:`~pcapkit.protocols.application` * - :class:`~pcapkit.protocols.application.rarp.RARP` - - ``Link.__proto__`` by EtherType + - :attr:`Link.__proto__ ` by EtherType - :mod:`~pcapkit.protocols.application` Nothing else needs overriding. :class:`~pcapkit.protocols.application.application.Application` @@ -186,22 +186,31 @@ protocol number is refused. :class:`~pcapkit.protocols.application.application.Application` **is not name-for-name.** Counting the names in each class's own namespace, whether new or overriding :class:`~pcapkit.protocols.protocol.ProtocolBase`'s, and including those -``ProtocolBase.__init_subclass__`` sets there, ``Link`` has -``__data__``, ``__layer__``, ``__post_init__``, ``__proto__``, -``__schema__``, ``_read_protos``, ``_shipped_proto``, ``from_data``, ``register`` and -``layer``, and ``Application`` has what they share (``__data__``, ``__layer__``, -``__post_init__``, ``__schema__``, ``from_data``, ``layer``) plus ``__index__``, -``_decode_next_layer`` and ``_import_next_layer``. -``Link`` overrides names that ``Application`` does not -- ``__proto__``, -``register``, ``_read_protos`` and ``_shipped_proto``, the last set because ``Link`` -defines its own ``__proto__`` -- but each also exists on ``ProtocolBase``, so -the strict difference ``Link`` minus ``Application`` minus ``ProtocolBase`` is **empty** -and every one of them still resolves on ``OSPF``. What changes is which registry it -resolves *to*: ``OSPF.__proto__`` is ``ProtocolBase.__proto__`` rather than -``Link.__proto__``, so OSPF does not see Link's EtherType entries. That is inert -- -nothing reaches OSPF by EtherType, and a ``-1`` lookup misses in both registries alike -and resolves to :class:`~pcapkit.protocols.misc.raw.Raw`, inserting nothing on the miss. -``RARP`` keeps ``Link``'s through ``ARP``. +:meth:`ProtocolBase.__init_subclass__ ` +sets there, :class:`~pcapkit.protocols.link.link.Link` has ``__data__``, ``__layer__``, +``__post_init__``, ``__proto__``, ``__schema__``, ``_read_protos``, ``_shipped_proto``, +``from_data``, ``register`` and ``layer``, and +:class:`~pcapkit.protocols.application.application.Application` has what they share +(``__data__``, ``__layer__``, ``__post_init__``, ``__schema__``, ``from_data``, ``layer``) +plus ``__index__``, ``_decode_next_layer`` and ``_import_next_layer``. +:class:`~pcapkit.protocols.link.link.Link` overrides names that +:class:`~pcapkit.protocols.application.application.Application` does not -- ``__proto__``, +``register``, ``_read_protos`` and ``_shipped_proto``, the last set because +:class:`~pcapkit.protocols.link.link.Link` defines its own ``__proto__`` -- but each also +exists on :class:`~pcapkit.protocols.protocol.ProtocolBase`, so the strict difference +:class:`~pcapkit.protocols.link.link.Link` minus +:class:`~pcapkit.protocols.application.application.Application` minus +:class:`~pcapkit.protocols.protocol.ProtocolBase` is **empty** and every one of them still +resolves on :class:`~pcapkit.protocols.application.ospf.OSPF`. What changes is which +registry it resolves *to*: ``OSPF.__proto__`` is +:attr:`ProtocolBase.__proto__ ` rather +than :attr:`Link.__proto__ `, so OSPF does not +see Link's EtherType entries. That is inert -- nothing reaches OSPF by EtherType, and a +``-1`` lookup misses in both registries alike and resolves to +:class:`~pcapkit.protocols.misc.raw.Raw`, inserting nothing on the miss. +:class:`~pcapkit.protocols.application.rarp.RARP` keeps +:class:`~pcapkit.protocols.link.link.Link`'s through +:class:`~pcapkit.protocols.link.arp.ARP`. Tie-Break Order ~~~~~~~~~~~~~~~ diff --git a/docs/source/contributing/conventions/registry-protocol.rst b/docs/source/contributing/conventions/registry-protocol.rst index 5e74e5d2a4..30b4a49f03 100644 --- a/docs/source/contributing/conventions/registry-protocol.rst +++ b/docs/source/contributing/conventions/registry-protocol.rst @@ -85,7 +85,8 @@ string-valued registries. :class:`~pcapkit.const.ipv6.extension_header.ExtensionHeader` have no ``_missing_`` to copy from, because their constructor refuses every value no member carries, so the first checks its member type and the second the 8-bit Next Header field the type is -sent in. The ``AppType`` transports inherit ``AppType``'s own. +sent in. The :class:`~pcapkit.const.reg.apptype.apptype.AppType` transports inherit +:class:`~pcapkit.const.reg.apptype.apptype.AppType`'s own. The check is the *range*, not everything ``_missing_`` decides. A value inside the range that matches neither a member nor an unassigned range -- ``CauseCode(0)``, say -- still @@ -163,11 +164,12 @@ Which branch a key takes, and what each miss ends in: Do not "improve" on the shape by making both misses report identically. Converting one into the other is exactly what :issue:`923` retired, and it was retired in several -places at once: ``TransportProtocol.get`` and ``Criticality.get`` had each turned the -base's :exc:`KeyError` into a :exc:`ValueError`, and -``FastBindingAcknowledgmentStatus.get`` raised -:exc:`~pcapkit.utilities.exceptions.EnumValueError` for a name miss, so that the two -ways of getting it wrong reported identically. +places at once: +:meth:`TransportProtocol.get ` +and ``Criticality.get`` had each turned the base's :exc:`KeyError` into a +:exc:`ValueError`, and ``FastBindingAcknowledgmentStatus.get`` raised +:exc:`~pcapkit.utilities.exceptions.EnumValueError` for a name miss, so that the two ways +of getting it wrong reported identically. One more thing is not visible from the exception class, and it is deliberate: whether a miss is **reported**. **Every miss** ``get`` **reports is quiet** -- by name @@ -180,8 +182,8 @@ installed, the interpreter's own unless an earlier loud error installed :mod:`pcapkit`'s. * **The name miss** is raised with ``quiet=True``. It is in-library control flow at - several call sites, and at ``Method.get`` it is part of a *successful* call -- - that override catches it in order to mint. + several call sites, and at :meth:`Method.get ` it + is part of a *successful* call -- that override catches it in order to mint. * **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 @@ -239,10 +241,10 @@ delegation **silently succeeds**, so a ``@staticmethod`` override cannot even be relied on to fail loudly. Measured, every case, rather than reasoned about. :meth:`~pcapkit.corekit.enum.EnumLookup.get` is itself a ``@classmethod``. The precedent is :issue:`903`, which made -``FEATCode.get`` a ``@classmethod def get(cls, key, default=NO_DEFAULT)`` ending in -``return super().get(key, default)``; -:issue:`908` followed it, which is -what turned ``Method.get`` into a classmethod. +:meth:`FEATCode.get ` a ``@classmethod def +get(cls, key, default=NO_DEFAULT)`` ending in ``return super().get(key, default)``; +:issue:`908` followed it, which is what turned +:meth:`Method.get ` into a classmethod. Callers cannot see the switch -- ``Method.get('X')`` binds identically either way -- so there is no compatibility argument for keeping the ``@staticmethod``. @@ -266,9 +268,9 @@ record, the :issue:`362` hazard, and outside development mode installs :mod:`pcapkit`'s exception hooks **process-wide**, so loudness is paid for by the whole library rather than by the override's own callers. The argument against changing them was that ``quiet=True`` exists on the base for a name miss -inside a *successful* call at ``Method.get`` and these two had no such caller; -uniformity beat it, because a per-class judgement about present callers cannot price -a process-wide effect. +inside a *successful* call at :meth:`Method.get ` +and these two had no such caller; uniformity beat it, because a per-class judgement about +present callers cannot price a process-wide effect. **A signature the base advertises has to be honoured, not suppressed.** Re-parenting onto :class:`~pcapkit.corekit.enum.EnumLookup` gave those same two classes an @@ -330,10 +332,10 @@ treats them as case-sensitive. And the reason a registry's spelling is never quietly normalised, from the same thread: an enumeration honours and keeps the original writing the registrar used, and case-insensitivity applies only to the selected registries where it makes logical sense --- ``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. +-- :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. 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 @@ -344,21 +346,19 @@ to make a lookup work -- which is what keeps ``R1_COUNTER = 129``, two IANA-registered HIP parameters differing only in case, both resolvable. -And a folding override carries **only** the fold. ``TransportProtocol.get`` is the -worked example: since -:issue:`923` it lowers ``key``, -forwards ``default`` verbatim and delegates to ``super().get()``, and that is all it -does. It used to convert the base's name-miss :exc:`KeyError` into a -:exc:`ValueError` as well, and :issue:`923`'s ruling retired that; the -:issue:`808` refusal to extend the -class at all is untouched by the retirement, since only the exception class moved. -``Criticality.get`` went further and was deleted: conversion was the *only* -thing it added over the base, so once that went there was nothing left for an -override to hold, and the class inherits -:meth:`~pcapkit.corekit.enum.EnumLookup.get` unchanged. **An override that would -now be empty 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. +And a folding override carries **only** the fold. +:meth:`TransportProtocol.get ` is +the worked example: since :issue:`923` it lowers ``key``, forwards ``default`` verbatim +and delegates to ``super().get()``, and that is all it does. It used to convert the base's +name-miss :exc:`KeyError` into a :exc:`ValueError` as well, and :issue:`923`'s ruling +retired that; the :issue:`808` refusal to extend the class at all is untouched by the +retirement, since only the exception class moved. ``Criticality.get`` went further and was +deleted: conversion was the *only* thing it added over the base, so once that went there +was nothing left for an override to hold, and the class inherits +:meth:`~pcapkit.corekit.enum.EnumLookup.get` unchanged. **An override that would now be +empty 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. The Lenient Criterion, in Two Limbs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -367,9 +367,10 @@ The ruling above leaves one question open, and :issue:`903` settled it: does a specification have to state a **comparison rule** for a registry to be treated case-insensitively, or does it also count when the authorities merely **disagree -about spelling**? The owner ruled for the lenient reading, with ``TransportProtocol`` -as the example given: upper and lower casings are used throughout the RFCs and IANA's -own data, and that mixed usage is itself an indication of case-insensitivity. +about spelling**? The owner ruled for the lenient reading, with +:class:`~pcapkit.const.reg.apptype.apptype.TransportProtocol` as the example given: upper +and lower casings are used throughout the RFCs and IANA's own data, and that mixed usage +is itself an indication of case-insensitivity. So the test a new registry has to pass has **two limbs**, and satisfying either one justifies case-insensitivity: @@ -400,9 +401,9 @@ The population it covers is every :class:`~pcapkit.corekit.enum.EnumRegistry` subclass, every one of them under :mod:`pcapkit.const` and each either :class:`int`-valued (the flag registries among them) or ``aenum.StrEnum``-valued, plus every non-registry enumeration a runtime walk over both the :mod:`enum` and -|aenum|_ flavours finds, nested classes included -- such as ``FrameType.Flags`` in -:mod:`pcapkit.protocols.schema.application.httpv2` and its concrete per-frame -subclasses. +|aenum|_ flavours finds, nested classes included -- such as +:class:`FrameType.Flags ` in +:mod:`pcapkit.protocols.schema.application.httpv2` and its concrete per-frame subclasses. **The** :class:`int`\ **-valued tier is case-sensitive throughout, and the criterion is vacuous on it rather than merely unmet.** A registry whose values are numbers has @@ -463,7 +464,7 @@ That leaves the classes with something to decide: key schedule"*, but those are a different document's secret names, not a second spelling of the log label.) - **case-sensitive** -- no override, conforms - * - ``TransportProtocol`` + * - :class:`~pcapkit.const.reg.apptype.apptype.TransportProtocol` - :rfc:`6335#section-8.1.1` - Nothing states a rule for the ``Transport Protocol`` field -- only *"limited to one or more of TCP, UDP, SCTP, and DCCP"*. Limb 2 carries it: the RFC and @@ -476,9 +477,13 @@ That leaves the classes with something to decide: - -- - Moot: its ``get`` takes a port number and refuses a non-:class:`int` outright, so there is no string to fold. It does inherit the row above through - ``_dispatch``, which resolves a ``proto`` string via ``TransportProtocol.get``. + ``_dispatch``, which resolves a ``proto`` string via + :meth:`TransportProtocol.get `. - **n/a** -- int-keyed - * - ``TCP``, ``UDP``, ``SCTP``, ``DCCP`` + * - :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` - :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 @@ -486,7 +491,8 @@ That leaves the classes with something to decide: spelling (``tcpmux``); the *value* is a composite that embeds it (``'tcpmux [1 - tcp]'``), not the bare name. - **unimplemented** -- no service-name lookup exists to fold; see below - * - ``CommandType``, ``ConformanceRequirement`` + * - :class:`~pcapkit.const.ftp.command.CommandType`, + :class:`~pcapkit.const.ftp.command.ConformanceRequirement` - :rfc:`959#section-4`, :rfc:`5797#section-2` - Limb 2 holds on measurement: the RFC and registry pages present the kind and conformance letters upper case (``A``/``P``/``S``, ``M``/``O``/``H``) while @@ -497,64 +503,78 @@ That leaves the classes with something to decide: * - The :mod:`~pcapkit.protocols.internet.mh` and :mod:`~pcapkit.protocols.application.ngap` helper enumerations - IANA Mobility Header registries, 3GPP TS 38.413 - - ``FastBindingAcknowledgmentStatus``, ``IPv6AddressPrefixCode``, - ``LMAAddressCode``, ``LocalizedRoutingStatus``, ``Criticality`` and ``PDUKind``. - **All but** ``PDUKind`` carry numeric codes, so the criterion is vacuous on them - exactly as for the :class:`int` tier above; ``PDUKind``'s are :class:`str` ASN.1 + - :class:`~pcapkit.protocols.internet.mh.FastBindingAcknowledgmentStatus`, + :class:`~pcapkit.protocols.internet.mh.IPv6AddressPrefixCode`, + :class:`~pcapkit.protocols.internet.mh.LMAAddressCode`, + :class:`~pcapkit.protocols.internet.mh.LocalizedRoutingStatus`, + :class:`~pcapkit.protocols.application.ngap.Criticality` and + :class:`~pcapkit.protocols.application.ngap.PDUKind`. **All but** + :class:`~pcapkit.protocols.application.ngap.PDUKind` carry numeric codes, so the + criterion is vacuous on them exactly as for the :class:`int` tier above; + :class:`~pcapkit.protocols.application.ngap.PDUKind`'s are :class:`str` ASN.1 identifiers from 3GPP TS 38.413, and ASN.1 identifiers are case-significant by - construction, so it lands the same way for a different reason. - **None** of them defines a ``get`` of its own. Some did when this - audit was taken -- - ``FastBindingAcknowledgmentStatus`` and ``IPv6AddressPrefixCode``, for - signature reasons (no ``default``, and an :class:`int`/:class:`str` dispatch) - rather than for case -- and both were deleted as redundant rather than widened, - on the ruling recorded in the section above and given in review of the widening - itself; each inherits ``get`` from - :class:`~pcapkit.corekit.enum.EnumLookup` unchanged. ``LMAAddressCode`` and - ``LocalizedRoutingStatus`` never carried a ``get`` at all, so they had no - string lookup to fold. ``Criticality`` had one when this audit was taken and no - longer does: - :issue:`877` re-parented it onto + construction, so it lands the same way for a different reason. **None** of them + defines a ``get`` of its own. Some did when this audit was taken -- + :class:`~pcapkit.protocols.internet.mh.FastBindingAcknowledgmentStatus` and + :class:`~pcapkit.protocols.internet.mh.IPv6AddressPrefixCode`, for signature + reasons (no ``default``, and an :class:`int`/:class:`str` dispatch) rather than for + case -- and both were deleted as redundant rather than widened, on the ruling + recorded in the section above and given in review of the widening itself; each + inherits ``get`` from :class:`~pcapkit.corekit.enum.EnumLookup` unchanged. + :class:`~pcapkit.protocols.internet.mh.LMAAddressCode` and + :class:`~pcapkit.protocols.internet.mh.LocalizedRoutingStatus` never carried a + ``get`` at all, so they had no string lookup to fold. + :class:`~pcapkit.protocols.application.ngap.Criticality` had one when this audit + was taken and no longer does: :issue:`877` re-parented it onto :class:`~pcapkit.corekit.enum.EnumLookup` and :issue:`923` retired the exception - conversion that was the override's only remaining job, so it inherits - ``get`` unchanged. + conversion that was the override's only remaining job, so it inherits ``get`` + unchanged. - **case-sensitive** -- conforms - * - ``WireGuardKeyLabel`` + * - :class:`~pcapkit.protocols.misc.pcapng.WireGuardKeyLabel` - ``draft-tuexen-opsawg-pcapng`` - The draft names the labels outright (*"The key type is one of - LOCAL_STATIC_PRIVATE_KEY, ..."*) and, as for ``OptionType`` above, never - discusses their case. Both authorities write them upper case. + LOCAL_STATIC_PRIVATE_KEY, ..."*) and, as for + :class:`~pcapkit.const.pcapng.option_type.OptionType` above, never discusses their + case. Both authorities write them upper case. - **case-sensitive** -- no override, conforms * - Every other non-registry enumeration - -- - Each is one of pcapkit's own discriminators and bit labels, with no registrar behind - them at all -- ``Completion``, ``ftp.Type``, ``httpv1.Type``, ``FinalisedState``, - ``ESPStatus``, ``PacketDirection``, ``PacketReception``, and the httpv2 - ``Flags``. + them at all -- :class:`~pcapkit.foundation.reassembly.data.data.Completion`, + :class:`ftp.Type `, + :class:`httpv1.Type `, + :class:`~pcapkit.corekit.infoclass.FinalisedState`, + :class:`~pcapkit.protocols.internet.esp.ESPStatus`, + :class:`~pcapkit.protocols.misc.pcapng.PacketDirection`, + :class:`~pcapkit.protocols.misc.pcapng.PacketReception`, and the httpv2 + :class:`~pcapkit.protocols.schema.application.httpv2.FrameType.Flags`. - **case-sensitive** -- nothing to cite, nothing to change Rows the audit deliberately left open rather than acting on, because each is wider than a case fix: -* **A service-name lookup on the** ``AppType`` **transport registries.** This is the - inverse of every other row: :rfc:`6335#section-5.1` *does* make service names - case-insensitive, and ``TCP``/``UDP``/``SCTP``/``DCCP`` take their member names from - that column -- but ``AppType.get`` refuses a non-:class:`int` key, so no service-name - lookup exists for the rule to apply to. Implementing one is new public API on a - registry of thousands of members where one name maps to many ports, which is a ``get_all`` - design question rather than a case fold. -* ``CommandType`` **and** ``ConformanceRequirement``. By parity with - ``TransportProtocol`` -- an :class:`int`-valued enumeration whose *names* are the - specification's own tokens -- the measured spelling disagreement above would make - these two case-insensitive. Nothing looks them up by string, though: the - crawler translates the CSV's lower-case letters to the upper-case member names at - generation time. Both classes inherit - :class:`~pcapkit.corekit.enum.EnumLookup`, since - :issue:`930` completed phase 2's - re-parenting, so a ``get`` exists on each, case-sensitive like the - base's own. Whether to fold case to match ``TransportProtocol``'s own override is a - design question for whoever writes the first string-keyed caller, not one this - audit settles. +* **A service-name lookup on the** :class:`~pcapkit.const.reg.apptype.apptype.AppType` + **transport registries.** This is the inverse of every other row: + :rfc:`6335#section-5.1` *does* make service names case-insensitive, and + :class:`~pcapkit.const.reg.apptype.tcp.TCP`, :class:`~pcapkit.const.reg.apptype.udp.UDP`, + :class:`~pcapkit.const.reg.apptype.sctp.SCTP` and + :class:`~pcapkit.const.reg.apptype.dccp.DCCP` take their member names from that column -- but + :meth:`AppType.get ` refuses a + non-:class:`int` key, so no service-name lookup exists for the rule to apply to. + Implementing one is new public API on a registry of thousands of members where one name + maps to many ports, which is a ``get_all`` design question rather than a case fold. +* :class:`~pcapkit.const.ftp.command.CommandType` **and** + :class:`~pcapkit.const.ftp.command.ConformanceRequirement`. By parity with + :class:`~pcapkit.const.reg.apptype.apptype.TransportProtocol` -- an :class:`int`-valued + enumeration whose *names* are the specification's own tokens -- the measured spelling + disagreement above would make these two case-insensitive. Nothing looks them up by + string, though: the crawler translates the CSV's lower-case letters to the upper-case + member names at generation time. Both classes inherit + :class:`~pcapkit.corekit.enum.EnumLookup`, since :issue:`930` completed phase 2's + re-parenting, so a ``get`` exists on each, case-sensitive like the base's own. Whether + to fold case to match :class:`~pcapkit.const.reg.apptype.apptype.TransportProtocol`'s + 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 @@ -595,14 +615,15 @@ rather than changing it. ``tests/corekit/test_enum_lookup_base_unit.py``'s own ``_Str``, a purpose-built closed set carrying ``angled = ''`` precisely so that the value fall-through can be measured on a class that defines no ``get``. - ``Command.get`` upper-cases its key + :meth:`Command.get ` upper-cases its key before matching, which makes it look as though the base were case-insensitive -- deliberately, since :rfc:`959#section-5` treats FTP command codes identically - regardless of case. ``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 - ``Method.GET`` -- it builds its own unregistered member, preserving the - caller's exact casing, the same way an unrecognised value always does. + 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. 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/docs/source/contributing/conventions/sentinel-convention.rst b/docs/source/contributing/conventions/sentinel-convention.rst index 277b389355..d721315414 100644 --- a/docs/source/contributing/conventions/sentinel-convention.rst +++ b/docs/source/contributing/conventions/sentinel-convention.rst @@ -24,16 +24,16 @@ derives from it mechanically -- title-case each underscore-separated word and ap - Type - Defined in * - ``NULL`` - - ``NullType`` + - :class:`~pcapkit.corekit.sentinels.NullType` - :mod:`pcapkit.corekit.sentinels` * - ``NO_VALUE`` - - ``NoValueType`` + - :class:`~pcapkit.corekit.sentinels.NoValueType` - :mod:`pcapkit.corekit.sentinels` * - ``NO_DEFAULT`` - - ``NoDefaultType`` + - :class:`~pcapkit.corekit.sentinels.NoDefaultType` - :mod:`pcapkit.corekit.sentinels` * - ``ABSENT`` - - ``AbsentType`` + - :class:`~pcapkit.corekit.sentinels.AbsentType` - :mod:`pcapkit.corekit.sentinels` GitHub issue :issue:`911`'s housing ruling -- one module for all of them -- is why the table @@ -47,7 +47,7 @@ Where a sentinel name already exists and already follows SCREAMING_SNAKE, keep i renaming a published sentinel again costs every caller for no further gain. ``ABSENT`` carries no leading underscore even though it is private -- it is read in -``_declared_keywords`` and discarded there, never leaving +:func:`~pcapkit.protocols.protocol._declared_keywords` and discarded there, never leaving :mod:`pcapkit.protocols.protocol`. The owner ruled on :issue:`937` that dropping the underscore is fine so long as the documentation states that the type and the object are private and not for public use, which is what this page and @@ -103,7 +103,8 @@ appears in a signature, in :func:`help` output and in a traceback. Compare what in an early draft of :mod:`pcapkit.corekit.enum` and was wrong. **Ported code is exempt.** ``_NOT_FOUND = object()`` at -:file:`pcapkit/utilities/compat.py`, line 73, sits inside the ``cached_property`` +:file:`pcapkit/utilities/compat.py`, line 73, sits inside the +:class:`~pcapkit.utilities.compat.cached_property` backport taken for interpreters below 3.8, which tracks CPython's own :mod:`functools` implementation down to that name. It is **not** to be converted: the value of a vendored backport is that it can still be diffed against upstream, and a @@ -113,8 +114,8 @@ ever sees. The rule above is for sentinels this package writes itself. Per-Sentinel Dunder Methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Most of the protocol is shared and lives on ``Sentinel``. Where the sentinels differ, -the differences are **needs, not inconsistencies**: +Most of the protocol is shared and lives on :class:`~pcapkit.corekit.sentinels.Sentinel`. +Where the sentinels differ, the differences are **needs, not inconsistencies**: ``__new__`` returning a cached instance Every sentinel has it, from the base, and returns the instance the metaclass made as @@ -163,8 +164,8 @@ the differences are **needs, not inconsistencies**: :exc:`TypeError` as ``type(None).x = 1`` does, so neither ``_name`` nor the cached instance can be swapped out. It also refuses to let a concrete sentinel type be subclassed, by a ``class`` statement or by ``register``, as ``type(None)`` cannot be; - ``Sentinel`` itself stays subclassable, being abstract. The owner asked for this in - review of the change for :issue:`1842`: a sentinel's repr and its other behaviour - follow Python's own singletons, :obj:`None`, :obj:`Ellipsis` and - :obj:`NotImplemented`. + :class:`~pcapkit.corekit.sentinels.Sentinel` itself stays subclassable, being abstract. + The owner asked for this in review of the change for :issue:`1842`: a sentinel's repr + and its other behaviour follow Python's own singletons, :obj:`None`, :obj:`Ellipsis` + and :obj:`NotImplemented`. diff --git a/docs/source/contributing/pep.rst b/docs/source/contributing/pep.rst index f381e29827..519ae33985 100644 --- a/docs/source/contributing/pep.rst +++ b/docs/source/contributing/pep.rst @@ -181,7 +181,7 @@ What is left, and why: each carries: ``protocol`` on option 54, ``igmp_type`` and ``mld_type`` on 56 and 57, and ``code`` on 60. Not the ``M`` flag on option 54, which selects the routing mode rather than the record format [:rfc:`7028#section-5.1.2`]. - ``Raw`` is already what an unregistered dispatch falls back + :class:`~pcapkit.protocols.misc.raw.Raw` is already what an unregistered dispatch falls back to everywhere else in the package, so this makes the mobility header consistent with the rest rather than inventing a convention; and once a dissector for one of these formats exists, registering it needs no change at the option site. @@ -200,8 +200,9 @@ What is left, and why: only ever called at a layer boundary and appends to the frame's protocol chain. An MLD address record inside a mobility option did not follow MH on the wire, so putting it in that chain would make ``layer=`` and ``protocol=`` limits - behave wrongly. Changing the parsed shape from :obj:`bytes` to ``Raw`` also - changes what existing captures dump to, so it is its own change. + behave wrongly. Changing the parsed shape from :obj:`bytes` to + :class:`~pcapkit.protocols.misc.raw.Raw` also changes what existing captures dump to, so + it is its own change. Wire-format traps are worth knowing before touching this code, since each looks like an ordinary field and is not: @@ -280,7 +281,7 @@ complaint and yields nothing useful. -- the values in :attr:`SCTP.__proto__ `: PPIDs 60 and 66, each to :class:`~pcapkit.protocols.application.ngap.NGAP`. Every other DATA chunk - payload is ``Raw`` until something calls + payload is :class:`~pcapkit.protocols.misc.raw.Raw` until something calls :func:`~pcapkit.foundation.registry.protocols.register_sctp`. How much of each registry is bound is measurable, so measure it rather than @@ -296,7 +297,7 @@ Most of those want a dissector written and are covered by the stub list above. Some wanted only a table entry, because the dissector was already there: * **Done.** :class:`~pcapkit.protocols.application.ospf.OSPF` is bound at - ``TransType`` 89 (``OSPFIGP``) and + :class:`~pcapkit.const.reg.transtype.TransType` 89 (``OSPFIGP``) and :class:`~pcapkit.protocols.link.l2tpv2.L2TPv2` at UDP port 1701. ``__index__`` raises on both, which is correct: neither is reached through a link-layer EtherType. @@ -316,7 +317,7 @@ Some wanted only a table entry, because the dissector was already there: :class:`~pcapkit.protocols.link.vlan.VLAN`, which carries the shared tag layout; the split exists so that a Q-in-Q frame's two tags stay distinct in the parsed output. -* **Done.** ``LinkType`` ``NULL`` and ``LOOP`` are bound to +* **Done.** :class:`~pcapkit.const.reg.linktype.LinkType` ``NULL`` and ``LOOP`` are bound to :class:`~pcapkit.protocols.link.loopback.Loopback`, which reads the four-octet address family word and hands 2 to IPv4 and 24, 28 or 30 to IPv6 (:issue:`1574`). ``RAW`` has no binding of its own: its bare IPv4 or IPv6 is @@ -324,16 +325,15 @@ Some wanted only a table entry, because the dissector was already there: Follow-ups to the bindings above: -* ``TransType`` 115 (``L2TP``) stays unbound, and the reason is structural: it - references :rfc:`3931`, i.e. L2TPv3 over IP, and - **there is no** ``L2TPv3`` **class for it to point at**. What exists is - :class:`~pcapkit.protocols.link.l2tpv2.L2TPv2`, the :rfc:`2661` v2 framing, - reached over UDP 1701. So the binding waits on a v3 dissector, which is also - the first member of the family to carry an - :meth:`~pcapkit.protocols.protocol.Protocol.__index__` of its own -- 115 - being that index. :mod:`pcapkit.protocols.link.l2tp` records what v3 needs, and - what ``L2F`` needs alongside it: the version nibble reading ``1`` selects L2F - [:rfc:`2341`], a separate protocol, not an earlier L2TP. +* :class:`~pcapkit.const.reg.transtype.TransType` 115 (``L2TP``) stays unbound, and the + reason is structural: it references :rfc:`3931`, i.e. L2TPv3 over IP, and **there is + no** ``L2TPv3`` **class for it to point at**. What exists is + :class:`~pcapkit.protocols.link.l2tpv2.L2TPv2`, the :rfc:`2661` v2 framing, reached over + UDP 1701. So the binding waits on a v3 dissector, which is also the first member of the + family to carry an :meth:`~pcapkit.protocols.protocol.Protocol.__index__` of its own -- + 115 being that index. :mod:`pcapkit.protocols.link.l2tp` records what v3 needs, and what + ``L2F`` needs alongside it: the version nibble reading ``1`` selects L2F [:rfc:`2341`], + a separate protocol, not an earlier L2TP. * The :class:`~pcapkit.protocols.link.l2tp.L2TP` family lives under :mod:`pcapkit.protocols.link` and so reports ``layer == 'Link'``, although it is carried inside UDP. That is deliberate: a tunnelling protocol is placed by what @@ -900,7 +900,7 @@ against the waves above in two deliberate steps: **The version string is the release button, so it is worth knowing exactly what each step does before editing it.** ``.github/workflows/create-release.yml`` is version-driven rather than tag-driven: its ``version_check`` job reads -``pcapkit.__version__`` directly, derives ``PCAPKIT_PRERELEASE`` from +:data:`pcapkit.__version__` directly, derives ``PCAPKIT_PRERELEASE`` from ``packaging.version.Version(...).is_prerelease``, and picks the Anaconda label from the same test. @@ -1021,7 +1021,7 @@ or IPv6 by its version nibble (:issue:`1502`), so of the link types this section names only ``LINUX_SLL`` and ``LINUX_SLL2`` remain enumerated but unparsed. Neither ``NULL`` nor ``RAW`` is an honest stand-in for "unknown link type", which is why the toolkit raises for an unresolvable link-layer name -instead of defaulting to ``LinkType.NULL``. +instead of defaulting to :attr:`LinkType.NULL `. **Wave 3 — the remaining protocols** from the same list, taken three or four at a time. QUIC, DSL, FDDI and ISDN come last: QUIC because it is really HTTP/3 over a @@ -1039,13 +1039,13 @@ is wanted instead of sequenced *after* wave 2 rather than by preference: the multicast options embed :rfc:`3810` MLD, which rides in **ICMPv6**, and :rfc:`3376` **IGMP** records — both wave 2 deliverables. Landing the registry first would give a dispatch table -with nothing but ``Raw`` to register. The counter-argument is real though, and -worth weighing rather than dismissing: changing the parsed shape from -:obj:`bytes` to ``Raw`` is the disruptive half, since it changes what existing -captures dump to, so there is a case for taking that churn early and registering -dissectors later. The remaining format, the :rfc:`6088` traffic selectors of -:rfc:`6089` and :rfc:`7222`, has no wave at all — it is a registry of its own and -nobody has claimed it. +with nothing but :class:`~pcapkit.protocols.misc.raw.Raw` to register. The +counter-argument is real though, and worth weighing rather than dismissing: changing the +parsed shape from :obj:`bytes` to :class:`~pcapkit.protocols.misc.raw.Raw` is the +disruptive half, since it changes what existing captures dump to, so there is a case for +taking that churn early and registering dissectors later. The remaining format, the +:rfc:`6088` traffic selectors of :rfc:`6089` and :rfc:`7222`, has no wave at all — it is a +registry of its own and nobody has claimed it. **Wave 2 or 3 — per-entry coverage of the option and parameter registries.** The option, parameter, block and frame registries are *enumerated* but not @@ -1130,11 +1130,11 @@ library gets swept deliberately, in strands: already *caused* defects rather than merely offended tidiness: - `#487 `__ existed because - ``IPv6_Route.make()`` computed ``Hdr Ext Len`` in **separate** branches with - different wrong units, and the read side computed the inverse elsewhere with - yet another. The fix was not new arithmetic but one helper per - direction, each documenting the :rfc:`8200#section-4.4` unit and saying what - its counterpart is. + :meth:`IPv6_Route.make() ` + computed ``Hdr Ext Len`` in **separate** branches with different wrong units, and the + read side computed the inverse elsewhere with yet another. The fix was not new + arithmetic but one helper per direction, each documenting the :rfc:`8200#section-4.4` + unit and saying what its counterpart is. - `#483 `__ was the scapy toolkit adapter passing a fragment offset unscaled where every sibling adapter scaled it. Every adapter restates the same field mapping, so a diff --git a/docs/source/contributing/releasing.rst b/docs/source/contributing/releasing.rst index b2a449f0f0..421b05c83f 100644 --- a/docs/source/contributing/releasing.rst +++ b/docs/source/contributing/releasing.rst @@ -9,7 +9,7 @@ Release Process being different. The short version: **the only manual edit is bumping** - ``pcapkit.__version__``. Everything else -- the ``v*`` tag, the GitHub + :data:`pcapkit.__version__`. Everything else -- the ``v*`` tag, the GitHub Release, the PyPI and Anaconda uploads, the ``conda-*`` tag and its commit to ``main`` -- is produced by ``.github/workflows/create-release.yml`` and is never hand-made. @@ -17,7 +17,7 @@ Release Process The One Manual Step ------------------- -Bumping ``pcapkit.__version__`` is done by :file:`util/bump_version.py`, whose +Bumping :data:`pcapkit.__version__` is done by :file:`util/bump_version.py`, whose own docstring is the authority on what it does; summarised here rather than duplicated. Given the current version, it works out the next one under :pep:`440` -- a ``devN`` counter, a pre-release counter, a post-release @@ -80,7 +80,7 @@ a workaround for another: Pushing a tag matching ``v*`` yourself. This chooses **which commit** to release, not which version: ``version_check``'s checkout (``:298-302``) pins ``ref: ${{ github.sha }}``, which on this trigger is - the commit the pushed tag points at, and reads ``pcapkit.__version__`` out + the commit the pushed tag points at, and reads :data:`pcapkit.__version__` out of *that* tree (``:327``). Every downstream tag and release name is built from that output -- ``v${{ needs.version_check.outputs.PCAPKIT_VERSION }}`` is ``github``'s ``tag_name`` at ``:719`` and the release ``pypi`` and diff --git a/docs/source/pcapkit/const/ftp.rst b/docs/source/pcapkit/const/ftp.rst index 3f8e9c4cf0..ef961b22be 100644 --- a/docs/source/pcapkit/const/ftp.rst +++ b/docs/source/pcapkit/const/ftp.rst @@ -28,29 +28,40 @@ names, c.f., :rfc:`5797#section-3`. .. versionchanged:: 1.5.0 - Neither ``Command._missing_`` nor ``Command.get()`` mints a member - (:issue:`860`): only IANA-registered values are members, and a new one is - created through :meth:`~pcapkit.const.ftp.command.Command.register`, which - can supply the ``feat``, ``desc``, ``type`` and ``conf`` a bare wire command - word carries none of. A minted member had each of them at its default. - - ``Command.get()`` refuses a non-:class:`str` ``key`` with - :exc:`~pcapkit.utilities.exceptions.EnumValueError`, with or without - ``default`` (:issue:`1824`). ``get(42)`` and ``get(None)`` raised + Neither :meth:`Command._missing_ ` nor + :meth:`Command.get() ` mints a member + (:issue:`860`): only IANA-registered values are members, and a new one is created + through :meth:`~pcapkit.const.ftp.command.Command.register`, which can supply the + ``feat``, ``desc``, ``type`` and ``conf`` a bare wire command word carries none of. A + minted member had each of them at its default. + + :meth:`Command.get() ` refuses a + non-:class:`str` ``key`` with :exc:`~pcapkit.utilities.exceptions.EnumValueError`, with + or without ``default`` (:issue:`1824`). ``get(42)`` and ``get(None)`` raised :exc:`AttributeError` from ``key.upper()``, and ``get(b'ABOR')`` returned an - unregistered member whose name and value were both the :class:`bytes` - object. + unregistered member whose name and value were both the :class:`bytes` object. .. autoclass:: pcapkit.const.ftp.command.Command :members: :undoc-members: :show-inheritance: + :special-members: __new__, __repr__ .. autoclass:: pcapkit.const.ftp.command.FEATCode :members: :undoc-members: :show-inheritance: +.. autoclass:: pcapkit.const.ftp.command.CommandType + :members: + :undoc-members: + :show-inheritance: + +.. autoclass:: pcapkit.const.ftp.command.ConformanceRequirement + :members: + :undoc-members: + :show-inheritance: + FTP Server Return Code ============================ @@ -63,6 +74,17 @@ which is automatically generated from :class:`pcapkit.vendor.ftp.return_code.Ret :members: :undoc-members: :show-inheritance: + :special-members: __new__, __repr__, __str__ + +.. autoclass:: pcapkit.const.ftp.return_code.ResponseKind + :members: + :undoc-members: + :show-inheritance: + +.. autoclass:: pcapkit.const.ftp.return_code.GroupingInformation + :members: + :undoc-members: + :show-inheritance: .. rubric:: Footnotes diff --git a/docs/source/pcapkit/const/http.rst b/docs/source/pcapkit/const/http.rst index 144baff695..bd1e41d78a 100644 --- a/docs/source/pcapkit/const/http.rst +++ b/docs/source/pcapkit/const/http.rst @@ -57,23 +57,25 @@ which is automatically generated from :class:`pcapkit.vendor.http.method.Method` .. versionchanged:: 1.5.0 - Neither ``Method._missing_`` nor ``Method.get()`` mints a member - (:issue:`860`): only IANA-registered values are members, and a new one is - created through :meth:`~pcapkit.corekit.enum.EnumRegistry.register`, which - can supply the ``safe`` and ``idempotent`` a bare wire method verb carries - neither of. A member's :class:`str` payload is its value, so - ``Method.GET == 'GET'`` (:issue:`870`). - - ``Method.get()`` matches ``key`` case-sensitively, as :rfc:`9110` makes the - method token (:issue:`896`), and checks it against member values as well as - names (:issue:`908`). 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`. + Neither :meth:`Method._missing_ ` nor + :meth:`Method.get() ` mints a member + (:issue:`860`): only IANA-registered values are members, and a new one is created + through :meth:`~pcapkit.corekit.enum.EnumRegistry.register`, which can supply the + ``safe`` and ``idempotent`` a bare wire method verb carries neither of. A member's + :class:`str` payload is its value, so ``Method.GET == 'GET'`` (:issue:`870`). + + :meth:`Method.get() ` matches ``key`` + case-sensitively, as :rfc:`9110` makes the method token (:issue:`896`), and checks it + against member values as well as names (:issue:`908`). 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`. .. autoclass:: pcapkit.const.http.method.Method :members: :undoc-members: :show-inheritance: + :special-members: __new__ HTTP/2 Settings =============== @@ -100,6 +102,7 @@ which is automatically generated from :class:`pcapkit.vendor.http.status_code.St :members: :undoc-members: :show-inheritance: + :special-members: __new__, __str__ .. rubric:: Footnotes diff --git a/docs/source/pcapkit/const/index.rst b/docs/source/pcapkit/const/index.rst index ba5bbb02fc..3a2cd2e519 100644 --- a/docs/source/pcapkit/const/index.rst +++ b/docs/source/pcapkit/const/index.rst @@ -40,9 +40,9 @@ That is what makes the enumerations usable against live capture data, where a protocol number assigned after this release was generated is a routine occurrence rather than an error. It also means an enumeration member is not a closed set: an unassigned value resolves to a member that is, apart from -``CGAType``, absent from iteration and ``__members__``, and whether a repeat -lookup returns the same object varies by registry, so compare such members by -value, not identity. +:class:`~pcapkit.const.mh.cga_type.CGAType`, absent from iteration and ``__members__``, +and whether a repeat lookup returns the same object varies by registry, so compare such +members by value, not identity. The common form of the override is generated -- see :mod:`pcapkit.vendor.default`, whose template emits it -- while the valid range and the names of the unassigned diff --git a/docs/source/pcapkit/const/pcapng.rst b/docs/source/pcapkit/const/pcapng.rst index 3ccea319fa..7b2e8549d3 100644 --- a/docs/source/pcapkit/const/pcapng.rst +++ b/docs/source/pcapkit/const/pcapng.rst @@ -70,6 +70,10 @@ which is automatically generated from :class:`pcapkit.vendor.pcapng.option_type. :members: :undoc-members: :show-inheritance: + :special-members: __new__ + + .. autoattribute:: __members_ns__ + :no-value: Record Types ============ @@ -133,7 +137,7 @@ which is automatically generated from :class:`pcapkit.vendor.pcapng.tls_key_labe .. versionadded:: 1.5.0 - Moved here from ``pcapkit.protocols.misc.pcapng`` (:issue:`886`). + Moved here from :mod:`pcapkit.protocols.misc.pcapng` (:issue:`886`). .. autoclass:: pcapkit.const.pcapng.tls_key_label.TLSKeyLabel :members: diff --git a/docs/source/pcapkit/const/reg.rst b/docs/source/pcapkit/const/reg.rst index fb20904320..5894a5255f 100644 --- a/docs/source/pcapkit/const/reg.rst +++ b/docs/source/pcapkit/const/reg.rst @@ -103,6 +103,7 @@ registry is one enumeration per transport protocol sharing a memberless base. :members: :undoc-members: :show-inheritance: + :special-members: __new__, __repr__, __str__, __int__, __eq__, __registry__, __canonical__ .. module:: pcapkit.const.reg.apptype.tcp diff --git a/docs/source/pcapkit/corekit/enum.rst b/docs/source/pcapkit/corekit/enum.rst index b0225443c2..4afddfdd42 100644 --- a/docs/source/pcapkit/corekit/enum.rst +++ b/docs/source/pcapkit/corekit/enum.rst @@ -12,8 +12,9 @@ adds the *mutating* half every registry under :mod:`pcapkit.const` inherits. The ``get`` each :mod:`pcapkit.const` enumeration carried as generated text moves onto :class:`EnumLookup`, and :class:`EnumRegistry` adds ``register``, - which is how a member is minted at runtime: outside ``CGAType``, looking up - an unknown value does not mint one (:issue:`775`). + which is how a member is minted at runtime: outside + :class:`~pcapkit.const.mh.cga_type.CGAType`, looking up an unknown value does not mint + one (:issue:`775`). .. autoclass:: pcapkit.corekit.enum.EnumLookup :members: diff --git a/docs/source/pcapkit/corekit/fields/field.rst b/docs/source/pcapkit/corekit/fields/field.rst index c0198ba409..017f99a35d 100644 --- a/docs/source/pcapkit/corekit/fields/field.rst +++ b/docs/source/pcapkit/corekit/fields/field.rst @@ -38,6 +38,9 @@ Internal Definitions :members: :show-inheritance: +.. autodata:: pcapkit.corekit.fields.field._RE_NEGATIVE_LENGTH_TEMPLATE + :no-value: + Type Variables -------------- diff --git a/docs/source/pcapkit/corekit/fields/ipaddress.rst b/docs/source/pcapkit/corekit/fields/ipaddress.rst index 5301581ac0..0ab4789bd2 100644 --- a/docs/source/pcapkit/corekit/fields/ipaddress.rst +++ b/docs/source/pcapkit/corekit/fields/ipaddress.rst @@ -52,6 +52,12 @@ Internal Definitions :members: :show-inheritance: +.. autoclass:: pcapkit.corekit.fields.ipaddress._RawMaskIPv4Interface + :members: + :show-inheritance: + +.. autofunction:: pcapkit.corekit.fields.ipaddress._reject_bool + Type Variables ~~~~~~~~~~~~~~ diff --git a/docs/source/pcapkit/corekit/fields/numbers.rst b/docs/source/pcapkit/corekit/fields/numbers.rst index ce1ca22d7b..36f16147aa 100644 --- a/docs/source/pcapkit/corekit/fields/numbers.rst +++ b/docs/source/pcapkit/corekit/fields/numbers.rst @@ -60,6 +60,14 @@ Internal Definitions .. autofunction:: pcapkit.corekit.fields.numbers._reduce_unregistered_member +.. autoclass:: pcapkit.corekit.fields.numbers._CapturedInt + :no-members: + :show-inheritance: + +.. autofunction:: pcapkit.corekit.fields.numbers._outside_bits + +.. autofunction:: pcapkit.corekit.fields.numbers._with_outside_bits + Type Variables -------------- diff --git a/docs/source/pcapkit/corekit/infoclass.rst b/docs/source/pcapkit/corekit/infoclass.rst index 68e197686f..ffe8563b96 100644 --- a/docs/source/pcapkit/corekit/infoclass.rst +++ b/docs/source/pcapkit/corekit/infoclass.rst @@ -24,16 +24,23 @@ classes :class:`~pcapkit.corekit.infoclass.MultiInfo` and .. autoattribute:: __excluded__ :no-value: + .. autoattribute:: __map__ + .. autoattribute:: __map_reverse__ + .. autodecorator:: pcapkit.corekit.infoclass.info_final .. autoclass:: pcapkit.corekit.infoclass.MultiInfo :no-members: :show-inheritance: + .. automethod:: to_dict + .. autoclass:: pcapkit.corekit.infoclass.OrderedMultiInfo :no-members: :show-inheritance: + .. automethod:: to_dict + Internal Definitions -------------------- @@ -41,6 +48,8 @@ Internal Definitions :no-members: :show-inheritance: + .. automethod:: __new__ + .. autoclass:: pcapkit.corekit.infoclass.FinalisedState :members: :show-inheritance: @@ -49,10 +58,19 @@ Internal Definitions :no-members: :show-inheritance: + .. automethod:: __new__ + .. automethod:: __update__ + .. automethod:: __setattr__ + .. autoclass:: pcapkit.corekit.infoclass._OrderedMultiDict :no-members: :show-inheritance: + .. automethod:: __ne__ + .. automethod:: __repr__ + +.. autofunction:: pcapkit.corekit.infoclass._ne + Type Variables -------------- diff --git a/docs/source/pcapkit/corekit/io.rst b/docs/source/pcapkit/corekit/io.rst index 9605119988..cfe48722b5 100644 --- a/docs/source/pcapkit/corekit/io.rst +++ b/docs/source/pcapkit/corekit/io.rst @@ -45,6 +45,10 @@ stream, and :class:`~pcapkit.corekit.io.NamedStream`, which gives a stream a .. automethod:: fileno .. automethod:: isatty + .. autoattribute:: _read_ahead_size + .. automethod:: _read_ahead + .. automethod:: _truncate_buffer + .. autoclass:: pcapkit.corekit.io.PeekableStream :no-members: diff --git a/docs/source/pcapkit/corekit/multidict.rst b/docs/source/pcapkit/corekit/multidict.rst index 58e0cc9046..4f7fbf4ac1 100644 --- a/docs/source/pcapkit/corekit/multidict.rst +++ b/docs/source/pcapkit/corekit/multidict.rst @@ -28,6 +28,8 @@ values per key, based on the `Werkzeug`_ project. .. automethod:: poplist .. automethod:: popitemlist + .. automethod:: deepcopy + .. autoclass:: pcapkit.corekit.multidict.OrderedMultiDict :no-members: :no-special-members: __init__ @@ -35,6 +37,8 @@ values per key, based on the `Werkzeug`_ project. .. automethod:: deepcopy + .. automethod:: items + Type Variables -------------- diff --git a/docs/source/pcapkit/corekit/packet.rst b/docs/source/pcapkit/corekit/packet.rst index 18c76c9b79..668992b8b3 100644 --- a/docs/source/pcapkit/corekit/packet.rst +++ b/docs/source/pcapkit/corekit/packet.rst @@ -38,3 +38,5 @@ object. .. automethod:: __init_subclass__ .. automethod:: __analyse__ .. automethod:: to_dict + + .. automethod:: __getattr__ diff --git a/docs/source/pcapkit/corekit/sentinels.rst b/docs/source/pcapkit/corekit/sentinels.rst index 8151361d65..0f85a9592b 100644 --- a/docs/source/pcapkit/corekit/sentinels.rst +++ b/docs/source/pcapkit/corekit/sentinels.rst @@ -25,6 +25,14 @@ subclassed (:issue:`1842`). .. autoclass:: pcapkit.corekit.sentinels.Sentinel + .. automethod:: __new__ + .. automethod:: __repr__ + .. automethod:: __copy__ + .. automethod:: __deepcopy__ + .. automethod:: __reduce__ + .. automethod:: __setattr__ + .. automethod:: __delattr__ + .. autoclass:: pcapkit.corekit.sentinels.NullType .. autodata:: pcapkit.corekit.sentinels.NULL diff --git a/docs/source/pcapkit/dumpkit/pcap.rst b/docs/source/pcapkit/dumpkit/pcap.rst index c3d8d7db30..d716907b53 100644 --- a/docs/source/pcapkit/dumpkit/pcap.rst +++ b/docs/source/pcapkit/dumpkit/pcap.rst @@ -11,3 +11,8 @@ PCAP Dumper :show-inheritance: .. autoproperty:: kind + + .. automethod:: __init__ + + .. automethod:: _append_value + .. automethod:: _make_timestamp diff --git a/docs/source/pcapkit/foundation/engines/3rdparty.rst b/docs/source/pcapkit/foundation/engines/3rdparty.rst index a7d112d455..2c6d9fabce 100644 --- a/docs/source/pcapkit/foundation/engines/3rdparty.rst +++ b/docs/source/pcapkit/foundation/engines/3rdparty.rst @@ -189,8 +189,9 @@ support, as is used by :class:`pcapkit.foundation.extraction.Extractor`. engine therefore checks which of the two it got and raises :exc:`~pcapkit.utilities.exceptions.UnsupportedCall` when it is `pcap-ct`_, naming ``engine='pcap_ct'`` in the message. Running `pcap-ct`_ under the - ``PyPCAP`` name would report an engine that is not the one in use, and the two - have different install requirements and different version coverage to report. + :class:`~pcapkit.foundation.engines.pypcap.PyPCAP` name would report an engine that is + not the one in use, and the two have different install requirements and different + version coverage to report. .. important:: @@ -231,6 +232,8 @@ support, as is used by :class:`pcapkit.foundation.extraction.Extractor`. .. autoattribute:: _dlink .. autoattribute:: _closed + .. automethod:: __init__ + pcap-ct Support =============== @@ -259,7 +262,7 @@ support, as is used by :class:`pcapkit.foundation.extraction.Extractor`. pip install pypcapkit[PCAP_CT] Together these engines cover every interpreter the project supports, and - ``PCAP_CT`` covers all of it on its own: + :class:`~pcapkit.foundation.engines.pcap_ct.PCAP_CT` covers all of it on its own: ========================================================================= ================== Engine Python @@ -418,6 +421,8 @@ support, as is used by :class:`pcapkit.foundation.extraction.Extractor`. .. autoattribute:: _dlink .. autoattribute:: _declf + .. automethod:: __init__ + Internal Definitions -------------------- diff --git a/docs/source/pcapkit/foundation/engines/index.rst b/docs/source/pcapkit/foundation/engines/index.rst index 461e5509f3..d05513ee31 100644 --- a/docs/source/pcapkit/foundation/engines/index.rst +++ b/docs/source/pcapkit/foundation/engines/index.rst @@ -85,9 +85,10 @@ their row says otherwise: | | :exc:`~pcapkit.utilities.exceptions.FormatError` / | | | :exc:`~pcapkit.utilities.exceptions.UnsupportedCall` | +-----------------------------------------------------------------+---------------------------------------------------------------+ -| :class:`~pcapkit.foundation.engines.pcap_ct.PCAP_CT` | the same gaps as ``PyPCAP`` above -- it reads the same | -| | interface, so no dissection, no reassembly and no flow | -| | tracing, and PCAP savefiles on disk only | +| :class:`~pcapkit.foundation.engines.pcap_ct.PCAP_CT` | the same gaps as | +| | :class:`~pcapkit.foundation.engines.pypcap.PyPCAP` above -- | +| | it reads the same interface, so no dissection, no reassembly | +| | and no flow tracing, and PCAP savefiles on disk only | +-----------------------------------------------------------------+---------------------------------------------------------------+ | :class:`~pcapkit.foundation.engines.pypcapfile.PyPCAPFile` | no IPv6 decoder, hence no IPv6 reassembly -- disabled with an | | | :class:`~pcapkit.utilities.warnings.AttributeWarning`, and | @@ -168,12 +169,14 @@ A constraint that an import cannot reveal is enforced by overriding :meth:`Extractor.run ` consults *before* the import test, so asking for an engine that cannot run here produces one warning naming the actual cause and a fall back to the built-in parser. -``PyShark`` checks its Python ceiling and for ``tshark``, ``PyPCAPFile`` its -Python ceiling, ``PCAP_CT`` for a system ``libpcap``, and ``PyPCAP`` and -``PCAP_CT`` which distribution owns ``pcap``. ``PyPCAP`` deliberately has no -Python ceiling check, even though upstream cannot be installed on 3.12 or newer: -what matters is which distribution is present, and a version check would refuse -a working build. +:class:`~pcapkit.foundation.engines.pyshark.PyShark` checks its Python ceiling and for +``tshark``, :class:`~pcapkit.foundation.engines.pypcapfile.PyPCAPFile` its Python ceiling, +:class:`~pcapkit.foundation.engines.pcap_ct.PCAP_CT` for a system ``libpcap``, and +:class:`~pcapkit.foundation.engines.pypcap.PyPCAP` and +:class:`~pcapkit.foundation.engines.pcap_ct.PCAP_CT` which distribution owns ``pcap``. +:class:`~pcapkit.foundation.engines.pypcap.PyPCAP` deliberately has no Python ceiling +check, even though upstream cannot be installed on 3.12 or newer: what matters is which +distribution is present, and a version check would refuse a working build. .. seealso:: @@ -209,16 +212,15 @@ installed.** Pick on availability, not on features. .. warning:: - ``PCAP_CT`` needs no toolchain to *install*, but it does need a system - :manpage:`libpcap(3)` to *run*. `libpcap`_ ships a vendored - :file:`libpcap.so` and, as published, does not use it: its - :file:`libpcap.cfg` says ``LIBPCAP = None``, which sends the loader to - :func:`ctypes.util.find_library`. With no system library at all - ``import pcap`` raises :exc:`OSError` rather than :exc:`ImportError`; - :meth:`PCAP_CT.unsupported_reason - ` catches that - and turns it into an ordinary fall back to the default engine, instead of the - hard error it would otherwise be. + :class:`~pcapkit.foundation.engines.pcap_ct.PCAP_CT` needs no toolchain to *install*, + but it does need a system :manpage:`libpcap(3)` to *run*. `libpcap`_ ships a vendored + :file:`libpcap.so` and, as published, does not use it: its :file:`libpcap.cfg` says + ``LIBPCAP = None``, which sends the loader to :func:`ctypes.util.find_library`. With no + system library at all ``import pcap`` raises :exc:`OSError` rather than + :exc:`ImportError`; :meth:`PCAP_CT.unsupported_reason + ` catches that and turns + it into an ordinary fall back to the default engine, instead of the hard error it would + otherwise be. **The two distributions collide, so install exactly one.** Both own the top-level ``pcap`` module, and pip will happily install both -- on Python 3.10, diff --git a/docs/source/pcapkit/foundation/extraction.rst b/docs/source/pcapkit/foundation/extraction.rst index 0d3a4dcfc8..f29f19c94a 100644 --- a/docs/source/pcapkit/foundation/extraction.rst +++ b/docs/source/pcapkit/foundation/extraction.rst @@ -102,6 +102,13 @@ and extracts parameters from a PCAP file. .. automethod:: __next__ .. automethod:: __call__ + .. automethod:: __enter__ + .. automethod:: __exit__ + .. automethod:: __del__ + .. automethod:: _register_internal_engine + .. automethod:: _owns_input + .. automethod:: _note_eof_progress + Type Variables -------------- diff --git a/docs/source/pcapkit/foundation/traceflow/index.rst b/docs/source/pcapkit/foundation/traceflow/index.rst index 05b5db96f7..505f23dda4 100644 --- a/docs/source/pcapkit/foundation/traceflow/index.rst +++ b/docs/source/pcapkit/foundation/traceflow/index.rst @@ -63,3 +63,7 @@ Auxiliary Data .. autoclass:: pcapkit.foundation.traceflow.data.data.Deferred :members: :show-inheritance: + +.. autoclass:: pcapkit.foundation.traceflow.data.data.FrameRecord + :members: + :show-inheritance: diff --git a/docs/source/pcapkit/foundation/traceflow/tcp.rst b/docs/source/pcapkit/foundation/traceflow/tcp.rst index 0c184ec336..70ccaca55d 100644 --- a/docs/source/pcapkit/foundation/traceflow/tcp.rst +++ b/docs/source/pcapkit/foundation/traceflow/tcp.rst @@ -24,6 +24,10 @@ TCP flows from a series of packets and connections. .. autoattribute:: __callback_fn__ :no-value: + .. automethod:: _make_segment + .. automethod:: _unique_label + .. automethod:: _ended + Terminology ----------- diff --git a/docs/source/pcapkit/protocols/application/http.rst b/docs/source/pcapkit/protocols/application/http.rst index b3f644f27c..99aa249cf5 100644 --- a/docs/source/pcapkit/protocols/application/http.rst +++ b/docs/source/pcapkit/protocols/application/http.rst @@ -32,6 +32,8 @@ which tells whether a payload opens with an HTTP/1.* start line. .. autoattribute:: _preface_length + .. automethod:: from_data + .. autofunction:: pcapkit.protocols.application.http.test_start_line .. autodata:: pcapkit.protocols.application.http._HTTP2_PREFACE diff --git a/docs/source/pcapkit/protocols/application/httpv2.rst b/docs/source/pcapkit/protocols/application/httpv2.rst index fc3ad16e28..d35ac6740f 100644 --- a/docs/source/pcapkit/protocols/application/httpv2.rst +++ b/docs/source/pcapkit/protocols/application/httpv2.rst @@ -78,6 +78,8 @@ Octets Bits Name Description .. autoattribute:: __frame__ :no-value: + .. automethod:: unpack + Header Schemas -------------- @@ -208,6 +210,10 @@ Data Models :members: :show-inheritance: +.. autoclass:: pcapkit.protocols.data.application.httpv2.Settings + :members: + :show-inheritance: + .. autoclass:: pcapkit.protocols.data.application.httpv2.SettingsFrameFlags :members: :show-inheritance: diff --git a/docs/source/pcapkit/protocols/application/ngap.rst b/docs/source/pcapkit/protocols/application/ngap.rst index d055456977..67e9be11d9 100644 --- a/docs/source/pcapkit/protocols/application/ngap.rst +++ b/docs/source/pcapkit/protocols/application/ngap.rst @@ -112,6 +112,10 @@ Auxiliary Functions .. autofunction:: pcapkit.protocols.application.ngap.load_pycrate +.. autofunction:: pcapkit.protocols.application.ngap._convert + +.. autofunction:: pcapkit.protocols.application.ngap._revert + Auxiliary Data -------------- @@ -123,6 +127,12 @@ Auxiliary Data :members: :show-inheritance: +.. autodata:: pcapkit.protocols.application.ngap._PYCRATE + :no-value: + +.. autodata:: pcapkit.protocols.application.ngap._PDU_LOCK + :no-value: + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/application/ospf.rst b/docs/source/pcapkit/protocols/application/ospf.rst index 8ce566a691..89c58d1855 100644 --- a/docs/source/pcapkit/protocols/application/ospf.rst +++ b/docs/source/pcapkit/protocols/application/ospf.rst @@ -42,10 +42,10 @@ as below: ``pcapkit.protocols.link.ospf``, ``pcapkit.protocols.data.link.ospf`` and ``pcapkit.protocols.schema.link.ospf``, the ``OSPF`` name that - ``pcapkit.protocols.link``, ``pcapkit.protocols.data.link`` and - ``pcapkit.protocols.schema.link`` each exported, and the + :mod:`pcapkit.protocols.link`, :mod:`pcapkit.protocols.data.link` and + :mod:`pcapkit.protocols.schema.link` each exported, and the ``OSPF_CrytographicAuthentication`` name that - ``pcapkit.protocols.data.link`` and ``pcapkit.protocols.schema.link`` + :mod:`pcapkit.protocols.data.link` and :mod:`pcapkit.protocols.schema.link` exported, with no alias (:issue:`719`, :issue:`1090`). .. versionchanged:: 1.5.0 @@ -78,6 +78,8 @@ as below: .. automethod:: __index__ + .. automethod:: _make_id_numbers + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/application/rarp.rst b/docs/source/pcapkit/protocols/application/rarp.rst index 1000cf3ab5..6c3b39a2fe 100644 --- a/docs/source/pcapkit/protocols/application/rarp.rst +++ b/docs/source/pcapkit/protocols/application/rarp.rst @@ -36,7 +36,7 @@ Octets Bits Name Description .. versionremoved:: 1.5.0 ``pcapkit.protocols.link.rarp``, and the ``RARP`` and ``DRARP`` names - that ``pcapkit.protocols.link`` exported, with no alias (:issue:`719`). + that :mod:`pcapkit.protocols.link` exported, with no alias (:issue:`719`). .. autoclass:: pcapkit.protocols.application.rarp.RARP :no-members: diff --git a/docs/source/pcapkit/protocols/index.rst b/docs/source/pcapkit/protocols/index.rst index efc89b8b89..36918052f3 100644 --- a/docs/source/pcapkit/protocols/index.rst +++ b/docs/source/pcapkit/protocols/index.rst @@ -226,6 +226,8 @@ Internal Definitions :no-members: :show-inheritance: + .. automethod:: __prepare__ + .. autoclass:: pcapkit.protocols.schema.schema.EnumMeta :no-members: :show-inheritance: diff --git a/docs/source/pcapkit/protocols/internet/esp.rst b/docs/source/pcapkit/protocols/internet/esp.rst index 730453a6eb..910d5d607b 100644 --- a/docs/source/pcapkit/protocols/internet/esp.rst +++ b/docs/source/pcapkit/protocols/internet/esp.rst @@ -272,6 +272,11 @@ Processing Status .. autofunction:: pcapkit.protocols.internet.esp.load_cryptography +.. autodata:: pcapkit.protocols.internet.esp._CRYPTO + :no-value: + +.. autofunction:: pcapkit.protocols.internet.esp._resolve + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/internet/ipv6.rst b/docs/source/pcapkit/protocols/internet/ipv6.rst index 05d9d2b5ce..7646353f7a 100644 --- a/docs/source/pcapkit/protocols/internet/ipv6.rst +++ b/docs/source/pcapkit/protocols/internet/ipv6.rst @@ -48,6 +48,8 @@ Octets Bits Name Description .. autoattribute:: __generic_ext_codes__ :no-value: + .. automethod:: _read_ip_hextet + Protocol Constants ------------------ diff --git a/docs/source/pcapkit/protocols/link/arp.rst b/docs/source/pcapkit/protocols/link/arp.rst index e0fcb16e73..2ab51dcbd4 100644 --- a/docs/source/pcapkit/protocols/link/arp.rst +++ b/docs/source/pcapkit/protocols/link/arp.rst @@ -54,6 +54,11 @@ below: .. automethod:: __index__ + .. automethod:: _read_addr_resolve + .. automethod:: _read_proto_resolve + .. automethod:: _make_addr_resolve + .. automethod:: _make_proto_resolve + .. autoclass:: pcapkit.protocols.link.arp.InARP :no-members: :show-inheritance: diff --git a/docs/source/pcapkit/protocols/link/ethernet.rst b/docs/source/pcapkit/protocols/link/ethernet.rst index 47a2cfec88..ca677eb284 100644 --- a/docs/source/pcapkit/protocols/link/ethernet.rst +++ b/docs/source/pcapkit/protocols/link/ethernet.rst @@ -38,6 +38,8 @@ below: .. automethod:: __index__ + .. automethod:: _make_mac_addr + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/link/index.rst b/docs/source/pcapkit/protocols/link/index.rst index 346e50ebe4..af786477c7 100644 --- a/docs/source/pcapkit/protocols/link/index.rst +++ b/docs/source/pcapkit/protocols/link/index.rst @@ -12,8 +12,8 @@ link layer, with detailed implementation and methods. ``pcapkit.protocols.link.OSPF``, ``pcapkit.protocols.link.RARP`` and ``pcapkit.protocols.link.DRARP``, and ``OSPF`` and - ``OSPF_CrytographicAuthentication`` in ``pcapkit.protocols.data.link`` and - ``pcapkit.protocols.schema.link``, with no alias. The protocols moved to + ``OSPF_CrytographicAuthentication`` in :mod:`pcapkit.protocols.data.link` and + :mod:`pcapkit.protocols.schema.link`, with no alias. The protocols moved to :mod:`pcapkit.protocols.application` (:issue:`719`); see :class:`~pcapkit.protocols.application.ospf.OSPF` and :class:`~pcapkit.protocols.application.rarp.RARP`. diff --git a/docs/source/pcapkit/protocols/misc/pcap.rst b/docs/source/pcapkit/protocols/misc/pcap.rst index da27a46058..67e3f32d57 100644 --- a/docs/source/pcapkit/protocols/misc/pcap.rst +++ b/docs/source/pcapkit/protocols/misc/pcap.rst @@ -59,6 +59,8 @@ of PCAP, whose structure is described as below: .. automethod:: _make_data + .. automethod:: _read_protos + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/misc/pcapng.rst b/docs/source/pcapkit/protocols/misc/pcapng.rst index e56eb22006..3a371fc82b 100644 --- a/docs/source/pcapkit/protocols/misc/pcapng.rst +++ b/docs/source/pcapkit/protocols/misc/pcapng.rst @@ -206,6 +206,17 @@ which implements an extractor for PCAP-NG file format [*]_, and the .. automethod:: __post_init__ .. automethod:: __index__ + .. autoattribute:: PACKET_TYPES + .. autoattribute:: SHORT_HEADER_TYPES + .. automethod:: _get_timezone + .. automethod:: _read_block_short + .. automethod:: _read_block_short_header + .. automethod:: _make_block_short + .. automethod:: _make_mac_addr + + .. automethod:: _check_block_floor + .. automethod:: __length_hint__ + Auxiliary Data -------------- @@ -304,6 +315,7 @@ Header Schemas .. autoclass:: pcapkit.protocols.schema.misc.pcapng.Option :members: :show-inheritance: + :special-members: __enum__ .. autoclass:: pcapkit.protocols.schema.misc.pcapng._OPT_Option :members: @@ -525,6 +537,14 @@ Header Schemas :members: :show-inheritance: +.. autoclass:: pcapkit.protocols.schema.misc.pcapng.OptionEnumField + :members: + :show-inheritance: + +.. autoclass:: pcapkit.protocols.schema.misc.pcapng.OptionAreaField + :members: + :show-inheritance: + Type Stubs ---------- @@ -556,6 +576,12 @@ shared with the PCAP frame header and documented in .. autofunction:: pcapkit.protocols.schema.misc.pcapng.pcapng_block_selector .. autofunction:: pcapkit.protocols.schema.misc.pcapng.dsb_secrets_selector +.. autofunction:: pcapkit.protocols.schema.misc.pcapng.nonnegative +.. autofunction:: pcapkit.protocols.schema.misc.pcapng.bounded_option +.. autofunction:: pcapkit.protocols.schema.misc.pcapng.bounded_area +.. autofunction:: pcapkit.protocols.schema.misc.pcapng.captured_area +.. autofunction:: pcapkit.protocols.schema.misc.pcapng._spb_data_length + Data Models ----------- diff --git a/docs/source/pcapkit/protocols/protocol.rst b/docs/source/pcapkit/protocols/protocol.rst index 761eea0840..5a086a3a1c 100644 --- a/docs/source/pcapkit/protocols/protocol.rst +++ b/docs/source/pcapkit/protocols/protocol.rst @@ -263,6 +263,14 @@ Internal Definitions :no-members: :show-inheritance: +.. autodata:: pcapkit.protocols.protocol.OUT_OF_BAND_KEYWORDS + :no-value: + +.. autodata:: pcapkit.protocols.protocol._KEYWORD_CONSUMERS + :no-value: + +.. autofunction:: pcapkit.protocols.protocol._declared_keywords + Type Variables -------------- diff --git a/docs/source/pcapkit/toolkit/3rdparty.rst b/docs/source/pcapkit/toolkit/3rdparty.rst index ceae7ca256..d4ef051373 100644 --- a/docs/source/pcapkit/toolkit/3rdparty.rst +++ b/docs/source/pcapkit/toolkit/3rdparty.rst @@ -42,6 +42,16 @@ Auxiliary Functions .. autofunction:: pcapkit.toolkit.scapy.attach_linktype +.. autofunction:: pcapkit.toolkit.scapy._frame + +.. autofunction:: pcapkit.toolkit.scapy._within_datagrams + +.. autofunction:: pcapkit.toolkit.scapy._tcp_carrier + +.. autofunction:: pcapkit.toolkit.scapy._tcp_segment + +.. autofunction:: pcapkit.toolkit.scapy._tcp_behind_extension_headers + DPKT Tools ========== @@ -79,6 +89,8 @@ Auxiliary Functions .. autofunction:: pcapkit.toolkit.dpkt.packet2dict +.. autofunction:: pcapkit.toolkit.dpkt._capture_time + Auxiliary Data -------------- @@ -120,6 +132,12 @@ Auxiliary Functions .. autofunction:: pcapkit.toolkit.pyshark.packet2dict +.. autofunction:: pcapkit.toolkit.pyshark._carrier + +.. autofunction:: pcapkit.toolkit.pyshark._has_fixed_header + +.. autofunction:: pcapkit.toolkit.pyshark._has_addresses + PyPCAP Tools ============ @@ -237,3 +255,22 @@ Internal Definitions .. autodata:: pcapkit.toolkit.pypcapfile.IPV4_FLAG_DF .. autodata:: pcapkit.toolkit.pypcapfile.IPV4_FLAG_MF + +.. autodata:: pcapkit.toolkit.pypcapfile.IPV4_EXTENSION_HEADERS + :no-value: + +.. autofunction:: pcapkit.toolkit.pypcapfile._walk_extension_headers + +.. autofunction:: pcapkit.toolkit.pypcapfile._default_extension_header + +.. autofunction:: pcapkit.toolkit.pypcapfile._default_accepts + +.. autofunction:: pcapkit.toolkit.pypcapfile._network + +.. autofunction:: pcapkit.toolkit.pypcapfile._transport + +.. autofunction:: pcapkit.toolkit.pypcapfile._ipv6_carries_tcp + +.. autofunction:: pcapkit.toolkit.pypcapfile._is_raw + +.. autofunction:: pcapkit.toolkit.pypcapfile._maybe_unhex diff --git a/docs/source/pcapkit/utilities/exceptions.rst b/docs/source/pcapkit/utilities/exceptions.rst index 2b83fb23a9..a3cf222a22 100644 --- a/docs/source/pcapkit/utilities/exceptions.rst +++ b/docs/source/pcapkit/utilities/exceptions.rst @@ -38,21 +38,17 @@ override, which cannot know to pass ``quiet=True`` itself (:issue:`1845`). In 1.4.x, ``quiet=True`` logged at ``ERROR`` instead of ``CRITICAL`` rather than not logging, and :data:`sys.tracebacklimit` was set on both paths. A - single ``MultiDict.get()`` miss therefore produced an ``ERROR`` record -- one - per frame when parsing unfragmented IPv6 with reassembly enabled -- and - truncated the tracebacks of unrelated exceptions for the rest of the process. - Those records never corresponded to a fault; to observe internal lookup + single :meth:`MultiDict.get() ` miss therefore + produced an ``ERROR`` record -- one per frame when parsing unfragmented IPv6 with + reassembly enabled -- and truncated the tracebacks of unrelated exceptions for the rest + of the process. Those records never corresponded to a fault; to observe internal lookup misses, catch the exception rather than read the log (:issue:`362`). .. autoexception:: pcapkit.utilities.exceptions.BaseError :no-members: :show-inheritance: - :param quiet: If :data:`True`, the error is neither logged nor allowed to - install the exception hook; it is raised silently, as internal control - flow. - :param \*args: Arbitrary positional arguments. - :param \*\*kwargs: Arbitrary keyword arguments. + .. automethod:: __init__ .. autoclass:: pcapkit.utilities.exceptions.CaughtByCaller :no-members: @@ -297,6 +293,12 @@ Error Handling Utilities .. autofunction:: pcapkit.utilities.exceptions.stacklevel +.. autofunction:: pcapkit.utilities.exceptions._install_excepthook + +.. autofunction:: pcapkit.utilities.exceptions._excepthook + +.. autofunction:: pcapkit.utilities.exceptions._threading_excepthook + .. rubric:: Footnotes .. [*] See |tbtrim|_ project for a modern Pythonic implementation. diff --git a/docs/source/pcapkit/utilities/index.rst b/docs/source/pcapkit/utilities/index.rst index 2ec32ded8d..a0d0a43426 100644 --- a/docs/source/pcapkit/utilities/index.rst +++ b/docs/source/pcapkit/utilities/index.rst @@ -81,3 +81,23 @@ following objects and functions: Taken from ``typing_extensions`` below Python 3.11 rather than 3.8, so that ``__final__`` is recorded on every supported version (:issue:`778`). + +.. function:: unquote(string, encoding='utf-8', errors='replace') + + :func:`urllib.parse.unquote`, which accepts :class:`bytes` from Python 3.9 on. + Below 3.9, a :class:`bytes` ``string`` is unquoted to octets with + :func:`urllib.parse.unquote_to_bytes` and then decoded with ``encoding`` and + ``errors``. + +.. data:: default_threading_excepthook + + The interpreter's own default thread exception hook, even when + :func:`threading.excepthook` has been replaced: + :data:`threading.__excepthook__` from Python 3.10 on, ``_thread._excepthook`` + on 3.8 and 3.9, and :data:`None` below 3.8, which has no thread exception hook. + +.. class:: cached_property(func) + + :func:`functools.cached_property` from Python 3.8 on. Below 3.8 it is a backport + of CPython's own implementation, kept verbatim so that it can be diffed against + upstream. diff --git a/docs/source/pcapkit/vendor/default.rst b/docs/source/pcapkit/vendor/default.rst index 16420b22dc..7458b9905b 100644 --- a/docs/source/pcapkit/vendor/default.rst +++ b/docs/source/pcapkit/vendor/default.rst @@ -29,6 +29,8 @@ only, which is the base class for all vendor crawlers. .. automethod:: _request .. automethod:: _dest_path + .. automethod:: __init__ + .. _vendor-variables: Environment Variables @@ -87,3 +89,5 @@ Internal Definitions :rtype: str .. autofunction:: pcapkit.vendor.default.get_proxies + +.. autofunction:: pcapkit.vendor.default.stdin_is_interactive diff --git a/docs/source/pcapkit/vendor/ftp.rst b/docs/source/pcapkit/vendor/ftp.rst index c5450c50d2..50d4bb94cd 100644 --- a/docs/source/pcapkit/vendor/ftp.rst +++ b/docs/source/pcapkit/vendor/ftp.rst @@ -24,7 +24,7 @@ This module contains the vendor crawler for **FTP Command**, which is automatically generating :class:`pcapkit.const.ftp.command.Command`. .. autoclass:: pcapkit.vendor.ftp.command.Command - :members: LINK + :members: LINK, process :show-inheritance: FTP Server Return Code diff --git a/docs/source/pcapkit/vendor/ngap.rst b/docs/source/pcapkit/vendor/ngap.rst index b11e660298..f8c78ecf05 100644 --- a/docs/source/pcapkit/vendor/ngap.rst +++ b/docs/source/pcapkit/vendor/ngap.rst @@ -28,7 +28,7 @@ This module contains the vendor crawler for **NGAP Elementary Procedure Codes**, which is automatically generating :class:`pcapkit.const.ngap.procedure_code.ProcedureCode`. .. autoclass:: pcapkit.vendor.ngap.procedure_code.ProcedureCode - :members: FLAG + :members: FLAG, _request :show-inheritance: NGAP Protocol IE Identifiers diff --git a/docs/source/pcapkit/vendor/reg.rst b/docs/source/pcapkit/vendor/reg.rst index f30e90985a..01bc2d749a 100644 --- a/docs/source/pcapkit/vendor/reg.rst +++ b/docs/source/pcapkit/vendor/reg.rst @@ -39,7 +39,7 @@ This module contains the vendor crawler for **Ethertype IEEE 802 Numbers**, which is automatically generating :class:`pcapkit.const.reg.ethertype.EtherType`. .. autoclass:: pcapkit.vendor.reg.ethertype.EtherType - :members: FLAG, LINK + :members: FLAG, LINK, _insert_range :show-inheritance: Transport Layer Protocol Numbers diff --git a/pcapkit/const/ftp/command.py b/pcapkit/const/ftp/command.py index 5337de3b90..14bb049fd6 100644 --- a/pcapkit/const/ftp/command.py +++ b/pcapkit/const/ftp/command.py @@ -218,7 +218,7 @@ class CommandType(EnumLookup, IntFlag): issue :issue:`930`, finishing :issue:`877`'s phase 2. Pure re-parenting as far as ``get``/``get_all`` are concerned -- this class defines no ``get`` of its own to reconcile with the base -- and its own :meth:`_missing_` range - guard below is untouched, since :class:`EnumLookup` does not touch that + guard below is untouched, since :class:`~pcapkit.corekit.enum.EnumLookup` does not touch that hook. """ @@ -280,6 +280,16 @@ class Command(EnumRegistry, StrEnum): def __new__(cls, name: 'str', feat: 'Optional[FEATCode]' = None, desc: 'Optional[str]' = None, type: 'CommandType' = CommandType.undefined, conf: 'ConformanceRequirement' = ConformanceRequirement.O) -> 'Type[Command]': + """Create a member from a registry row. + + Args: + name: Command word, which is also the member's value. + feat: Feature code of the ``FEAT`` response line for the command. + desc: Brief description of the command. + type: Kind of command, based on :rfc:`959#section-4`. + conf: Expectation for support in modern FTP implementations. + + """ obj = str.__new__(cls, name) obj._value_ = name @@ -291,6 +301,7 @@ def __new__(cls, name: 'str', feat: 'Optional[FEATCode]' = None, return obj def __repr__(self) -> 'str': + """Show the member with its description, as ````.""" return f'<{self.__class__.__name__}.{self._name_}: {self.desc}>' #: Abort [:rfc:`959`] @@ -570,7 +581,7 @@ def get(cls, key: 'str', default: 'Optional[str]' = None) -> 'Command': ``super()`` needs a first argument to bind. ``Command.get('X')`` binds the same as it would on a :class:`staticmethod`. - ``default`` is not forwarded on that path, as ``Method.get`` does not + ``default`` is not forwarded on that path, as :meth:`Method.get ` does not forward its own: here it supplies the *value* of the unregistered member built on a name miss, while the base's ``default`` must name a *registered* value. A refused ``key`` therefore raises whatever diff --git a/pcapkit/const/ftp/return_code.py b/pcapkit/const/ftp/return_code.py index 163715a830..a2b3f65187 100644 --- a/pcapkit/const/ftp/return_code.py +++ b/pcapkit/const/ftp/return_code.py @@ -140,6 +140,13 @@ class ReturnCode(EnumRegistry, IntEnum): group: 'GroupingInformation' def __new__(cls, value: 'int', description: 'Optional[str]' = None) -> 'Type[ReturnCode]': + """Create a member, deriving its kind and group from the code's first two digits. + + Args: + value: Three-digit return code. + description: Description of the return code. + + """ obj = int.__new__(cls, value) obj._value_ = value @@ -151,9 +158,11 @@ def __new__(cls, value: 'int', description: 'Optional[str]' = None) -> 'Type[Ret return obj def __repr__(self) -> 'str': + """Show the return code, as ````.""" return f'<{self.__class__.__name__} [{self._value_}]>' def __str__(self) -> 'str': + """Show the return code with its description, as ``[code] description``.""" return "[%s] %s" % (self._value_, self.description) #: Restart marker replay. In this case, the text is exact and not left to the diff --git a/pcapkit/const/http/method.py b/pcapkit/const/http/method.py index 3a7597cdcf..56a07fadb4 100644 --- a/pcapkit/const/http/method.py +++ b/pcapkit/const/http/method.py @@ -33,6 +33,14 @@ class Method(EnumRegistry, StrEnum): def __new__(cls, value: 'str', safe: 'bool' = False, idempotent: 'bool' = False) -> 'Type[Method]': + """Create a member from a registry row. + + Args: + value: Method token, which is also the member's value. + safe: Whether the method is safe. + idempotent: Whether the method is idempotent. + + """ obj = str.__new__(cls, value) obj._value_ = value diff --git a/pcapkit/const/http/status_code.py b/pcapkit/const/http/status_code.py index 37ffb4f8cb..60d54f2357 100644 --- a/pcapkit/const/http/status_code.py +++ b/pcapkit/const/http/status_code.py @@ -32,6 +32,13 @@ class StatusCode(EnumRegistry, IntEnum): message: 'str' def __new__(cls, value: 'int', message: 'str' = '(Unknown)') -> 'Type[StatusCode]': + """Create a member from a registry row. + + Args: + value: Status code. + message: Status message. + + """ obj = int.__new__(cls, value) obj._value_ = value @@ -43,6 +50,7 @@ def __repr__(self) -> 'str': return f'<{self.__class__.__name__} [{self._value_}]>' def __str__(self) -> 'str': + """Show the status code with its message, as ``[code] message``.""" return "[%s] %s" % (self._value_, self.message) #: Continue [:rfc:`9110#section-15.2.1`] diff --git a/pcapkit/corekit/enum.py b/pcapkit/corekit/enum.py index f1992a42cd..a0f9e20108 100644 --- a/pcapkit/corekit/enum.py +++ b/pcapkit/corekit/enum.py @@ -45,9 +45,9 @@ :issue:`842`: ``get``, ``get_all``, ``register`` and ``register_alias`` are to exist on every const registry, so the abstraction is finished by moving them to the base class. The closed sets split off by :issue:`877` are outside that -contract. ``AppType``'s sub-base class carries the overrides and dispatching -logic it needs, and ``AppType``'s subclasses override again where their contracts -differ. +contract. :class:`~pcapkit.const.reg.apptype.apptype.AppType`'s sub-base class carries the overrides and dispatching +logic it needs, and :class:`~pcapkit.const.reg.apptype.apptype.AppType`'s subclasses override again +where their contracts differ. That is a tiered hierarchy, of which this module is **tier one**: @@ -58,7 +58,7 @@ inheritance. So a registry under :mod:`pcapkit.const` gets all of them from this module, apart from the overrides below and a few hand-written ``get`` overrides. -2. ``AppType``'s sub-base -- overrides each of them, routing the lookups +2. :class:`~pcapkit.const.reg.apptype.apptype.AppType`'s sub-base -- overrides each of them, routing the lookups through its ``_dispatch``, because a port lookup needs a transport protocol to be answerable at all. Defined not in this module but in :class:`pcapkit.const.reg.apptype.apptype.AppType` itself (GitHub issue @@ -69,11 +69,12 @@ alias for the second. Plus ``_unregistered_member``, for its own extra attributes (``svc``, ``port``, ``proto``) that the generic one below does not know to set. -3. The ``AppType`` transport subclasses -- ``TCP``, ``UDP``, ``SCTP``, ``DCCP`` - -- need no override of their own at all: ``_dispatch`` - already returns ``cls`` unchanged the moment ``cls.__registry__`` is not - :obj:`None`, which is true for exactly these subclasses, so tier 2's lookups - already answer correctly on each of them without a further layer. +3. The :class:`~pcapkit.const.reg.apptype.apptype.AppType` 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` -- + need no override of their own at all: ``_dispatch`` already returns ``cls`` unchanged the moment + ``cls.__registry__`` is not :obj:`None`, which is true for exactly these subclasses, so tier 2's + lookups already answer correctly on each of them without a further layer. Defining these methods here, rather than as generated *text* written out longhand in :data:`pcapkit.vendor.default.LINE` and copied into each crawler @@ -91,7 +92,7 @@ The ``get`` each :mod:`pcapkit.const` enumeration carried as generated text moves onto :class:`EnumLookup`, and :class:`EnumRegistry` adds ``register``, - which is how a member is minted at runtime: outside ``CGAType``, looking up + which is how a member is minted at runtime: outside :class:`~pcapkit.const.mh.cga_type.CGAType`, looking up an unknown value does not mint one (:issue:`775`). """ @@ -267,15 +268,16 @@ def get(cls, key: 'Any', default: 'Any' = NO_DEFAULT) -> 'Self': It never mints while resolving ``default``; ``key`` may still mint through a ``_missing_`` that GitHub issue :issue:`775`'s ruling - deliberately kept minting, on ``CGAType``. Registering a member any other way is :meth:`register`'s job - and nobody else's, which is the ruling :issue:`775` exists to carry + deliberately kept minting, on :class:`~pcapkit.const.mh.cga_type.CGAType`. Registering a + member any other way is :meth:`~EnumRegistry.register`'s job and nobody else's, which is the ruling + :issue:`775` exists to carry out: an unrecognised or unregistered value does not become a registered member unless a user or caller explicitly creates one. A value inside a registry's declared-but-unassigned range still resolves, through that - registry's own ``_missing_`` and :meth:`_unregistered_member`, to a + registry's own ``_missing_`` and :meth:`~EnumRegistry._unregistered_member`, to a member that is deliberately absent from the lookup tables -- true - outside ``CGAType``, where such a value instead lands - in *both* tables, exactly as :meth:`register` would leave it -- for a + outside :class:`~pcapkit.const.mh.cga_type.CGAType`, where such a value instead lands + in *both* tables, exactly as :meth:`~EnumRegistry.register` would leave it -- for a non-``str`` key; the ``str`` case is qualified below. Both describe ``key`` resolution only. ``default`` never reaches ``_missing_`` on either branch: a declared-but-unassigned ``default`` does not resolve @@ -319,7 +321,7 @@ def get(cls, key: 'Any', default: 'Any' = NO_DEFAULT) -> 'Self': That restriction has a cost the paragraph above glosses over: a *declared-but-unassigned* value -- the case resolved there through - ``_missing_`` and :meth:`_unregistered_member` without either lookup + ``_missing_`` and :meth:`~EnumRegistry._unregistered_member` without either lookup table growing -- is for that exact reason invisible to the ``_value2member_map_`` check above. Such a value resolves through ``cls(value)`` but not through ``get(value)`` when ``value`` is a @@ -354,7 +356,7 @@ def get(cls, key: 'Any', default: 'Any' = NO_DEFAULT) -> 'Self': Both failure paths raise from :mod:`pcapkit.utilities.exceptions` rather than a builtin, per the ruling recorded on GitHub issue - :issue:`923`: in-library code raises from ``pcapkit.utilities.exceptions`` + :issue:`923`: in-library code raises from :mod:`pcapkit.utilities.exceptions` rather than a builtin, and whether ``ValueError`` or ``KeyError`` applies follows what stdlib's ``Enum`` raises in the same circumstance: a name miss is :exc:`KeyError`-derived and a value miss :exc:`ValueError`-derived, @@ -461,13 +463,13 @@ def get_all(cls, key: 'Any') -> 'tuple[Self, ...]': For a registry that maps one key to one member -- which is every registry inheriting this base unmodified -- that tuple holds exactly one - entry, since an alias registered by :meth:`register_alias` is a second + entry, since an alias registered by :meth:`~EnumRegistry.register_alias` is a second *name* for the canonical member rather than a second member. The method still exists here, because ``get``, ``get_all``, ``register`` and ``register_alias`` are all to exist on every const registry (GitHub issue :issue:`842`), and it is where a registry with genuinely - several matches puts them: ``AppType`` overrides it to return every service IANA - assigns to a port. + several matches puts them: :class:`~pcapkit.const.reg.apptype.apptype.AppType` overrides it + to return every service IANA assigns to a port. Args: key: Name or value to look up. @@ -517,7 +519,7 @@ def register(cls, value: 'Any', name: 'str') -> 'Self': The caller-named path, and the only one that grows the registry: it mints a new member on the class at runtime under the ``name`` the caller specifies, so nothing has to be guessed (GitHub issue :issue:`842`). - Contrast :meth:`get` and ``_missing_``, which resolve without naming anything. + Contrast :meth:`~EnumLookup.get` and ``_missing_``, which resolve without naming anything. Refuses a ``value`` that already has a member. Without this guard, ``aenum.extend_enum`` does not mint anything for an already-taken @@ -621,13 +623,13 @@ def register_alias(cls, value: 'Any', name: 'str') -> 'Self': member's mapping -- so it needs an existing member to attach to, and this refuses a value no member carries rather than falling through to :meth:`register`. :issue:`842` settled on an alias always attaching to an - existing member, leaving open whether ``AppType`` or a concrete - enumeration might need to alias a value no member carries. ``AppType``'s + existing member, leaving open whether :class:`~pcapkit.const.reg.apptype.apptype.AppType` or a concrete + enumeration might need to alias a value no member carries. :class:`~pcapkit.const.reg.apptype.apptype.AppType`'s override does not: it requires the port to already carry a member of that - very registry. What an alias means also differs away from ``AppType``: on - every other registry it is a custom name the caller opts into, not one - recorded by the IANA registrars. Minting under the name of an aliasing - call would manufacture exactly the unrecorded member :issue:`775` removes. + very registry. What an alias means also differs away from + :class:`~pcapkit.const.reg.apptype.apptype.AppType`: on every other registry it is a custom + name the caller opts into, not one recorded by the IANA registrars. Minting under the name + of an aliasing call would manufacture exactly the unrecorded member :issue:`775` removes. Membership is tested against ``_value2member_map_`` rather than by calling ``cls(value)``: a declared-but-unassigned value resolves through @@ -641,7 +643,7 @@ def register_alias(cls, value: 'Any', name: 'str') -> 'Self': would refuse this call outright -- :meth:`register` and :meth:`register_alias` test ``value``'s membership for opposite outcomes, so neither can be the other's implementation. - ``AppType``'s override differs: it mints a real member through + :class:`~pcapkit.const.reg.apptype.apptype.AppType`'s override differs: it mints a real member through ``aenum.extend_enum``, so its iteration grows too. Args: diff --git a/pcapkit/corekit/fields/collections.py b/pcapkit/corekit/fields/collections.py index 729f4a8446..20b5a0ad95 100644 --- a/pcapkit/corekit/fields/collections.py +++ b/pcapkit/corekit/fields/collections.py @@ -305,8 +305,8 @@ class OptionField(ListField, Generic[_TS]): 3. its length is a fixed integer rather than a callable, so that its width does not depend on packet data the shortcut has not read. - All of that is true of every ``OptionField`` declared in this package. A - base schema registered from outside it -- cf. + All of that is true of every :class:`~pcapkit.corekit.fields.collections.OptionField` + declared in this package. A base schema registered from outside it -- cf. :mod:`pcapkit.foundation.registry` -- need not satisfy it, and is **not** rejected: such a base schema is unpacked in full. It parses correctly and pays the cost of the second parse. Only the shortcut is withheld, so a base schema whose type field diff --git a/pcapkit/corekit/fields/field.py b/pcapkit/corekit/fields/field.py index e270849b1a..6765fddf48 100644 --- a/pcapkit/corekit/fields/field.py +++ b/pcapkit/corekit/fields/field.py @@ -69,7 +69,7 @@ #: #: Be precise about what that means in practice, because nothing in this package #: calls :func:`_zero_pad_budget` -- so the bound as shipped is over everything a -#: context has ever parsed, not over one ``Extractor`` run. It is still a bound, +#: context has ever parsed, not over one :class:`~pcapkit.foundation.extraction.Extractor` run. It is still a bound, #: and still proportionate: total padding past #: :data:`~pcapkit.utilities.config.MAX_ZERO_PAD_SHORTFALL` stays within #: ``MAX_ZERO_PAD_LENGTH + ZERO_PAD_BUDGET_RATIO`` times the octets that @@ -98,7 +98,7 @@ def _zero_pad_budget() -> 'Iterator[list[int]]': bound :meth:`FieldBase.unpack` enforces is over everything that context has parsed. Entering this scope starts a fresh ledger and restores the previous one afterwards, which is what makes the bound *per parse* -- per - ``Extractor`` run, per file, per frame -- for a caller that wants it that + :class:`~pcapkit.foundation.extraction.Extractor` run, per file, per frame -- for a caller that wants it that way, and what makes it reproducible for a test. Note that nothing in this package calls it: the layer that knows where @@ -126,8 +126,9 @@ def _zero_pad_budget() -> 'Iterator[list[int]]': #: ``f'{length}s'`` (see :mod:`pcapkit.corekit.fields.strings`, #: :mod:`~pcapkit.corekit.fields.misc`, :mod:`~pcapkit.corekit.fields.collections` #: and, before it gets byte-order-prefixed, :mod:`~pcapkit.corekit.fields.numbers` -#: -- :meth:`NumberField.build_template `'s ``else`` arm), so a negative resolved length +#: -- +#: :meth:`NumberField.build_template `'s +#: ``else`` arm), so a negative resolved length #: always leaves that literal ``-`` immediately before the digits. What #: :class:`~pcapkit.corekit.fields.numbers.NumberField` adds is a byte-order #: prefix -- one of ``'@=<>!'`` -- *in front of* that whole template diff --git a/pcapkit/corekit/fields/ipaddress.py b/pcapkit/corekit/fields/ipaddress.py index a10fd35c09..9d4a2e682c 100644 --- a/pcapkit/corekit/fields/ipaddress.py +++ b/pcapkit/corekit/fields/ipaddress.py @@ -86,7 +86,7 @@ def _reject_bool(value: 'object', description: str) -> 'None': Args: value: Value to check. description: Human-readable description of what ``value`` is, used - to build the :exc:`FieldValueError` message. + to build the :exc:`~pcapkit.utilities.exceptions.FieldValueError` message. Raises: FieldValueError: If ``value`` is a :obj:`bool`. @@ -385,17 +385,21 @@ class _RawMaskIPv4Interface(ipaddress.IPv4Interface): sorts ``address/32``, then by mask, so the order stays total when mixed with :class:`ipaddress.IPv4Interface` values. - Such a mask has no prefix length, so :attr:`network`, :attr:`with_prefixlen` - and the prefix length all describe the address alone (``/32``); only - :attr:`netmask`, :attr:`hostmask`, :attr:`with_netmask` and - :attr:`with_hostmask` carry the mask. :attr:`with_netmask` is always - dotted, so it is not the round-trip form; :class:`str` is. + Such a mask has no prefix length, so + :attr:`~ipaddress.IPv4Interface.network`, + :attr:`~ipaddress.IPv4Interface.with_prefixlen` and the prefix length all + describe the address alone (``/32``); only :attr:`netmask`, + :attr:`hostmask`, :attr:`~ipaddress.IPv4Interface.with_netmask` and + :attr:`~ipaddress.IPv4Interface.with_hostmask` carry the mask. + :attr:`~ipaddress.IPv4Interface.with_netmask` is always dotted, so it is + not the round-trip form; :class:`str` is. """ def __init__(self, address: 'str') -> 'None': addr, _, mask = address.partition('/') super().__init__(addr) + #: Mask octets as given, which need not form a contiguous netmask. self.netmask = _parse_literal_mask(mask) @property diff --git a/pcapkit/corekit/fields/misc.py b/pcapkit/corekit/fields/misc.py index f7dcbcef9e..84c85c13ca 100644 --- a/pcapkit/corekit/fields/misc.py +++ b/pcapkit/corekit/fields/misc.py @@ -608,7 +608,7 @@ def nested_packet_context(packet: 'dict[str, Any]') -> 'dict[str, Any]': :class:`~collections.ChainMap` and not for :class:`dict`, the poisoning observed in :issue:`439` came from ordinary code asking :func:`isinstance` about a plain :class:`dict`, and :issue:`439` has - been fixed directly -- every :class:`Schema` subclass gets its own + been fixed directly -- every :class:`~pcapkit.protocols.schema.schema.Schema` subclass gets its own ``_abc_impl``. Against that, swapping the ``ChainMap`` for a plain literal on CPython 3.10 once moved ``test_pcapng_remaining_constructor_branches_and_custom_dispatch`` diff --git a/pcapkit/corekit/fields/numbers.py b/pcapkit/corekit/fields/numbers.py index 222a2ace3f..7b4157c980 100644 --- a/pcapkit/corekit/fields/numbers.py +++ b/pcapkit/corekit/fields/numbers.py @@ -834,38 +834,36 @@ def _unregistered_member(namespace: 'Type[StdlibEnum] | Type[AenumEnum]', Note: Building a member this way, rather than as some other type altogether, is why :meth:`PortEnumField.post_process` and - ``OptionEnumField.post_process`` (in - :mod:`pcapkit.protocols.schema.misc.pcapng`) need this rather than :meth:`_pseudo_member`: + :meth:`OptionEnumField.post_process ` + need this rather than :meth:`_pseudo_member`: the result answers ``isinstance(result, AppType)`` truthfully, which :meth:`PortEnumField.pre_process` tests before reading - ``.port`` off it, and ``Transport._make_port`` in - :mod:`pcapkit.protocols.transport.transport` before passing it - through unchanged (see ``test_a_member_is_still_an_apptype`` in - ``tests/const/test_const_apptype_split_unit.py``); a value that - fails them would be a defect of its own. + ``.port`` off it, and + :meth:`Transport._make_port ` + in :mod:`pcapkit.protocols.transport.transport` before passing it through unchanged (see + ``test_a_member_is_still_an_apptype`` in + ``tests/const/test_const_apptype_split_unit.py``); a value that fails them would be a + defect of its own. The member this returns is absent from ``_value2member_map_``, so a *value*-keyed lookup on it -- ``self._namespace(value)`` -- still raises. Nothing on the parse or reconstruction path does that to a value it just resolved this way, which is what keeps this safe to return from - ``post_process``. A direct call to ``AppType.get()`` for the same - port does not come through here, and it does not mint either: it - builds an unregistered member of its own, through the registry's - ``_unregistered_member`` -- measured on this tree, - ``AppType.get(54321, proto=tcp)`` returns - ````, ``TCP.__members__`` does not - grow, and a second call with the same port returns an equal - member rather than the same one. Two - unregistered members for the same value also compare equal - without being identical, since + ``post_process``. A direct call to + :meth:`AppType.get() ` for the same port + does not come through here, and it does not mint either: it builds an unregistered + member of its own, through the registry's ``_unregistered_member`` -- measured on this + tree, ``AppType.get(54321, proto=tcp)`` returns ````, + ``TCP.__members__`` does not grow, and a second call with the same port returns an equal + member rather than the same one. Two unregistered members for the same value also + compare equal without being identical, since :class:`~pcapkit.const.reg.apptype.apptype.AppType` and :class:`~pcapkit.const.pcapng.option_type.OptionType` both define - ``__eq__``/``__hash__`` off an attribute (``.port`` / - ``.opt_value``) rather than object identity -- harmless for every - reader in this package, since none compares one with ``is`` or - keys a mapping on it expecting identity, but worth knowing before - reusing this elsewhere. + ``__eq__``/``__hash__`` off an attribute (``.port`` / ``.opt_value``) rather than object + identity -- harmless for every reader in this package, since none compares one with + ``is`` or keys a mapping on it expecting identity, but worth knowing before reusing this + elsewhere. :mod:`pickle` and :func:`copy.copy`/:func:`copy.deepcopy` all reduce an :class:`~enum.Enum` member through @@ -1088,7 +1086,7 @@ def post_process(self, value: 'int | bytes', packet: 'dict[str, Any]') -> 'AppTy the port itself, when the registry declares none. Notes: - This peeks at what ``AppType.get()`` itself would + This peeks at what :meth:`AppType.get() ` itself would consult -- the per-port rows via ``__registry__.getlist``, then the documented IANA spans via :meth:`~pcapkit.const.reg.apptype.apptype.AppType._missing_` -- and @@ -1098,13 +1096,12 @@ def post_process(self, value: 'int | bytes', packet: 'dict[str, Any]') -> 'AppTy :issue:`860`), and the registry does not grow either way (:issue:`1774`). It exists because that member is not the one a parse needs. ``get`` builds it through - ``AppType._unregistered_member``, which installs no - ``__reduce_ex__``, so ``pickle.loads(pickle.dumps(...))`` raises - ``ValueError: 'unknown [50000 - tcp]' is not a valid TCP``; and its - ``.name`` is ``'unknown'``, so it dumps as ``TCP::unknown [...]`` - rather than as the ``TCP:: [...]`` of every other - unassigned wire value. A genuine miss therefore builds its member - through :meth:`EnumField._unregistered_member` instead. + :meth:`AppType._unregistered_member `, + which installs no ``__reduce_ex__``, so ``pickle.loads(pickle.dumps(...))`` raises + ``ValueError: 'unknown [50000 - tcp]' is not a valid TCP``; and its ``.name`` is + ``'unknown'``, so it dumps as ``TCP::unknown [...]`` rather than as the + ``TCP:: [...]`` of every other unassigned wire value. A genuine miss + therefore builds its member through :meth:`EnumField._unregistered_member` instead. That member carries the registry's own ``__transport__``, as ``get``'s does, rather than ``self._proto``. The two are the same @@ -1118,19 +1115,17 @@ def post_process(self, value: 'int | bytes', packet: 'dict[str, Any]') -> 'AppTy :meth:`~pcapkit.const.reg.apptype.apptype.AppType._missing_`, which raises a bare :exc:`ValueError` for it. That call sits inside an ``except ValueError`` that absorbs a foreign miss, and - ``AppType.get()`` rejects the out-of-range case deliberately, so - that it is never minted over -- a - catch keyed on exception type alone cannot see the difference - between that and a "no member has this value", so it would quietly - turn the rejection into a miss. Within the width, ``_missing_`` - raises for no port of any per-transport registry: it returns a - member for a documented span and :data:`None` otherwise. Checking - the width first needs no exception-based distinction at all. - ``self.length`` is the field's own declared byte width (``2`` for - every caller of this class, hence ``0``-``65535``) - rather than a hard-coded ``65535`` borrowed from - :meth:`~pcapkit.const.reg.apptype.apptype.AppType._missing_`'s own guard, - so the two stay in lockstep by construction. + :meth:`AppType.get() ` rejects the + out-of-range case deliberately, so that it is never minted over -- a catch keyed on + exception type alone cannot see the difference between that and a "no member has this + value", so it would quietly turn the rejection into a miss. Within the width, + ``_missing_`` raises for no port of any per-transport registry: it returns a member for + a documented span and :data:`None` otherwise. Checking the width first needs no + exception-based distinction at all. ``self.length`` is the field's own declared byte + width (``2`` for every caller of this class, hence ``0``-``65535``) rather than a + hard-coded ``65535`` borrowed from + :meth:`~pcapkit.const.reg.apptype.apptype.AppType._missing_`'s own guard, so the two + stay in lockstep by construction. """ value = super(EnumField, self).post_process(value, packet) diff --git a/pcapkit/corekit/infoclass.py b/pcapkit/corekit/infoclass.py index 9900c00725..21ac598050 100644 --- a/pcapkit/corekit/infoclass.py +++ b/pcapkit/corekit/infoclass.py @@ -286,6 +286,16 @@ class C(B): """ def __new__(cls, name: 'str', bases: 'tuple[type, ...]', attrs: 'dict[str, Any]', **kwargs: 'Any') -> 'Type[Info]': + """Create the class, extending its :attr:`Info.__additional__` and + :attr:`Info.__excluded__` with every base's. + + Args: + name: Class name. + bases: Base classes. + attrs: Class namespace. + **kwargs: Arbitrary keyword arguments, passed on to :class:`type`. + + """ if '__additional__' not in attrs: attrs['__additional__'] = [] if '__excluded__' not in attrs: @@ -766,6 +776,10 @@ class _OrderedMultiDict(OrderedMultiDict[KT, VT]): __ne__ = _ne def __repr__(self) -> 'str': + """Write the export as the :class:`~pcapkit.corekit.multidict.OrderedMultiDict` + it is, with every pair. + + """ return f'{OrderedMultiDict.__name__}({list(self.items(multi=True))!r})' @@ -805,6 +819,7 @@ class itself defines, such as ``items``, is a method and not a key. """ def __new__(cls, *args: 'Any', **kwargs: 'Any') -> 'Self': # pylint: disable=unused-argument + """Create the instance with the empty multi-mapping :meth:`__update__` adds to.""" self = super().__new__(cls) # NOTE: the multi-mapping class's own ``__init__``, which follows Info # and Mapping in the MRO, gives the empty storage -- including @@ -819,6 +834,16 @@ def __init__(self, mapping: 'Optional[Mapping[Any, Any] | Iterable[tuple[Any, An def __update__(self, dict_: 'Optional[Mapping[Any, Any] | Iterable[tuple[Any, Any]]]' = None, **kwargs: 'Any') -> 'None': + """Add every pair to the multi-mapping, so that a repeated key keeps every value. + + Args: + dict_: Source pairs, as a mapping or an iterable of ``(key, value)`` pairs; + a list value of a mapping counts as one pair, and a + :class:`~pcapkit.corekit.multidict.MultiDict` gives every value of + each key. + **kwargs: More pairs, added after ``dict_``. + + """ # NOTE: every pair is *added*, as the multi-mapping's own constructor # and ``update`` do: a mapping's list values count as one pair each, # and a MultiDict gives every value of each key. @@ -849,6 +874,16 @@ def __getattr__(self, name: 'str') -> 'Any': quiet=True) def __setattr__(self, name: 'str', value: 'Any') -> 'None': # type: ignore[override] + """Admit an attribute write only while :meth:`__new__` or :meth:`__update__` is writing. + + Args: + name: Attribute name. + value: Attribute value. + + Raises: + UnsupportedCall: Outside those writes, as assignment to any :class:`Info` does. + + """ if not self.__dict__.get('_MultiInfo__open'): raise UnsupportedCall("can't set attribute") object.__setattr__(self, name, value) diff --git a/pcapkit/corekit/packet.py b/pcapkit/corekit/packet.py index 1390533f73..0c508d936d 100644 --- a/pcapkit/corekit/packet.py +++ b/pcapkit/corekit/packet.py @@ -129,6 +129,18 @@ def __analyse__(self) -> 'Any': return value def __getattr__(self, name: 'str') -> 'Any': + """Resolve ``packet`` on first access; any other missing name raises. + + Args: + name: Attribute name, reached only when it is absent from ``__dict__``. + + Returns: + The resolved ``packet``, through :meth:`__analyse__`. + + Raises: + AttributeError: If ``name`` is not ``packet``. + + """ # NOTE: reached only for names absent from ``__dict__``, which ``packet`` # always is -- see ``__additional__`` in the module docstring. Everything # else has to raise, or a typo would silently answer with the resolved diff --git a/pcapkit/corekit/sentinels.py b/pcapkit/corekit/sentinels.py index ded221b9cc..1abb27e187 100644 --- a/pcapkit/corekit/sentinels.py +++ b/pcapkit/corekit/sentinels.py @@ -470,7 +470,7 @@ class with no ``__eq__`` of its own does; what it lacks is a readable .. note:: Reloading :mod:`pcapkit.corekit.enum` alone does not produce a fresh - :data:`NO_DEFAULT`. That module does not define ``NoDefaultType``; it + :data:`NO_DEFAULT`. That module does not define :class:`~pcapkit.corekit.sentinels.NoDefaultType`; it reads :data:`NO_DEFAULT` off this module once, at its own import time, into its own module global, and a reload just re-runs that read, which -- so long as this module has not *also* been reloaded -- @@ -551,7 +551,7 @@ def __bool__(self) -> 'Literal[False]': #: AbsentType: Absent-versus-:obj:`None` sentinel for -#: ``pcapkit.protocols.protocol._declared_keywords()`` to read a class's +#: :func:`pcapkit.protocols.protocol._declared_keywords` to read a class's #: own ``__keywords__`` out of its :attr:`~object.__dict__`, where #: :obj:`None` is itself a meaningful value -- the opt-out that says the #: class cannot enumerate its keywords, cf. diff --git a/pcapkit/foundation/engines/pcapng.py b/pcapkit/foundation/engines/pcapng.py index 17baf34c73..9d7a9225ba 100644 --- a/pcapkit/foundation/engines/pcapng.py +++ b/pcapkit/foundation/engines/pcapng.py @@ -338,12 +338,12 @@ def read_frame(self) -> 'P_PCAPNG': def _block_name(self, kind: 'str') -> 'str': """Name the next non-packet block of the given kind. - The per-section context lists (e.g. ``Context.interfaces``) restart - with every Section Header Block, so numbering a block by their length - gives the first IDB of every section the same name. The output dumpers - key by name, so a repeated name overwrites the earlier block (#1256). - The number is therefore counted across the whole file instead; for a - single-section file it is the same number as before. + The per-section context lists (e.g. + :attr:`Context.interfaces `) restart + with every Section Header Block, so numbering a block by their length gives the first IDB of + every section the same name. The output dumpers key by name, so a repeated name overwrites + the earlier block (#1256). The number is therefore counted across the whole file instead; + for a single-section file it is the same number as before. Args: kind: The kind of the block, e.g. ``'Interface Description'``. diff --git a/pcapkit/foundation/engines/pypcap.py b/pcapkit/foundation/engines/pypcap.py index dbed530d32..e6ecbac77c 100644 --- a/pcapkit/foundation/engines/pypcap.py +++ b/pcapkit/foundation/engines/pypcap.py @@ -140,7 +140,7 @@ def unsupported_reason(cls) -> 'Optional[str]': the import test, because the import test cannot answer this question: ``import pcap`` succeeds when `pcap-ct`_ is installed and upstream `PyPCAP`_ is not, so the guard is satisfied and this engine would run on a - distribution it does not drive -- reporting ``PyPCAP`` for work that + distribution it does not drive -- reporting :class:`~pcapkit.foundation.engines.pypcap.PyPCAP` for work that ``pcap-ct`` did. The condition is deliberately *not* a Python version ceiling, even though @@ -200,10 +200,10 @@ def __init__(self, extractor: 'Extractor') -> 'None': hook is only consulted by :meth:`Extractor.run `; constructing the engine directly bypasses it, and running - `pcap-ct`_ under the ``PyPCAP`` name would misreport what did the - work. Via ``Extractor`` the hook fires first and degrades to the - default engine with a warning, so this is a backstop rather than - the usual path. + `pcap-ct`_ under the :class:`~pcapkit.foundation.engines.pypcap.PyPCAP` name would + misreport what did the work. Via :class:`~pcapkit.foundation.extraction.Extractor` + the hook fires first and degrades to the default engine with a warning, so this is a + backstop rather than the usual path. """ # NOTE: the probe comes *before* ``import pcap`` so that this engine and diff --git a/pcapkit/foundation/engines/pypcapfile.py b/pcapkit/foundation/engines/pypcapfile.py index a076ef032d..f0ef4bd3d0 100644 --- a/pcapkit/foundation/engines/pypcapfile.py +++ b/pcapkit/foundation/engines/pypcapfile.py @@ -172,6 +172,12 @@ def dlink(self) -> 'Enum_LinkType': ########################################################################## def __init__(self, extractor: 'Extractor') -> 'None': + """Import |pcapfile|_ and the submodules it reads savefiles with. + + Args: + extractor: :class:`~pcapkit.foundation.extraction.Extractor` instance. + + """ import pcapfile # isort:skip import pcapfile.linklayer # isort:skip import pcapfile.savefile # isort:skip diff --git a/pcapkit/foundation/extraction.py b/pcapkit/foundation/extraction.py index bc76351e17..483a186b64 100644 --- a/pcapkit/foundation/extraction.py +++ b/pcapkit/foundation/extraction.py @@ -99,8 +99,10 @@ _P = TypeVar('_P') -#: Engines whose library opens the input by *path*, ``Extractor._ifnm``, rather -#: than reading ``Extractor._ifile``, so they cannot take an unnamed stream. +#: Engines whose library opens the input by *path*, +#: :attr:`Extractor._ifnm `, rather than reading +#: :attr:`Extractor._ifile `, so they cannot take an +#: unnamed stream. _PATH_ENGINES = frozenset({'scapy', 'pyshark', 'pypcap', 'pcap_ct'}) @@ -472,7 +474,7 @@ class it names, restores the shipped entry rather than storing ext: file extension Raises: - RegistryError: If ``dumper`` is not a class, or not a ``Dumper`` subclass. + RegistryError: If ``dumper`` is not a class, or not a :class:`~pcapkit.dumpkit.common.Dumper` subclass. Warns: RegistryWarning: If a different dumper is already registered for @@ -528,7 +530,8 @@ def register_engine(cls, name: 'str', engine: 'ModuleDescriptor[Engine] | Type[E engine ``name`` shipped with Raises: - RegistryError: If ``engine`` is not a class, or not an ``Engine`` subclass. + RegistryError: If ``engine`` is not a class, or not an + :class:`~pcapkit.foundation.engines.engine.Engine` subclass. Warns: RegistryWarning: If a different class is already registered under @@ -578,7 +581,7 @@ def _register_internal_engine(cls, name: 'str', engine: 'ModuleDescriptor[Engine :class:`~pcapkit.foundation.engines.engine.EngineBase` subclass Raises: - RegistryError: If ``engine`` is not an ``EngineBase`` subclass. + RegistryError: If ``engine`` is not an :class:`~pcapkit.foundation.engines.engine.EngineBase` subclass. Warns: RegistryWarning: If a different class is already registered under @@ -622,7 +625,8 @@ def register_reassembly(cls, protocol: 'str', reassembly: 'ModuleDescriptor[Reas ``protocol`` shipped with Raises: - RegistryError: If ``reassembly`` is not a class, or not a ``Reassembly`` subclass. + RegistryError: If ``reassembly`` is not a class, or not a + :class:`~pcapkit.foundation.reassembly.reassembly.Reassembly` subclass. Warns: RegistryWarning: If a different class is already registered under @@ -664,7 +668,8 @@ def _register_internal_reassembly(cls, protocol: 'str', reassembly: 'ModuleDescr :class:`~pcapkit.foundation.reassembly.reassembly.ReassemblyBase` subclass Raises: - RegistryError: If ``reassembly`` is not a ``ReassemblyBase`` subclass. + RegistryError: If ``reassembly`` is not a + :class:`~pcapkit.foundation.reassembly.reassembly.ReassemblyBase` subclass. Warns: RegistryWarning: If a different class is already registered under @@ -708,7 +713,8 @@ def register_traceflow(cls, protocol: 'str', traceflow: 'ModuleDescriptor[TraceF ``protocol`` shipped with Raises: - RegistryError: If ``traceflow`` is not a class, or not a ``TraceFlow`` subclass. + RegistryError: If ``traceflow`` is not a class, or not a + :class:`~pcapkit.foundation.traceflow.traceflow.TraceFlow` subclass. Warns: RegistryWarning: If a different class is already registered under @@ -750,7 +756,8 @@ def _register_internal_traceflow(cls, protocol: 'str', traceflow: 'ModuleDescrip :class:`~pcapkit.foundation.traceflow.traceflow.TraceFlowBase` subclass Raises: - RegistryError: If ``traceflow`` is not a ``TraceFlowBase`` subclass. + RegistryError: If ``traceflow`` is not a + :class:`~pcapkit.foundation.traceflow.traceflow.TraceFlowBase` subclass. Warns: RegistryWarning: If a different class is already registered under diff --git a/pcapkit/foundation/reassembly/data/tcp.py b/pcapkit/foundation/reassembly/data/tcp.py index cf199a7016..104f402ea9 100644 --- a/pcapkit/foundation/reassembly/data/tcp.py +++ b/pcapkit/foundation/reassembly/data/tcp.py @@ -158,7 +158,7 @@ class HoleDescriptor(Info): ``HoleDiscriptor``, and its re-export ``pcapkit.foundation.reassembly.data.TCP_HoleDiscriptor``, with no - alias; import ``HoleDescriptor`` and ``TCP_HoleDescriptor`` instead. + alias; import :class:`~pcapkit.foundation.reassembly.data.tcp.HoleDescriptor` and ``TCP_HoleDescriptor`` instead. """ diff --git a/pcapkit/foundation/registry/protocols.py b/pcapkit/foundation/registry/protocols.py index b78eea4634..10e0b337fc 100644 --- a/pcapkit/foundation/registry/protocols.py +++ b/pcapkit/foundation/registry/protocols.py @@ -412,7 +412,7 @@ def _register_name(protocol: 'Type[ProtocolBase]', *targets: 'tuple[type, Any]') #: lets ``code=`` infer a destination from a key's own type, per :issue:`514`. It #: is the same targeting :func:`register_ethertype`, :func:`register_transtype`, #: :func:`register_linktype` and :func:`register_sctp` hard-code by hand, in one -#: table so :func:`register_protocol_code` can consult it. ``LinkType`` naming +#: table so :func:`register_protocol_code` can consult it. :class:`~pcapkit.const.reg.linktype.LinkType` naming #: more than one class is deliberate, not ambiguous -- :func:`register_linktype` #: already fans out to each of them. _CODE_DESTINATIONS: 'dict[type, tuple[Type[ProtocolBase], ...]]' = { @@ -514,10 +514,10 @@ def register_protocol_code(protocol: 'Type[ProtocolBase]', code: 'Any') -> 'None v3 that is genuinely reachable both ways: :rfc:`3931` §4.1.1 puts it directly over IP on protocol 115 and §4.1.2 puts it over UDP on port 1701. :class:`L2TPv2 ` answers on - port 1701 only, and registering *it* at ``TransType.L2TP`` would point - the :rfc:`2661` parser at a v3-over-IP header -- see - :class:`~pcapkit.protocols.link.l2tp.L2TP` and GitHub issue :issue:`548` for - what that produced when measured. + port 1701 only, and registering *it* at + :attr:`TransType.L2TP ` would point the + :rfc:`2661` parser at a v3-over-IP header -- see :class:`~pcapkit.protocols.link.l2tp.L2TP` + and GitHub issue :issue:`548` for what that produced when measured. Args: protocol: Protocol class to register. diff --git a/pcapkit/foundation/traceflow/traceflow.py b/pcapkit/foundation/traceflow/traceflow.py index 190df66a37..ce652aba2a 100644 --- a/pcapkit/foundation/traceflow/traceflow.py +++ b/pcapkit/foundation/traceflow/traceflow.py @@ -242,7 +242,7 @@ class it names, restores the shipped entry rather than storing ext: file extension Raises: - RegistryError: If ``dumper`` is not a class, or not a ``Dumper`` subclass. + RegistryError: If ``dumper`` is not a class, or not a :class:`~pcapkit.dumpkit.common.Dumper` subclass. Warns: RegistryWarning: If a different dumper is already registered for diff --git a/pcapkit/interface/misc.py b/pcapkit/interface/misc.py index 3fa29338b3..a66de11322 100644 --- a/pcapkit/interface/misc.py +++ b/pcapkit/interface/misc.py @@ -100,7 +100,7 @@ def follow_tcp_stream(fin: 'Optional[str]' = None, verbose: 'bool' = False, what "following a TCP stream" means elsewhere. :data:`False` restores one stream per direction. trace_analyse: whether each traced flow reassembles its application - layer, so that ``Index.packet`` can be read off + layer, so that :attr:`Index.packet ` can be read off :attr:`Extractor.trace `. Off by default, and independent of the ``conversations`` this function returns -- those come from the TCP reassembly this function diff --git a/pcapkit/protocols/application/http.py b/pcapkit/protocols/application/http.py index 54653dcef9..8f97995d32 100644 --- a/pcapkit/protocols/application/http.py +++ b/pcapkit/protocols/application/http.py @@ -68,8 +68,8 @@ def _test_http2_frame(data: 'bytes') -> 'bool': """Test whether ``data`` opens with a frame header a conforming sender could write. This is a plausibility test for :meth:`HTTP._guess_version`'s fall-through - only. ``httpv2.HTTP`` itself parses every frame :rfc:`9113` lets a receiver - accept, which includes frames this test turns down. + only. :class:`httpv2.HTTP ` itself parses every frame + :rfc:`9113` lets a receiver accept, which includes frames this test turns down. Args: data: Payload, starting at the frame header. @@ -214,7 +214,7 @@ class HTTP(Application[_PT, _ST], Generic[_PT, _ST]): #: the 24-octet HTTP/2 connection preface, when :meth:`_guess_version` #: identified one and parsed the frame that follows it. They belong to this #: packet's header rather than to its payload, so :meth:`read` adds them to - #: :attr:`length`; otherwise ``ProtocolBase.__init__``'s + #: :attr:`length`; otherwise :meth:`ProtocolBase.__init__ `'s #: ``self._info.__update__(packet=self.packet.payload)`` would slice the #: payload from octet 9 of a buffer whose frame starts at octet 24 and report #: the tail of the preface as packet payload. @@ -485,7 +485,7 @@ def _guess_version(self, length: 'int', **kwargs: 'Any') -> 'HTTP': Raises: ProtocolError: If no version in the family accepts the payload, or if an identified version's own parser refuses it. Only a - ``ProtocolError`` from a trial parse falls through to the next + :exc:`~pcapkit.utilities.exceptions.ProtocolError` from a trial parse falls through to the next version, and the final HTTP/2 trial also absorbs :exc:`struct.error`; any other exception from either HTTP/1 arm, or from that HTTP/2 trial, propagates unconverted. @@ -507,17 +507,16 @@ def _guess_version(self, length: 'int', **kwargs: 'Any') -> 'HTTP': * **A mid-stream segment** -- a bare HTTP/2 frame header with no preface ahead of it, or an opaque HTTP/1 message body -- is genuinely undecidable from one payload, and the honest answer is - the ``Raw`` that an escaping ``ProtocolError`` becomes under - :func:`~pcapkit.corekit.decorators.beholder`. The nine-octet frame - header is therefore never used to *identify* HTTP/2 ahead of - HTTP/1: as identification it misfires on binary HTTP/1 bodies, - which is precisely how garbage text was classified HTTP/2 to begin - with. A bare frame is parsed as HTTP/2 only by the fall-through - below, and only if its header is one a conforming sender could - write (see ``_test_http2_frame()``): a frame type :rfc:`9113` - defines, no undefined flag, and a stream that type may be sent on. - Nine zero octets -- a ``DATA`` frame on stream 0, a protocol error - under :rfc:`9113#section-6.1` -- are not guessed as HTTP/2. + the :class:`~pcapkit.protocols.misc.raw.Raw` that an escaping + :exc:`~pcapkit.utilities.exceptions.ProtocolError` becomes under + :func:`~pcapkit.corekit.decorators.beholder`. The nine-octet frame header is therefore + never used to *identify* HTTP/2 ahead of HTTP/1: as identification it misfires on + binary HTTP/1 bodies, which is precisely how garbage text was classified HTTP/2 to + begin with. A bare frame is parsed as HTTP/2 only by the fall-through below, and only + if its header is one a conforming sender could write (see ``_test_http2_frame()``): a + frame type :rfc:`9113` defines, no undefined flag, and a stream that type may be sent + on. Nine zero octets -- a ``DATA`` frame on stream 0, a protocol error under + :rfc:`9113#section-6.1` -- are not guessed as HTTP/2. """ # NOTE: Positive identification, before any parse attempt. The preface is diff --git a/pcapkit/protocols/application/httpv2.py b/pcapkit/protocols/application/httpv2.py index 86a18e0255..ed534f1bd5 100644 --- a/pcapkit/protocols/application/httpv2.py +++ b/pcapkit/protocols/application/httpv2.py @@ -230,14 +230,15 @@ def unpack(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_HTTP' still drive it negative on a buffer that clears nine (e.g. a ``GOAWAY`` frame at 9-16 octets, whose fixed ``stream`` and ``error`` fields alone consume eight); the schema layer raises - :exc:`ProtocolError` for that itself. + :exc:`~pcapkit.utilities.exceptions.ProtocolError` for that itself. ``length`` is resolved against :func:`len` only for *this* method's own check, and the *original* argument -- ``None`` included -- is what is forwarded to :meth:`Protocol.unpack `. Collapsing ``None`` to a concrete ``0`` would change what - :meth:`Schema.unpack`'s own ``prepare`` decorator does with an + :meth:`Schema.unpack `'s own + ``prepare`` decorator does with an exhausted stream: it raises :exc:`~pcapkit.utilities.exceptions.StreamEOFError` (an :exc:`EOFError`, the documented "no more packets" signal) only when diff --git a/pcapkit/protocols/application/ngap.py b/pcapkit/protocols/application/ngap.py index 8eb061200d..48425427a7 100644 --- a/pcapkit/protocols/application/ngap.py +++ b/pcapkit/protocols/application/ngap.py @@ -130,8 +130,8 @@ _PYCRATE = NotImplemented # type: Any #: Guards the module-level ``NGAP-PDU`` object returned by :func:`load_pycrate`. -#: That object is *stateful*: :meth:`from_aper` stores the decoded value on it -#: and :meth:`get_val` hands back the decoder's own containers rather than +#: That object is *stateful*: ``from_aper`` stores the decoded value on it +#: and ``get_val`` hands back the decoder's own containers rather than #: copies, so two decodes running concurrently through it would each see the #: other's tree. The lock is held across the conversion, not merely across the #: decode, for that second reason. @@ -252,7 +252,7 @@ def _missing_(cls, value: 'int') -> 'NoReturn': value: Value to get enum item. Raises: - ValueError: Always. ``Criticality`` is an ``ENUMERATED`` with no + ValueError: Always. :class:`~pcapkit.protocols.application.ngap.Criticality` is an ``ENUMERATED`` with no extension marker, so a fourth value cannot be encoded and a lookup for one is a bug rather than a newer specification. diff --git a/pcapkit/protocols/application/ospf.py b/pcapkit/protocols/application/ospf.py index 714b96569b..8d83c3ac1f 100644 --- a/pcapkit/protocols/application/ospf.py +++ b/pcapkit/protocols/application/ospf.py @@ -45,10 +45,10 @@ ``pcapkit.protocols.link.ospf``, ``pcapkit.protocols.data.link.ospf`` and ``pcapkit.protocols.schema.link.ospf``, the ``OSPF`` name that - ``pcapkit.protocols.link``, ``pcapkit.protocols.data.link`` and - ``pcapkit.protocols.schema.link`` each exported, and the + :mod:`pcapkit.protocols.link`, :mod:`pcapkit.protocols.data.link` and + :mod:`pcapkit.protocols.schema.link` each exported, and the ``OSPF_CrytographicAuthentication`` name that - ``pcapkit.protocols.data.link`` and ``pcapkit.protocols.schema.link`` + :mod:`pcapkit.protocols.data.link` and :mod:`pcapkit.protocols.schema.link` exported, with no alias (:issue:`719`, :issue:`1090`). .. versionchanged:: 1.5.0 @@ -395,9 +395,10 @@ def _make_id_numbers(self, id: 'IPv4Address | str | bytes | bytearray') -> 'byte Latent rather than live: nothing in this module calls this method (:meth:`make` builds ``router_id``/``area_id`` straight from its own arguments), so the only caller is a unit test. It is routed - through :func:`parse_ip_address` anyway, so that a future caller passing a - :obj:`bool` gets :exc:`~pcapkit.utilities.exceptions.FieldValueError` - rather than a silent ``0.0.0.1``. + through :func:`~pcapkit.corekit.fields.ipaddress.parse_ip_address` + anyway, so that a future caller passing a :obj:`bool` gets + :exc:`~pcapkit.utilities.exceptions.FieldValueError` rather than a + silent ``0.0.0.1``. The description below uses :attr:`self.__class__.__name__ ` rather than :attr:`self.alias diff --git a/pcapkit/protocols/application/rarp.py b/pcapkit/protocols/application/rarp.py index a1af47e65b..37a55136da 100644 --- a/pcapkit/protocols/application/rarp.py +++ b/pcapkit/protocols/application/rarp.py @@ -39,7 +39,7 @@ .. versionremoved:: 1.5.0 ``pcapkit.protocols.link.rarp``, and the ``RARP`` and ``DRARP`` names - that ``pcapkit.protocols.link`` exported, with no alias (:issue:`719`). + that :mod:`pcapkit.protocols.link` exported, with no alias (:issue:`719`). """ from typing import TYPE_CHECKING diff --git a/pcapkit/protocols/data/application/httpv2.py b/pcapkit/protocols/data/application/httpv2.py index 208cc7c743..ec1e8ff360 100644 --- a/pcapkit/protocols/data/application/httpv2.py +++ b/pcapkit/protocols/data/application/httpv2.py @@ -170,8 +170,10 @@ class Settings(OrderedMultiInfo['Setting', int]): An identifier may repeat, and the values apply in order, so the last one wins (:rfc:`9113#section-6.5`). Every entry is kept in wire order, as - :meth:`getlist` and ``items(multi=True)`` show and the rebuild writes, but - indexing, :meth:`get`, :meth:`values`, ``items()`` and ``to_dict(flat=True)`` + :meth:`~pcapkit.corekit.multidict.MultiDict.getlist` and ``items(multi=True)`` + show and the rebuild writes, but indexing, + :meth:`~pcapkit.corekit.multidict.MultiDict.get`, + :meth:`~pcapkit.corekit.multidict.MultiDict.values`, ``items()`` and ``to_dict(flat=True)`` answer with the *last* value of each identifier rather than the first. It is immutable, as an diff --git a/pcapkit/protocols/internet/ah.py b/pcapkit/protocols/internet/ah.py index f835555187..873f102c65 100644 --- a/pcapkit/protocols/internet/ah.py +++ b/pcapkit/protocols/internet/ah.py @@ -52,7 +52,7 @@ class AH(IPsec[Data_AH, Schema_AH], IPv6_Ext[Data_AH, Schema_AH], schema=Schema_AH, data=Data_AH): """This class implements Authentication Header. - Double-inherited (GitHub issue :issue:`917`): ``AH`` is both a member of the + Double-inherited (GitHub issue :issue:`917`): :class:`~pcapkit.protocols.internet.ah.AH` is both a member of the IPsec family and an IPv6 extension header. IANA's *IPv6 Extension Header Types* registry lists it at 51 (:rfc:`4302#section-3.1.1` has it appear after the hop-by-hop, routing and fragmentation extension headers in the diff --git a/pcapkit/protocols/internet/hip.py b/pcapkit/protocols/internet/hip.py index 932c5afd03..29893ce42a 100644 --- a/pcapkit/protocols/internet/hip.py +++ b/pcapkit/protocols/internet/hip.py @@ -285,9 +285,9 @@ class HIP(IPv6_Ext[Data_HIP, Schema_HIP], Internet[Data_HIP, Schema_HIP], carries its equivalent messages over UDP port 434 (:rfc:`5944`) rather than as protocol 135. - ``Internet`` is already reached transitively through ``IPv6_Ext``; naming - it records the classification, so a reader can tell a deliberate standalone - protocol from a header that merely inherits one. + :class:`~pcapkit.protocols.internet.internet.Internet` is already reached transitively through + :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`; naming it records the classification, so + a reader can tell a deliberate standalone protocol from a header that merely inherits one. This class parses the following HIP parameters, which are registered in the :attr:`self.__parameter__ ` diff --git a/pcapkit/protocols/internet/ipv6.py b/pcapkit/protocols/internet/ipv6.py index f12a18aa05..ab50daab07 100644 --- a/pcapkit/protocols/internet/ipv6.py +++ b/pcapkit/protocols/internet/ipv6.py @@ -763,20 +763,21 @@ def _import_next_layer(self, proto: 'int', length: 'Optional[int]' = None, *, # rather than letting the exception reach the :func:`~pcapkit.corekit.decorators.beholder` decorating this method, which would substitute plain - :class:`~pcapkit.protocols.misc.raw.Raw` -- and ``Raw`` has no + :class:`~pcapkit.protocols.misc.raw.Raw` -- and :class:`~pcapkit.protocols.misc.raw.Raw` has no ``next`` field, so it could not continue :meth:`_decode_next_layer`'s walk. Every other exception -- - including one raised by ``IPv6_Ext`` itself, or by ``ESP``'s own - dedicated parser -- still reaches ``beholder`` unchanged, giving a - plain ``Raw`` for that one layer. ``253`` and ``254`` have no - dedicated parser, so they reach plain ``Raw`` with no exception - involved; :meth:`_decode_next_layer` stops its walk structurally on - any layer whose info carries no ``next`` attribute, ``Raw`` - included. ``ESP``'s info always carries a ``next`` (:data:`None` - unless its payload was decrypted, since :rfc:`4303` encrypts the - real value), so neither this substitution nor that structural - check engages for it, and the walk ends after it -- see - :attr:`__generic_ext_codes__`'s docstring for the full distinction. + including one raised by :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` itself, + or by :class:`~pcapkit.protocols.internet.esp.ESP`'s own dedicated parser -- still + reaches ``beholder`` unchanged, giving a plain :class:`~pcapkit.protocols.misc.raw.Raw` + for that one layer. ``253`` and ``254`` have no dedicated parser, so they reach plain + :class:`~pcapkit.protocols.misc.raw.Raw` with no exception involved; + :meth:`_decode_next_layer` stops its walk structurally on any layer whose info carries + no ``next`` attribute, :class:`~pcapkit.protocols.misc.raw.Raw` included. + :class:`~pcapkit.protocols.internet.esp.ESP`'s info always carries a ``next`` + (:data:`None` unless its payload was decrypted, since :rfc:`4303` encrypts the real + value), so neither this substitution nor that structural check engages for it, and the + walk ends after it -- see :attr:`__generic_ext_codes__`'s docstring for the full + distinction. The upper layer past a walk cut at :data:`EXTENSION_HEADER_LIMIT ` is the diff --git a/pcapkit/protocols/internet/ipv6_ext.py b/pcapkit/protocols/internet/ipv6_ext.py index 6442582b0c..c8f7b630d8 100644 --- a/pcapkit/protocols/internet/ipv6_ext.py +++ b/pcapkit/protocols/internet/ipv6_ext.py @@ -93,7 +93,7 @@ 1. **An unrecognised protocol number.** :class:`~pcapkit.const.ipv6.extension_header.ExtensionHeader.Shim6` is a real IANA extension header (:rfc:`5533`) with no dedicated parser, so - :meth:`pcapkit.protocols.internet.internet.Internet._lookup_next_layer` + :meth:`Internet._lookup_next_layer ` would default it to plain :class:`~pcapkit.protocols.misc.raw.Raw`. That has no ``next`` field, so the walk in :meth:`IPv6._decode_next_layer ` @@ -214,7 +214,8 @@ class keywords implement it. It is generic in its data and schema types, like :class:`~pcapkit.protocols.internet.ipsec.IPsec`, another base in this package, so that a subclass keeps its *own* ``_PT``/``_ST`` instead of - inheriting this class's. ``AH`` and ``ESP`` therefore double-inherit two + inheriting this class's. :class:`~pcapkit.protocols.internet.ah.AH` and + :class:`~pcapkit.protocols.internet.esp.ESP` therefore double-inherit two identically-parameterised generic bases, ``IPsec[…]`` and ``IPv6_Ext[…]``. Warning: @@ -500,11 +501,12 @@ def make(self, *, They are keyword-*only* because this class is also a base and each subclass's ``make`` puts different names in the same positions. As positional parameters they draw pylint ``arguments-renamed`` - warnings (``ESP.make`` has ``spi``, ``seq`` and ``next`` where + warnings (:meth:`ESP.make ` has ``spi``, ``seq`` and ``next`` where these have ``next``, ``len`` and ``data``) and mypy ``[override]`` errors. With no positional parameters there is no position to disagree about, and the allowlist is unaffected because - ``_declared_keywords`` collects ``KEYWORD_ONLY`` parameters too. + :func:`~pcapkit.protocols.protocol._declared_keywords` collects ``KEYWORD_ONLY`` + parameters too. Nothing calls a protocol's ``make`` positionally; ``__init__`` spreads ``**kwargs`` into it. @@ -580,10 +582,10 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Opt packet whose protocol byte is 140 would reach this class and walk an IPv6-style extension-header chain out of an IPv4 payload, reading ``IPv4:IPv6-Ext:...`` instead of the ``IPv4:Shim6`` that a - plain, non-continuing ``Raw`` gives. Rejecting here sends + plain, non-continuing :class:`~pcapkit.protocols.misc.raw.Raw` gives. Rejecting here sends construction back through :func:`~pcapkit.corekit.decorators.beholder` at the *caller's* - layer, which substitutes that same ``Raw``: the version-agnostic + layer, which substitutes that same :class:`~pcapkit.protocols.misc.raw.Raw`: the version-agnostic behaviour, with no IPv4-specific code. """ diff --git a/pcapkit/protocols/link/arp.py b/pcapkit/protocols/link/arp.py index 18402ff476..e8c6e25b64 100644 --- a/pcapkit/protocols/link/arp.py +++ b/pcapkit/protocols/link/arp.py @@ -389,7 +389,7 @@ def _read_proto_resolve(self, addr: 'bytes', ptype: 'int') -> 'str | IPv4Address Protocol address. If ``ptype`` is ``0x0800`` (IPv4) and ``addr`` is 4 octets, an :class:`~ipaddress.IPv4Address`; if ``0x86dd`` (IPv6) and 16 octets, an :class:`~ipaddress.IPv6Address`; otherwise, the - address as a hex :data:`str`. + address as a hex :class:`str`. """ if ptype == Enum_EtherType.Internet_Protocol_version_4 and len(addr) == 4: # IPv4 @@ -409,14 +409,14 @@ def _make_addr_resolve(self, addr: 'str | bytes', htype: 'int') -> 'bytes': Hardware address packed as raw octets. If ``htype`` is ``1``, i.e. MAC address, the ``:``- or ``-``-separated *hex* encoded MAC address is validated and packed, one octet per group (an empty string packs - as no octets); otherwise, a :data:`str` is + as no octets); otherwise, a :class:`str` is taken as the *hex* encoded address that :meth:`_read_addr_resolve` returns, and :obj:`bytes` are returned as given. Raises: ProtocolError: If ``htype`` is ``1`` and ``addr`` is not a well-formed MAC address, or if ``htype`` is not ``1`` and - ``addr`` is a :data:`str` that is not *hex* encoded. + ``addr`` is a :class:`str` that is not *hex* encoded. """ if htype == Enum_Hardware.Ethernet: @@ -444,7 +444,7 @@ def _make_proto_resolve(self, addr: 'IPv4Address | IPv6Address | str | bytes', p Packed protocol address. If ``ptype`` is ``0x0800`` (IPv4) or ``0x86dd`` (IPv6), and ``plen`` is either not given or 4 or 16 respectively, the address is validated and packed to 4 or 16 - octets; otherwise, a :data:`str` is taken as the *hex* encoded + octets; otherwise, a :class:`str` is taken as the *hex* encoded address that :meth:`_read_proto_resolve` returns, an :class:`~ipaddress.IPv4Address`/:class:`~ipaddress.IPv6Address` is packed, and anything else is returned as given. @@ -453,10 +453,10 @@ def _make_proto_resolve(self, addr: 'IPv4Address | IPv6Address | str | bytes', p FieldValueError: If ``addr`` is a :obj:`bool` (see :func:`~pcapkit.corekit.fields.ipaddress.parse_ip_address`). ProtocolError: If ``ptype`` is neither IPv4 nor IPv6 and ``addr`` - is a :data:`str` that is not *hex* encoded. + is a :class:`str` that is not *hex* encoded. Notes: - Through :func:`parse_ip_address` rather than + Through :func:`~pcapkit.corekit.fields.ipaddress.parse_ip_address` rather than :class:`~ipaddress.IPv4Address`/:class:`~ipaddress.IPv6Address` directly, because :obj:`bool` is an :class:`int` subclass that either constructor accepts without complaint: ``addr=True`` would diff --git a/pcapkit/protocols/misc/pcapng.py b/pcapkit/protocols/misc/pcapng.py index 95d96e7e4a..3cd4e5144f 100644 --- a/pcapkit/protocols/misc/pcapng.py +++ b/pcapkit/protocols/misc/pcapng.py @@ -693,6 +693,7 @@ class PCAPNG(ProtocolBase[Data_PCAPNG, Schema_PCAPNG], #: PCAP-NG block byte order. _byte: 'Literal["little", "big"]' + #: Blocks that carry a captured packet. PACKET_TYPES = (Enum_BlockType.Enhanced_Packet_Block, Enum_BlockType.Simple_Packet_Block, Enum_BlockType.Packet_Block) diff --git a/pcapkit/protocols/protocol.py b/pcapkit/protocols/protocol.py index d78f0fe681..bb9446abc8 100644 --- a/pcapkit/protocols/protocol.py +++ b/pcapkit/protocols/protocol.py @@ -216,7 +216,7 @@ def _raw_where_parsed(made: 'Any', parsed: 'Any') -> 'bool': #: therefore consumed by :meth:`ProtocolBase.__init__ #: ` or by the schema layer #: instead of by a :meth:`make `. No -#: signature declares them, so ``_declared_keywords()`` cannot find them and +#: signature declares them, so :func:`_declared_keywords` cannot find them and #: they are listed here. #: #: ``packet`` is here because the library puts it there itself: @@ -237,7 +237,7 @@ def _raw_where_parsed(made: 'Any', parsed: 'Any') -> 'bool': '__packet__', 'packet'}) -#: Cache for ``_declared_keywords()``, keyed by protocol class. Signatures do +#: Cache for :func:`_declared_keywords`, keyed by protocol class. Signatures do #: not change after the class is created and the walk below is #: :math:`O(\\text{MRO} \\times \\text{methods})`, so it runs once per class #: rather than once per constructed packet. @@ -367,7 +367,7 @@ def _check_construction_keywords(cls: 'type', kwargs: 'dict[str, Any]', Raises: UnsupportedCall: If ``strict`` and any keyword is not among those - ``_declared_keywords()`` collects for ``cls``. + :func:`_declared_keywords` collects for ``cls``. Warns: UnknownFieldWarning: The same finding when not ``strict``. @@ -529,7 +529,7 @@ class ProtocolBase(Generic[_PT, _ST], metaclass=ProtocolMeta): #: of declaring as a parameter, e.g. with ``kwargs.get('spam')`` in #: :meth:`read` -- as :meth:`ESP.read ` #: does with ``packet``. - #: ``_declared_keywords()`` finds keywords by + #: :func:`_declared_keywords` finds keywords by #: reading signatures, which cannot see such a name, so a protocol that #: consumes one names it here and the construction check of :meth:`__init__` #: accepts it. The union over the MRO is used, so a subclass need not repeat @@ -1434,11 +1434,10 @@ def __init_subclass__(cls, /, schema: 'Optional[Type[_ST]]' = None, :class:`~pcapkit.dumpkit.common.Dumper`. Omitting ``code`` is a deliberate way to decline registration, and what every built-in protocol class does: the built-in dispatch tables (e.g. - ``Link.__proto__``) are populated by literal assignment in each layer - module, not by this hook. A subclass that declines can still be - registered later via the owning class's :meth:`register` classmethod or - the matching ``register_*`` helper in - :mod:`pcapkit.foundation.registry.protocols`. + :attr:`Link.__proto__ `) are populated by + literal assignment in each layer module, not by this hook. A subclass that declines can + still be registered later via the owning class's :meth:`register` classmethod or the + matching ``register_*`` helper in :mod:`pcapkit.foundation.registry.protocols`. ``code`` accepts: diff --git a/pcapkit/protocols/schema/internet/ipv6_route.py b/pcapkit/protocols/schema/internet/ipv6_route.py index d399f68c65..9f96260383 100644 --- a/pcapkit/protocols/schema/internet/ipv6_route.py +++ b/pcapkit/protocols/schema/internet/ipv6_route.py @@ -53,12 +53,11 @@ def ipv6_route_data_length(hdr_ext_len: 'int') -> 'int': i.e. the total on-the-wire header is ``8 + 8 * hdr_ext_len`` octets. Of that, the 4 octets of ``next``/``length``/``type``/``seg_left`` are not part of the type-specific data, so the data itself -- what - :func:`ipv6_route_data_selector` hands to the nested ``RoutingType`` - schema -- is ``4 + 8 * hdr_ext_len`` octets. This is the single place - that arithmetic is done on the read side; see - :meth:`~pcapkit.protocols.internet.ipv6_route.IPv6_Route._make_hdr_ext_len` - for its inverse on the write side. Dropping the ``4 +`` would leave the data - length 4 octets short. + :func:`ipv6_route_data_selector` hands to the nested + :class:`~pcapkit.protocols.schema.internet.ipv6_route.RoutingType` schema -- is ``4 + 8 * + hdr_ext_len`` octets. This is the single place that arithmetic is done on the read side; see + :meth:`~pcapkit.protocols.internet.ipv6_route.IPv6_Route._make_hdr_ext_len` for its inverse on + the write side. Dropping the ``4 +`` would leave the data length 4 octets short. Args: hdr_ext_len: raw ``Hdr Ext Len`` field value, as read off the wire. diff --git a/pcapkit/protocols/schema/misc/pcapng.py b/pcapkit/protocols/schema/misc/pcapng.py index c32f76eab4..2e8d235e2f 100644 --- a/pcapkit/protocols/schema/misc/pcapng.py +++ b/pcapkit/protocols/schema/misc/pcapng.py @@ -305,13 +305,11 @@ def nonnegative(length: 'Callable[[dict[str, Any]], int]') -> 'Callable[[dict[st That distinction is what keeps the decryption-secrets payloads -- :attr:`UnknownSecrets.data`, :attr:`TLSKeyLog.data` and :attr:`WireGuardKeyLog.data` -- out of this. They read ``__length__`` - *whole* rather than subtracting from it, and ``Schema.pack`` leaves it at - ``-1`` for "unknown", so flooring them packs nothing at all. On the - parsing path their - ``__length__`` is the length the enclosing - :class:`~pcapkit.corekit.fields.misc.SchemaField` declared from a 32-bit - ``secrets_length``, which cannot be negative, so there is nothing there to - floor. + *whole* rather than subtracting from it, and + :meth:`Schema.pack ` leaves it at ``-1`` for + "unknown", so flooring them packs nothing at all. On the parsing path their ``__length__`` + is the length the enclosing :class:`~pcapkit.corekit.fields.misc.SchemaField` declared from + a 32-bit ``secrets_length``, which cannot be negative, so there is nothing there to floor. """ def callback(pkt: 'dict[str, Any]') -> 'int': @@ -421,13 +419,13 @@ def bounded_area(length: 'Callable[[dict[str, Any]], int]') -> 'Callable[[dict[s :func:`bounded_option` refuses a payload that runs past the area, and the area is sized from the block's declared Block Total Length. Nothing checks - that length against the file: ``BlockType.post_process`` compares - ``length`` only against its own trailing copy. A block declaring 1,000,000 - octets while holding 36 would therefore size its option area at 999,964, - read the trailing Block Total Length into it, and leave that field to be - zero-filled. Clamping the area to ``__length__`` keeps the trailing length - out of the area, so the area holds exactly the octets the block was handed - for it, whatever it declared. + that length against the file: + :meth:`BlockType.post_process ` + compares ``length`` only against its own trailing copy. A block declaring 1,000,000 octets while + holding 36 would therefore size its option area at 999,964, read the trailing Block Total Length + into it, and leave that field to be zero-filled. Clamping the area to ``__length__`` keeps the + trailing length out of the area, so the area holds exactly the octets the block was handed for + it, whatever it declared. This is a no-op on every well-formed block rather than a second guess at the area. At the option field the only field still to come is the trailing Block @@ -729,14 +727,15 @@ def post_process(self, value: 'int | bytes', packet: 'dict[str, Any]') -> 'Enum_ This replicates the read-only membership test :meth:`~pcapkit.const.pcapng.option_type.OptionType.get` itself runs first, and calls it only once that test finds the code declared. An - undeclared option type gets :meth:`EnumField._unregistered_member` + undeclared option type gets + :meth:`EnumField._unregistered_member ` instead of ``get()``'s own miss path, for pickle-safety: that method carries its own ``__reduce_ex__``, so a member built this way round-trips through :mod:`pickle` -- see its own docstring -- which the override of :meth:`~pcapkit.const.pcapng.option_type.OptionType._unregistered_member`, the miss path, does not. The override's - :attr:`~pcapkit.const.pcapng.option_type.OptionType._value_` is the + :attr:`~enum.Enum._value_` is the *formatted* display string (``'opt_unknown [8888]'``, not ``8888``), so ``pickle.dumps`` on one succeeds but ``pickle.loads`` of the result raises ``ValueError``: the default reduction reconstructs through @@ -749,7 +748,8 @@ def post_process(self, value: 'int | bytes', packet: 'dict[str, Any]') -> 'Enum_ :attr:`~pcapkit.const.pcapng.option_type.OptionType.opt_value` are never set -- which is *why* the override exists, choosing to render correctly over round-tripping correctly. - :meth:`EnumField._unregistered_member` is the only one of these + :meth:`EnumField._unregistered_member ` + is the only one of these that gets both right. :func:`copy.deepcopy` is not the difference: it succeeds on both kinds, since the standard library's :class:`enum.Enum` (which ``aenum.Enum`` subclasses) defines diff --git a/pcapkit/protocols/schema/schema.py b/pcapkit/protocols/schema/schema.py index cb4bbfe6f2..cd275271f0 100644 --- a/pcapkit/protocols/schema/schema.py +++ b/pcapkit/protocols/schema/schema.py @@ -284,7 +284,7 @@ def __prepare__(cls, name: 'str', bases: 'tuple[type, ...]', /, **kwds: 'Any') - #: **kwargs)``), so a class keyword spelled the same as any of them binds #: that parameter twice: ``TypeError: ABCMeta.__new__() got multiple #: values for argument '...'``. ``namespace`` collided this way, which is why - #: :mod:`pcapkit.protocols.schema.misc.pcapng`'s ``Option`` subclasses + #: :mod:`pcapkit.protocols.schema.misc.pcapng`'s :class:`~pcapkit.protocols.schema.misc.pcapng.Option` subclasses #: spell it ``ns=`` instead. #: #: ``cls`` collides one level *later*: every ``__init_subclass__`` is an @@ -1329,7 +1329,7 @@ def registry(self) -> 'DefaultDict[_ET, Type[Self]]': Note: This property answers on an instance; on the class, the same - mapping is served by ``EnumMeta.registry``. + mapping is served by :attr:`EnumMeta.registry `. Important: See :attr:`EnumMeta.registry diff --git a/pcapkit/protocols/schema/transport/tcp.py b/pcapkit/protocols/schema/transport/tcp.py index 3148f8a56c..1f6311504d 100644 --- a/pcapkit/protocols/schema/transport/tcp.py +++ b/pcapkit/protocols/schema/transport/tcp.py @@ -333,13 +333,13 @@ def mptcp_dss_ack_selector(pkt: 'dict[str, Any]') -> 'Field': is 4 octets, so a width lambda returning ``0`` for it would leave the ``length`` octet counting 4 octets that were never packed. - The ``ConditionalField`` form could express this field correctly, since a - callable-length ``NumberField`` packs and unpacks both the 4- and the + The :class:`~pcapkit.corekit.fields.misc.ConditionalField` form could express this field correctly, since a + callable-length :class:`~pcapkit.corekit.fields.numbers.NumberField` packs and unpacks both the 4- and the 8-octet form, and wire *absence* is no obstacle: :attr:`MPTCPDSS.ssn`, :attr:`MPTCPDSS.dl_len` and - :attr:`MPTCPDSS.checksum` are each a ``ConditionalField`` on the sibling + :attr:`MPTCPDSS.checksum` are each a :class:`~pcapkit.corekit.fields.misc.ConditionalField` on the sibling ``M`` flag (the checksum on the option length as well). The - ``SwitchField`` form is kept for a narrower reason about + :class:`~pcapkit.corekit.fields.misc.SwitchField` form is kept for a narrower reason about composition. A :class:`~pcapkit.corekit.fields.misc.ConditionalField`'s ``length`` forwards to the wrapped field unconditionally, never consulting the @@ -348,8 +348,8 @@ def mptcp_dss_ack_selector(pkt: 'dict[str, Any]') -> 'Field': ``struct.error: bad char in struct format``. Nothing here meets that only because :class:`Schema `'s ``pack`` and ``unpack`` - special-case ``ConditionalField`` by type and skip the wrapped field - outright before any ``length`` is read. A ``SwitchField`` needs no such + special-case :class:`~pcapkit.corekit.fields.misc.ConditionalField` by type and skip the wrapped field + outright before any ``length`` is read. A :class:`~pcapkit.corekit.fields.misc.SwitchField` needs no such special case: its selector always hands back an already-concrete field, :class:`~pcapkit.corekit.fields.misc.NoValueField` included, so its ``length`` is safe wherever it is read. Swapping the two would be a @@ -381,8 +381,9 @@ def mptcp_dss_dsn_selector(pkt: 'dict[str, Any]') -> 'Field': Note: Identical in shape to :func:`mptcp_dss_ack_selector`; see that function's - note for why a ``SwitchField`` is used in place of a - ``ConditionalField`` around a callable-length ``NumberField``. + note for why a :class:`~pcapkit.corekit.fields.misc.SwitchField` is used in place of a + :class:`~pcapkit.corekit.fields.misc.ConditionalField` around a callable-length + :class:`~pcapkit.corekit.fields.numbers.NumberField`. """ if not pkt['flags']['M']: diff --git a/pcapkit/utilities/config.py b/pcapkit/utilities/config.py index b4d3cbb87b..e9c8dcd241 100644 --- a/pcapkit/utilities/config.py +++ b/pcapkit/utilities/config.py @@ -310,9 +310,9 @@ def read_flag(name: 'str', default: 'bool' = False) -> 'bool': #: ceiling is honoured however often it is declared. Measured for :issue:`573`, 200 #: minimal PCAP-NG Decryption Secrets Blocks -- 4,800 wire octets, each block #: declaring ``secrets_length`` of 262,142 against two supplied octets through -#: ``UnknownSecrets.data`` (``pcapkit/protocols/schema/misc/pcapng.py``) -- -#: retained 50.0 MiB, an amplification of 10,922x per block. Every individual -#: field was under the ceiling, so nothing refused any of them. +#: :attr:`UnknownSecrets.data ` +#: (``pcapkit/protocols/schema/misc/pcapng.py``) -- retained 50.0 MiB, an amplification of 10,922x +#: per block. Every individual field was under the ceiling, so nothing refused any of them. #: #: The sum therefore wants a budget, and this is the figure that makes one #: *safe*. A budget on its own is not: a capture cut short by its snapshot length diff --git a/pcapkit/utilities/exceptions.py b/pcapkit/utilities/exceptions.py index a0ebd88e30..24b45680fa 100644 --- a/pcapkit/utilities/exceptions.py +++ b/pcapkit/utilities/exceptions.py @@ -283,6 +283,20 @@ class BaseError(Exception): """ def __init__(self, *args: 'Any', quiet: 'bool' = False, **kwargs: 'Any') -> 'None': + """Log the error, unless it is quiet, and initialise the exception. + + A loud error logs at ``CRITICAL`` and, unless ``PCAPKIT_DEVMODE`` is set, + installs the terse exception hooks; the class docstring says when an error is + quiet. + + Args: + *args: Arbitrary positional arguments. + quiet: If :data:`True`, the error is neither logged nor allowed to + install the exception hook; it is raised silently, as internal + control flow. + **kwargs: Arbitrary keyword arguments. + + """ # log error -- a quiet error emits nothing and mutates nothing, and so # does one a :class:`CaughtByCaller` block says its caller catches if not quiet and not isinstance(self, _caught_here.get()): @@ -408,7 +422,7 @@ def _threading_excepthook(args: 'ExceptHookArgs') -> 'None': """Thread analogue of :func:`_excepthook`; same contract, one argument. :func:`threading.excepthook` is called with a single - :class:`threading.ExceptHookArgs`, carrying ``exc_type``, ``exc_value``, + ``threading.ExceptHookArgs``, carrying ``exc_type``, ``exc_value``, ``exc_traceback`` and ``thread`` -- not the three positional parameters :func:`sys.excepthook` takes. Everything else here mirrors :func:`_excepthook` as closely as the default thread hook's own output allows: a loud diff --git a/pcapkit/vendor/default.py b/pcapkit/vendor/default.py index 0e0e1c5f9a..f2b5d5ac1f 100644 --- a/pcapkit/vendor/default.py +++ b/pcapkit/vendor/default.py @@ -552,11 +552,11 @@ def _dest_path(cls) -> 'str': ``vendor/reg/apptype/tcp.py`` to ``vendor/const/apptype/tcp.py``, *inside* ``vendor/`` itself rather than under ``pcapkit/const/``. - ``pcapkit.vendor`` is imported here, inside the method, rather than at + :mod:`pcapkit.vendor` is imported here, inside the method, rather than at module scope: it imports *this* module while it is still initialising (see ``pcapkit/vendor/__init__.py``), so a module-level import would have to reason about that partial state. By the time any concrete - :class:`Vendor` subclass is instantiated, ``pcapkit.vendor`` has always + :class:`Vendor` subclass is instantiated, :mod:`pcapkit.vendor` has always finished importing. That relative-path mirroring is only safe once the module is confirmed @@ -569,7 +569,7 @@ def _dest_path(cls) -> 'str': from a module that never sits under ``vendor/`` at all, resolves :mod:`pcapkit.vendor` from wherever it is installed while ``inspect.getfile(cls)`` names a file elsewhere entirely -- and - ``Vendor.__init__`` then opens that path with ``'w'``, silently + :meth:`Vendor.__init__ ` then opens that path with ``'w'``, silently truncating whatever :attr:`~Vendor.__module__` actually names instead of raising. So the escape is rejected outright, rather than trusted to produce a harmless-looking wrong path. diff --git a/pcapkit/vendor/ftp/command.py b/pcapkit/vendor/ftp/command.py index ad2521d523..af084c129c 100644 --- a/pcapkit/vendor/ftp/command.py +++ b/pcapkit/vendor/ftp/command.py @@ -230,7 +230,7 @@ class CommandType(EnumLookup, IntFlag): issue :issue:`930`, finishing :issue:`877`'s phase 2. Pure re-parenting as far as ``get``/``get_all`` are concerned -- this class defines no ``get`` of its own to reconcile with the base -- and its own :meth:`_missing_` range - guard below is untouched, since :class:`EnumLookup` does not touch that + guard below is untouched, since :class:`~pcapkit.corekit.enum.EnumLookup` does not touch that hook. """ @@ -292,6 +292,16 @@ class {NAME}(EnumRegistry, StrEnum): def __new__(cls, name: 'str', feat: 'Optional[FEATCode]' = None, desc: 'Optional[str]' = None, type: 'CommandType' = CommandType.undefined, conf: 'ConformanceRequirement' = ConformanceRequirement.O) -> 'Type[{NAME}]': + """Create a member from a registry row. + + Args: + name: Command word, which is also the member's value. + feat: Feature code of the ``FEAT`` response line for the command. + desc: Brief description of the command. + type: Kind of command, based on :rfc:`959#section-4`. + conf: Expectation for support in modern FTP implementations. + + """ obj = str.__new__(cls, name) obj._value_ = name @@ -303,6 +313,7 @@ def __new__(cls, name: 'str', feat: 'Optional[FEATCode]' = None, return obj def __repr__(self) -> 'str': + """Show the member with its description, as ````.""" return f'<{{self.__class__.__name__}}.{{self._name_}}: {{self.desc}}>' {ENUM} @@ -404,7 +415,7 @@ def get(cls, key: 'str', default: 'Optional[str]' = None) -> '{NAME}': ``super()`` needs a first argument to bind. ``{NAME}.get('X')`` binds the same as it would on a :class:`staticmethod`. - ``default`` is not forwarded on that path, as ``Method.get`` does not + ``default`` is not forwarded on that path, as :meth:`Method.get ` does not forward its own: here it supplies the *value* of the unregistered member built on a name miss, while the base's ``default`` must name a *registered* value. A refused ``key`` therefore raises whatever @@ -511,7 +522,7 @@ def process(self, data: 'list[str]') -> 'tuple[list[str], list[str]]': Returns: :class:`Command`'s enumeration fields, and every distinct per-command keyword the ``FEAT code`` column names -- - :class:`FEATCode` must declare these as real members (GitHub + :class:`~pcapkit.const.ftp.command.FEATCode` must declare these as real members (GitHub issue :issue:`860`), rather than the old approach of minting one as an incidental side effect the first time a :class:`Command` row referencing it was evaluated at import time. diff --git a/pcapkit/vendor/ftp/return_code.py b/pcapkit/vendor/ftp/return_code.py index 5d4c2ab0e8..fe3afb86b4 100644 --- a/pcapkit/vendor/ftp/return_code.py +++ b/pcapkit/vendor/ftp/return_code.py @@ -169,6 +169,13 @@ class {NAME}(EnumRegistry, IntEnum): group: 'GroupingInformation' def __new__(cls, value: 'int', description: 'Optional[str]' = None) -> 'Type[{NAME}]': + """Create a member, deriving its kind and group from the code's first two digits. + + Args: + value: Three-digit return code. + description: Description of the return code. + + """ obj = int.__new__(cls, value) obj._value_ = value @@ -180,9 +187,11 @@ def __new__(cls, value: 'int', description: 'Optional[str]' = None) -> 'Type[{NA return obj def __repr__(self) -> 'str': + """Show the return code, as ````.""" return f'<{{self.__class__.__name__}} [{{self._value_}}]>' def __str__(self) -> 'str': + """Show the return code with its description, as ``[code] description``.""" return "[%s] %s" % (self._value_, self.description) {ENUM} diff --git a/pcapkit/vendor/http/method.py b/pcapkit/vendor/http/method.py index 03cb433ad3..b2207fb696 100644 --- a/pcapkit/vendor/http/method.py +++ b/pcapkit/vendor/http/method.py @@ -57,6 +57,14 @@ class {NAME}(EnumRegistry, StrEnum): def __new__(cls, value: 'str', safe: 'bool' = False, idempotent: 'bool' = False) -> 'Type[{NAME}]': + """Create a member from a registry row. + + Args: + value: Method token, which is also the member's value. + safe: Whether the method is safe. + idempotent: Whether the method is idempotent. + + """ obj = str.__new__(cls, value) obj._value_ = value diff --git a/pcapkit/vendor/http/status_code.py b/pcapkit/vendor/http/status_code.py index 007f382300..c2a5da5851 100644 --- a/pcapkit/vendor/http/status_code.py +++ b/pcapkit/vendor/http/status_code.py @@ -57,6 +57,13 @@ class {NAME}(EnumRegistry, IntEnum): message: 'str' def __new__(cls, value: 'int', message: 'str' = '(Unknown)') -> 'Type[{NAME}]': + """Create a member from a registry row. + + Args: + value: Status code. + message: Status message. + + """ obj = int.__new__(cls, value) obj._value_ = value @@ -68,6 +75,7 @@ def __repr__(self) -> 'str': return f'<{{self.__class__.__name__}} [{{self._value_}}]>' def __str__(self) -> 'str': + """Show the status code with its message, as ``[code] message``.""" return "[%s] %s" % (self._value_, self.message) {ENUM} diff --git a/pcapkit/vendor/ngap/procedure_code.py b/pcapkit/vendor/ngap/procedure_code.py index 6eed64a595..6a55a33702 100644 --- a/pcapkit/vendor/ngap/procedure_code.py +++ b/pcapkit/vendor/ngap/procedure_code.py @@ -145,9 +145,9 @@ def _request(self) -> 'list[tuple[str, int]]': # type: ignore[override] identifier verbatim (``'id-AMFConfigurationUpdate'``), and a private ``_val`` holding the assigned integer. None of these has a public accessor that survives this far: - :meth:`~pycrate_asn1rt.asnobj.ASN1Obj.get_typeref` + ``pycrate_asn1rt.asnobj.ASN1Obj.get_typeref`` resolves to the *referenced type object* rather than its - ``(module, name)`` pair, and that object's own :meth:`get_name` reads + ``(module, name)`` pair, and that object's own ``get_name`` reads :obj:`None` here despite :func:`repr` showing the name -- measured against pycrate 0.8.1. Reading the private attributes directly is therefore the only way to recover what this crawler needs, which is a diff --git a/pcapkit/vendor/reg/ethertype.py b/pcapkit/vendor/reg/ethertype.py index a622e2e6bd..f9472ec4ad 100644 --- a/pcapkit/vendor/reg/ethertype.py +++ b/pcapkit/vendor/reg/ethertype.py @@ -88,7 +88,7 @@ def rename(self, name: 'str', code: 'str', *, original: 'Optional[str]' = None) def _insert_range(ranges: 'list[tuple[int, int, list[str]]]', entry: 'tuple[int, int, list[str]]') -> 'None': """Insert a range block ahead of the first already-placed range that fully contains it. - The generated :meth:`~EtherType._missing_` tests these ``if`` blocks in + The generated :meth:`~pcapkit.const.reg.ethertype.EtherType._missing_` tests these ``if`` blocks in list order and returns on the first match, so a range that is fully contained within an earlier, wider one would never be reached -- see GitHub issue :issue:`862`, where the source table lists ``0x0101-0x01FF`` diff --git a/tests/const/test_const_enum_builtin_parity.py b/tests/const/test_const_enum_builtin_parity.py index 39558eb0f5..615d62fe5b 100644 --- a/tests/const/test_const_enum_builtin_parity.py +++ b/tests/const/test_const_enum_builtin_parity.py @@ -1337,7 +1337,8 @@ def test_no_percent_formatting_survives_in_the_issue_818_triples_vendor_repr_tem with self.subTest(vendor=vendor_name): source = inspect.getsource(importlib.import_module(vendor_name)) repr_def = source[source.index("def __repr__(self) -> 'str':"):] - repr_line = repr_def.splitlines()[1] + repr_line = next(line for line in repr_def.splitlines()[1:] + if line.strip().startswith('return')) self.assertNotIn('%', repr_line, f'{vendor_name} template still emits a %-formatted __repr__; ' f'see GitHub issue #818') diff --git a/tests/project/test_conventions_doc_claims.py b/tests/project/test_conventions_doc_claims.py index c53ee04fe8..ceac7b0eb0 100644 --- a/tests/project/test_conventions_doc_claims.py +++ b/tests/project/test_conventions_doc_claims.py @@ -724,8 +724,8 @@ class AenumRoleExclusionTests(unittest.TestCase): #: The qualified sentinel references part B's fix relies on: the role, #: the dotted target, and how many times that exact pairing has to appear. QUALIFIED_SENTINEL_REFS = ( - (':class:', '~pcapkit.corekit.sentinels.AbsentType', 2), - (':class:', '~pcapkit.corekit.sentinels.NoValueType', 1), + (':class:', '~pcapkit.corekit.sentinels.AbsentType', 3), + (':class:', '~pcapkit.corekit.sentinels.NoValueType', 2), (':data:', '~pcapkit.corekit.sentinels.ABSENT', 1), ) @@ -1077,7 +1077,7 @@ def test_a_non_string_key_raises_a_value_miss_on_every_helper(self) -> 'None': row = page[start:page.index('\n * - ', start)] for klass in helpers: with self.subTest(named=klass.__name__): - self.assertIn(f'``{klass.__name__}``', row, + self.assertIn(f':class:`~{klass.__module__}.{klass.__name__}`', row, 'the audit table\'s helper row no longer names this helper') for klass in helpers: diff --git a/tests/project/test_docs_internal_refs_1940_unit.py b/tests/project/test_docs_internal_refs_1940_unit.py new file mode 100644 index 0000000000..8b03abf85a --- /dev/null +++ b/tests/project/test_docs_internal_refs_1940_unit.py @@ -0,0 +1,325 @@ +# -*- coding: utf-8 -*- +"""Pins the library-internal references #1940 turned from literals into roles. + +A library-internal name written as a double-backtick literal in rendered prose +never links, and neither does a role whose target has no documented entry. #1940 +converts those literals to Python-domain roles wherever the prose means the +object, and gives every object such a reference names an entry -- the public +classes that had none, and the private helpers and special methods that rendered +text refers to -- so that each reference has a target. + +* :class:`DocumentedEntryTests` -- the public classes that gained an ``autoclass``. +* :class:`HelperEntryTests` -- the private helpers and special methods that + rendered text names, each declared on an API page. +* :class:`ConvertedReferenceTests` -- a curated set of the converted references, + the two examples #1940 names among them, pinned as roles rather than literals + and as naming a declared entry. +* :class:`EnumOwnNewTests` -- the ``conf.py`` attribute getter that lets + ``:special-members: __new__`` document an enumeration's own ``__new__``. + +Whether a role resolves is a property of the built inventory, not of the source, +so the honest check is a ``sphinx-build`` run, recorded in the pull request. What +this pins is the textual precondition a plain (non-nitpicky) build would not +notice losing: the entry is declared, the role is written, and the dotted path it +names is a real object. + +""" + +from __future__ import annotations + +import enum +import importlib +import pathlib +import re +import types +import unittest +from typing import TYPE_CHECKING + +from tests.project.test_docs_ambiguous_xrefs_1775_unit import HAS_SPHINX, SPHINX_SKIP_REASON, load_conf + +if TYPE_CHECKING: + from typing import Any, Iterator + +ROOT = pathlib.Path(__file__).resolve().parents[2] +DOCS = ROOT / 'docs' / 'source' + +#: ``(page, dotted path)`` for every public class #1940 gave an entry. +ENTRIES = ( + ('pcapkit/const/ftp.rst', 'pcapkit.const.ftp.command.CommandType'), + ('pcapkit/const/ftp.rst', 'pcapkit.const.ftp.command.ConformanceRequirement'), + ('pcapkit/const/ftp.rst', 'pcapkit.const.ftp.return_code.ResponseKind'), + ('pcapkit/const/ftp.rst', 'pcapkit.const.ftp.return_code.GroupingInformation'), + ('pcapkit/protocols/application/httpv2.rst', 'pcapkit.protocols.data.application.httpv2.Settings'), + ('pcapkit/protocols/misc/pcapng.rst', 'pcapkit.protocols.schema.misc.pcapng.OptionEnumField'), +) + +#: Private helpers and special methods that rendered text names, and the page +#: that declares each. The changelog names several of them. +HELPERS = ( + ('pcapkit/const/ftp.rst', 'pcapkit.const.ftp.command.Command.__new__'), + ('pcapkit/const/reg.rst', 'pcapkit.const.reg.apptype.apptype.AppType.__eq__'), + ('pcapkit/dumpkit/pcap.rst', 'pcapkit.dumpkit.pcap.PCAPIO.__init__'), + ('pcapkit/foundation/extraction.rst', 'pcapkit.foundation.extraction.Extractor._note_eof_progress'), + ('pcapkit/foundation/extraction.rst', 'pcapkit.foundation.extraction.Extractor._owns_input'), + ('pcapkit/foundation/extraction.rst', 'pcapkit.foundation.extraction.Extractor._register_internal_engine'), + ('pcapkit/protocols/application/http.rst', 'pcapkit.protocols.application.http.HTTP.from_data'), + ('pcapkit/protocols/application/httpv2.rst', 'pcapkit.protocols.application.httpv2.HTTP.unpack'), + ('pcapkit/protocols/application/ngap.rst', 'pcapkit.protocols.application.ngap._convert'), + ('pcapkit/protocols/application/ngap.rst', 'pcapkit.protocols.application.ngap._revert'), + ('pcapkit/protocols/application/ngap.rst', 'pcapkit.protocols.application.ngap._PYCRATE'), + ('pcapkit/protocols/application/ngap.rst', 'pcapkit.protocols.application.ngap._PDU_LOCK'), + ('pcapkit/protocols/application/ospf.rst', 'pcapkit.protocols.application.ospf.OSPF._make_id_numbers'), + ('pcapkit/protocols/internet/esp.rst', 'pcapkit.protocols.internet.esp._resolve'), + ('pcapkit/protocols/internet/esp.rst', 'pcapkit.protocols.internet.esp._CRYPTO'), + ('pcapkit/protocols/internet/ipv6.rst', 'pcapkit.protocols.internet.ipv6.IPv6._read_ip_hextet'), + ('pcapkit/protocols/link/arp.rst', 'pcapkit.protocols.link.arp.ARP._make_addr_resolve'), + ('pcapkit/protocols/link/arp.rst', 'pcapkit.protocols.link.arp.ARP._make_proto_resolve'), + ('pcapkit/protocols/link/ethernet.rst', 'pcapkit.protocols.link.ethernet.Ethernet._make_mac_addr'), + ('pcapkit/protocols/misc/pcap.rst', 'pcapkit.protocols.misc.pcap.header.Header._read_protos'), + ('pcapkit/protocols/misc/pcapng.rst', 'pcapkit.protocols.misc.pcapng.PCAPNG.PACKET_TYPES'), + ('pcapkit/protocols/misc/pcapng.rst', 'pcapkit.protocols.misc.pcapng.PCAPNG._make_mac_addr'), + ('pcapkit/protocols/misc/pcapng.rst', 'pcapkit.protocols.schema.misc.pcapng.OptionEnumField.post_process'), + ('pcapkit/protocols/protocol.rst', 'pcapkit.protocols.protocol._declared_keywords'), + ('pcapkit/utilities/exceptions.rst', 'pcapkit.utilities.exceptions.BaseError.__init__'), + ('pcapkit/utilities/index.rst', 'pcapkit.utilities.compat.cached_property'), + ('pcapkit/utilities/index.rst', 'pcapkit.utilities.compat.unquote'), + ('pcapkit/vendor/default.rst', 'pcapkit.vendor.default.Vendor.__init__'), + ('pcapkit/vendor/reg.rst', 'pcapkit.vendor.reg.ethertype.EtherType._insert_range'), +) + +#: Helpers in :data:`HELPERS` documented without a docstring of their own: the vendor +#: template that generates their module is outside this change (#1920). +UNDOCSTRINGED = frozenset({'pcapkit.const.reg.apptype.apptype.AppType.__eq__'}) + +#: ``(source, literal it replaced, role now written)``. The first two are the +#: examples #1940 names. +CONVERTED = ( + ('docs/source/contributing/conventions/registry-protocol.rst', '``AppType.get``', + ':meth:`AppType.get `'), + ('docs/source/contributing/conventions/registry-protocol.rst', '``FrameType.Flags``', + ':class:`FrameType.Flags `'), + ('docs/source/contributing/conventions/registry-protocol.rst', '``CommandType``', + ':class:`~pcapkit.const.ftp.command.CommandType`'), + ('docs/source/contributing/conventions/sentinel-convention.rst', '``NoDefaultType``', + ':class:`~pcapkit.corekit.sentinels.NoDefaultType`'), + ('docs/source/contributing/conventions/documentation.rst', '``cached_property``', + ':class:`~pcapkit.utilities.compat.cached_property`'), + ('docs/source/contributing/releasing.rst', '``pcapkit.__version__``', + ':data:`pcapkit.__version__`'), + ('docs/source/pcapkit/const/ftp.rst', '``Command.get()``', + ':meth:`Command.get() `'), + ('pcapkit/corekit/fields/numbers.py', '``OptionEnumField.post_process``', + ':meth:`OptionEnumField.post_process `'), + ('pcapkit/corekit/sentinels.py', '``pcapkit.protocols.protocol._declared_keywords()``', + ':func:`pcapkit.protocols.protocol._declared_keywords`'), + ('pcapkit/foundation/extraction.py', '``Dumper``', + ':class:`~pcapkit.dumpkit.common.Dumper`'), + ('pcapkit/vendor/default.py', '``Vendor.__init__``', + ':meth:`Vendor.__init__ `'), +) + +#: A Python-domain role, bare or with an explicit title; group 2 is the target. +ROLE = re.compile(r':(mod|class|meth|func|attr|exc|data):`(?:[^`<]*<)?~?([\w.]+)>?`') + +#: An rst directive, with its name. +DIRECTIVE = re.compile(r'^(?P *)\.\. (?:py:)?(?P[\w-]+)::\s*(?P\S+)') + +#: Member options of an ``autoclass`` that may name members explicitly. +MEMBER_OPTIONS = ('members', 'special-members', 'private-members') + + +def _resolve(path: 'str') -> 'object': + """Import the longest module prefix of ``path`` and walk the rest as attributes.""" + parts = path.split('.') + for cut in range(len(parts), 0, -1): + try: + obj = importlib.import_module('.'.join(parts[:cut])) + except ImportError: + continue + for name in parts[cut:]: + obj = getattr(obj, name) + return obj + raise ImportError(path) + + +def declared(text: 'str') -> 'Iterator[str]': + """Every dotted name ``text``, an rst page, declares an entry for. + + That is an ``auto*`` directive naming it in full, a hand-written ``function``, + ``data`` or ``class`` directive under the current ``module``, and a member a + class directive names in a member option or in a nested ``auto*`` directive. + A bare ``:members:`` is reported as ``.*``, for :func:`is_declared`. + + """ + module = None + owner = None # type: Any + for line in text.splitlines(): + match = DIRECTIVE.match(line) + if match is None: + if owner is not None: + option = re.match(r'^ :([\w-]+):\s*(.*)$', line) + if option and option.group(1) in MEMBER_OPTIONS: + names = [name.strip() for name in option.group(2).split(',') if name.strip()] + for name in names: + yield f'{owner}.{name}' + if option.group(1) == 'members' and not names: + yield f'{owner}.*' + continue + indent, kind, name = len(match.group('indent')), match.group('kind'), match.group('name') + if kind in ('module', 'currentmodule'): + module, owner = name, None + elif indent == 0 and kind.startswith('auto'): + yield name + owner = name if kind in ('autoclass', 'autoexception') else None + elif indent == 0 and kind in ('function', 'data', 'class') and module: + yield f'{module}.{name.split("(")[0]}' + owner = None + elif indent and owner is not None and kind.startswith('auto'): + yield f'{owner}.{name}' + elif indent == 0: + owner = None + + +def is_declared(path: 'str', found: 'set[str]') -> 'bool': + """Whether ``path`` is in ``found``, or is a public member with a docstring of + its own on a class whose bare ``:members:`` documents it.""" + if path in found: + return True + owner, _, name = path.rpartition('.') + if f'{owner}.*' not in found or name.startswith('_'): + return False + value = vars(_resolve(owner)).get(name) + return bool(getattr(getattr(value, '__func__', value), '__doc__', None)) + + +def declared_anywhere() -> 'set[str]': + """Every dotted name an API page declares, the changelog excluded.""" + found = set() # type: set[str] + for path in sorted(DOCS.rglob('*.rst')): + if 'changelog' not in path.parts: + found.update(declared(path.read_text(encoding='utf-8'))) + return found + + +class DocumentedEntryTests(unittest.TestCase): + """Each public class that had no inventory entry now has an ``autoclass``.""" + + def test_pages_declare_the_entries(self) -> 'None': + for page, path in ENTRIES: + with self.subTest(path=path): + text = (DOCS / page).read_text(encoding='utf-8') + self.assertRegex(text, rf'(?m)^\.\. autoclass:: {re.escape(path)}\n :members:\n', + f'{page} has no autoclass with :members: for {path}') + + def test_entries_are_real_documented_classes(self) -> 'None': + for _, path in ENTRIES: + with self.subTest(path=path): + obj = _resolve(path) + self.assertIsInstance(obj, type) + self.assertTrue((obj.__doc__ or '').strip(), f'{path} has no docstring to render') + + +class HelperEntryTests(unittest.TestCase): + """Each private helper or special method rendered text names has an entry.""" + + def test_pages_declare_the_helpers(self) -> 'None': + for page, path in HELPERS: + with self.subTest(path=path): + text = (DOCS / page).read_text(encoding='utf-8') + self.assertTrue(is_declared(path, set(declared(text))), f'{page} declares no entry for {path}') + + def test_helpers_are_real_objects(self) -> 'None': + for _, path in HELPERS: + with self.subTest(path=path): + _resolve(path) + + def test_documented_functions_have_docstrings(self) -> 'None': + """A helper's entry renders its docstring, so a function must have one of its own.""" + for _, path in HELPERS: + if path in UNDOCSTRINGED: + continue + owner, _, name = path.rpartition('.') + parent = _resolve(owner) + obj = vars(parent).get(name) if isinstance(parent, type) else getattr(parent, name) + if name == '__new__' and isinstance(parent, enum.EnumMeta): + obj = vars(parent)['_new_member_'] + func = getattr(obj, '__func__', obj) + if isinstance(func, types.FunctionType): + with self.subTest(path=path): + self.assertTrue((func.__doc__ or '').strip(), f'{path} has no docstring') + + +class ConvertedReferenceTests(unittest.TestCase): + """A curated set of converted references stays a role, not a literal.""" + + def test_literal_is_now_a_role(self) -> 'None': + for source, literal, role in CONVERTED: + with self.subTest(source=source, literal=literal): + text = (ROOT / source).read_text(encoding='utf-8') + self.assertIn(role, text) + self.assertNotIn(literal, text) + + def test_role_targets_are_declared_entries(self) -> 'None': + found = declared_anywhere() + for _, _, role in CONVERTED: + with self.subTest(role=role): + match = ROLE.fullmatch(role) + self.assertIsNotNone(match, role) + _resolve(match.group(2)) + self.assertTrue(is_declared(match.group(2), found), f'no API page declares {match.group(2)}') + + +@unittest.skipUnless(HAS_SPHINX, SPHINX_SKIP_REASON) +class EnumOwnNewTests(unittest.TestCase): + """``read_own_enum_new`` answers ``__new__`` with the class body's own.""" + + conf = None # type: Any + + @classmethod + def setUpClass(cls) -> 'None': + cls.conf = load_conf() + + def test_registry_new_is_its_own(self) -> 'None': + from pcapkit.const.ftp.command import Command # pylint: disable=import-outside-toplevel + found = self.conf.read_own_enum_new(Command, '__new__') + self.assertEqual(found.__qualname__, 'Command.__new__') + self.assertTrue(found.__doc__) + self.assertIsNot(found, getattr(Command, '__new__')) + + def test_stdlib_enum_new_is_its_own(self) -> 'None': + class Coded(enum.Enum): + """An enumeration with a ``__new__`` of its own.""" + + def __new__(cls, value: 'int') -> 'Coded': + obj = object.__new__(cls) + obj._value_ = value + return obj + + ONE = 1 + + self.assertEqual(self.conf.read_own_enum_new(Coded, '__new__').__qualname__, + f'{Coded.__qualname__}.__new__') + + def test_other_names_and_enums_fall_through(self) -> 'None': + from pcapkit.const.ftp.command import Command # pylint: disable=import-outside-toplevel + + class Plain(enum.Enum): + """An enumeration without a ``__new__`` of its own.""" + + ONE = 1 + + self.assertEqual(self.conf.read_own_enum_new(Command, 'get'), Command.get) + self.assertIs(self.conf.read_own_enum_new(Plain, '__new__'), Plain.__new__) + self.assertEqual(self.conf.read_own_enum_new(Plain, 'missing', 42), 42) + + def test_setup_registers_it_for_every_enum(self) -> 'None': + from unittest import mock # pylint: disable=import-outside-toplevel + + from sphinx.application import Sphinx # pylint: disable=import-outside-toplevel + app = mock.create_autospec(Sphinx, instance=True) + self.conf.setup(app) + app.add_autodoc_attrgetter.assert_any_call(enum.EnumMeta, self.conf.read_own_enum_new) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/project/test_docs_unresolvable_roles_1919_unit.py b/tests/project/test_docs_unresolvable_roles_1919_unit.py index d6c4929c70..3fe84b05b5 100644 --- a/tests/project/test_docs_unresolvable_roles_1919_unit.py +++ b/tests/project/test_docs_unresolvable_roles_1919_unit.py @@ -159,14 +159,6 @@ class DeadTargetTests(unittest.TestCase): "cryptography's inventory has no top-level module; write it as a literal"), ('mod', 'cryptography.exceptions', "cryptography's inventory has no such module; write it as a literal"), - (None, '_declared_keywords', - 'a private helper with no entry; write it as a literal'), - (None, 'pcapkit.protocols.protocol._declared_keywords', - 'a private helper with no entry; write it as a literal'), - (None, 'OptionEnumField.post_process', - 'the pcapng schema field has no entry; write it as a literal'), - (None, 'pcapkit.protocols.schema.misc.pcapng.OptionEnumField.post_process', - 'the pcapng schema field has no entry; write it as a literal'), (None, 'pcapkit.vendor.__main__.run', 'the CLI module documents no functions; write it as a literal'), (None, 'EnumMeta.registry', From 78d68598db3299017c53d261b51b7a8fea5cf181 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Mon, 19 Oct 2026 13:50:22 -0400 Subject: [PATCH 2/2] docs: link the ipv6_ext page's next-layer lookup and drop timed test wording (#1940) - Point the ipv6_ext page's Internet._lookup_next_layer role at ProtocolBase._lookup_next_layer, where the method is documented, as the module docstring already does - Restate the #1940 test's docstrings and comments, and the conf.py getter's docstring, as standing facts rather than as what changed Refs #1940 --- docs/source/conf.py | 5 +++-- .../pcapkit/protocols/internet/ipv6_ext.rst | 2 +- .../project/test_docs_internal_refs_1940_unit.py | 16 ++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 545db3ab42..37d95451ad 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1093,8 +1093,9 @@ def read_own_enum_new(obj: type, name: str, *defargs: 'Any') -> 'Any': 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_``, so ``:special-members: __new__`` documented the machinery's - constructor rather than the one a registry row is built with (:issue:`1940`). + ``_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. diff --git a/docs/source/pcapkit/protocols/internet/ipv6_ext.rst b/docs/source/pcapkit/protocols/internet/ipv6_ext.rst index b4d64dc436..4d607ddd30 100644 --- a/docs/source/pcapkit/protocols/internet/ipv6_ext.rst +++ b/docs/source/pcapkit/protocols/internet/ipv6_ext.rst @@ -124,7 +124,7 @@ This class is reached in these ways: 1. **An unrecognised protocol number.** :attr:`~pcapkit.const.ipv6.extension_header.ExtensionHeader.Shim6` is a real IANA extension header (:rfc:`5533`) with no dedicated parser, so - :meth:`Internet._lookup_next_layer ` + :meth:`Internet._lookup_next_layer ` would default it to plain :class:`~pcapkit.protocols.misc.raw.Raw`. That has no ``next`` field, so the walk in :meth:`IPv6._decode_next_layer ` would diff --git a/tests/project/test_docs_internal_refs_1940_unit.py b/tests/project/test_docs_internal_refs_1940_unit.py index 8b03abf85a..f1db4fb3d9 100644 --- a/tests/project/test_docs_internal_refs_1940_unit.py +++ b/tests/project/test_docs_internal_refs_1940_unit.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- -"""Pins the library-internal references #1940 turned from literals into roles. +"""Pins library-internal references in rendered text as roles with a documented target (#1940). A library-internal name written as a double-backtick literal in rendered prose never links, and neither does a role whose target has no documented entry. #1940 converts those literals to Python-domain roles wherever the prose means the -object, and gives every object such a reference names an entry -- the public -classes that had none, and the private helpers and special methods that rendered -text refers to -- so that each reference has a target. +object, and every object such a reference names has an entry -- the public +classes, and the private helpers and special methods that rendered text refers +to -- so that each reference has a target. -* :class:`DocumentedEntryTests` -- the public classes that gained an ``autoclass``. +* :class:`DocumentedEntryTests` -- the public classes #1940 documents, each with an ``autoclass``. * :class:`HelperEntryTests` -- the private helpers and special methods that rendered text names, each declared on an API page. * :class:`ConvertedReferenceTests` -- a curated set of the converted references, @@ -91,7 +91,7 @@ #: template that generates their module is outside this change (#1920). UNDOCSTRINGED = frozenset({'pcapkit.const.reg.apptype.apptype.AppType.__eq__'}) -#: ``(source, literal it replaced, role now written)``. The first two are the +#: ``(source, literal, the role that replaced it)``. The first two are the #: examples #1940 names. CONVERTED = ( ('docs/source/contributing/conventions/registry-protocol.rst', '``AppType.get``', @@ -202,7 +202,7 @@ def declared_anywhere() -> 'set[str]': class DocumentedEntryTests(unittest.TestCase): - """Each public class that had no inventory entry now has an ``autoclass``.""" + """Each public class #1940 documents has an ``autoclass``.""" def test_pages_declare_the_entries(self) -> 'None': for page, path in ENTRIES: @@ -250,7 +250,7 @@ def test_documented_functions_have_docstrings(self) -> 'None': class ConvertedReferenceTests(unittest.TestCase): - """A curated set of converted references stays a role, not a literal.""" + """Each curated reference is a role, not a literal.""" def test_literal_is_now_a_role(self) -> 'None': for source, literal, role in CONVERTED: