diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index de373b6f3d..8ff23fd61c 100755 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -163,9 +163,15 @@ header records which of them are clean and what each of the others would need be `continue-on-error` line could be deleted. Read the job's run summary, where each step writes its verdict, and try not to add to the numbers. -`isort` is the exception: no `Lint` step runs it. It appears in `cron-vendor.yml` as a formatter -that rewrites the regenerated constants, and `tests/project/test_isort_clean.py`, which the `test` -extra's `isort` lets run, fails on a file whose imports `make isort` would reorder. +**The job is not entirely advisory, though.** It carries one more step, `actionlint`, which lints +the workflow YAML rather than `pcapkit/`, and that one has no `continue-on-error` and no +`Makefile` target — so a change under `.github/` can turn `Lint` red even though none of the +linters above can. The header records that split, and asks for a promoted step to be moved below +the advisory ones, since a failed step skips every step after it. + +`isort` is absent from the job altogether: no `Lint` step runs it. It appears in `cron-vendor.yml` +as a formatter that rewrites the regenerated constants, and `tests/project/test_isort_clean.py`, +which the `test` extra's `isort` lets run, fails on a file whose imports `make isort` would reorder. One trap: `make vermin` redirects its report into `temp/vermin.txt` rather than to your terminal, so when vermin finds code that misses `vermin.ini`'s `targets = 3.8` and exits 1, diff --git a/docs/source/conf.py b/docs/source/conf.py index 45afcc9242..9a574de102 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -183,7 +183,7 @@ # Those packages are therefore linked to their project pages instead, through # :data:`rst_epilog` below (#1936). -#: The label on the note in :file:`pcapkit/foundation/engines/3rdparty.rst` that +#: The label on the note in :file:`docs/source/pcapkit/foundation/engines/3rdparty.rst` that #: says which distribution installs the ``pcap`` module, which ``|pcap|_`` links #: to (:issue:`1948`). PCAP_MODULE_LABEL = 'pcap-module' diff --git a/docs/source/contributing/conventions/protocol-layer-placement.rst b/docs/source/contributing/conventions/protocol-layer-placement.rst index 43d5ff1573..ed81d320e9 100644 --- a/docs/source/contributing/conventions/protocol-layer-placement.rst +++ b/docs/source/contributing/conventions/protocol-layer-placement.rst @@ -191,20 +191,26 @@ Counting the names in each class's own namespace, whether new or overriding :class:`~pcapkit.protocols.protocol.ProtocolBase`'s, and including those :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 +``__post_init__``, ``__proto__``, ``__proto_key__``, ``__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` defines its own ``__proto__`` -- and each of +those also exists on :class:`~pcapkit.protocols.protocol.ProtocolBase`, so each still +resolves on :class:`~pcapkit.protocols.application.ospf.OSPF`. 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 +:class:`~pcapkit.protocols.protocol.ProtocolBase` is ``__proto_key__`` alone +(:issue:`2343`), which :class:`~pcapkit.protocols.link.link.Link` declares and neither of +the others does, so it does **not** resolve on +:class:`~pcapkit.protocols.application.ospf.OSPF`. Nothing turns on that: +:func:`~pcapkit.foundation.registry.protocols.register_protocol_code` reads the pairing +out of the registry owner's own namespace rather than off the class, and reads its +absence as *declares no pairing* rather than as an error. What changes is which registry it resolves *to*: ``OSPF.__proto__`` is :attr:`ProtocolBase.__proto__ ` rather than :attr:`Link.__proto__ `, so OSPF does not diff --git a/docs/source/contributing/conventions/registry-protocol.rst b/docs/source/contributing/conventions/registry-protocol.rst index 17a66d448d..c9df762763 100644 --- a/docs/source/contributing/conventions/registry-protocol.rst +++ b/docs/source/contributing/conventions/registry-protocol.rst @@ -74,7 +74,7 @@ generated registries spell by hand in ``_missing_``: if not (isinstance(value, int) and 0 <= value <= 0xFF): raise EnumValueError(f'{value!r} is not a valid {cls.__name__}') -**Every registry overrides it**, so that ``register`` refuses what ``get`` refuses +**Every registry has it overridden**, so that ``register`` refuses what ``get`` refuses (:issue:`1816`). Each vendor generator emits the override from the check it already writes at the top of ``_missing_``, with the same message. That is the ``FLAG`` of most crawlers, but a bespoke template's own range where its ``_missing_`` checks something diff --git a/docs/source/engines.rst b/docs/source/engines.rst index 8954346c9a..26ad1c025c 100644 --- a/docs/source/engines.rst +++ b/docs/source/engines.rst @@ -188,7 +188,7 @@ preflight described at the top of this page. Only the engines above carry extra constraints. :mod:`pcapkit` itself and its ``default``, ``dpkt`` and ``scapy`` engines need nothing beyond their - ``pip install`` -- |dpkt|_ 1.9.8 and ``scapy`` 2.7.0 were both measured + ``pip install`` -- |dpkt|_ 1.9.8 and ``scapy`` 2.8.0 were both measured reading a capture on Python 3.14. The ``scapy`` engine needs **scapy 2.6.0 or newer**, which the ``Scapy`` extra requires: 2.5.0 can drop a PCAP-NG interface's timestamp resolution, reading nanoseconds as microseconds diff --git a/docs/source/ext.rst b/docs/source/ext.rst index b7bd0ffba4..817964ce48 100644 --- a/docs/source/ext.rst +++ b/docs/source/ext.rst @@ -241,14 +241,14 @@ The following code snippet shows how to create a new protocol class: silently wrong field. The accepted set is read from :func:`inspect.signature` -- the union of every keyword-taking parameter of ``make``, ``read``, ``pack``, ``unpack``, ``__post_init__`` and ``__init__`` anywhere in the class's MRO -- which is - wider than ``make`` alone because :meth:`ProtocolBase.__post_init__ - ` hands the same + wider than ``make`` alone because :meth:`Protocol.__post_init__ + ` hands the same ``**kwargs`` to the construction and to the parse, so a keyword only ``read`` declares still travels through ``make``. Shapes a signature cannot express are declared on the class instead, via - :attr:`ProtocolBase.__keywords__ - `: + :attr:`Protocol.__keywords__ + `: .. code-block:: python @@ -271,8 +271,8 @@ The following code snippet shows how to create a new protocol class: subclass of a dispatcher is checked normally. Prefer a declared parameter to either: it is also what documents the keyword to your callers. - Parsing is unaffected. :meth:`ProtocolBase.from_data - ` checks the keywords its + Parsing is unaffected. :meth:`Protocol.from_data + ` checks the keywords its caller passes just as strictly, raising :exc:`~pcapkit.utilities.exceptions.UnsupportedCall`, but only warns with :exc:`~pcapkit.utilities.warnings.UnknownFieldWarning` for a key that comes @@ -281,8 +281,8 @@ The following code snippet shows how to create a new protocol class: .. warning:: - The check lives in :meth:`ProtocolBase.__init__ - `, where every producer's + The check lives in :meth:`Protocol.__init__ + `, where every producer's keywords converge, so it covers ``SomeProtocol(...)`` and the ``pack`` it leads to -- but **not** a direct ``SomeProtocol.make(...)`` call, which still discards an undeclared keyword in silence. ``object.__new__(cls).make(...)`` diff --git a/docs/source/pcapkit/protocols/internet/ipv6_ext.rst b/docs/source/pcapkit/protocols/internet/ipv6_ext.rst index 72775bc357..38cb0ab9b5 100644 --- a/docs/source/pcapkit/protocols/internet/ipv6_ext.rst +++ b/docs/source/pcapkit/protocols/internet/ipv6_ext.rst @@ -126,7 +126,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/pcapkit/corekit/sentinels.py b/pcapkit/corekit/sentinels.py index 80e12f6dc8..19f87a9690 100644 --- a/pcapkit/corekit/sentinels.py +++ b/pcapkit/corekit/sentinels.py @@ -571,8 +571,8 @@ def __bool__(self) -> 'Literal[False]': #: 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. -#: :attr:`ProtocolBase.__keywords__ -#: `. Never leaves +#: :attr:`Protocol.__keywords__ +#: `. Never leaves #: :mod:`pcapkit.protocols.protocol`, which imports it from here for exactly #: that one read. Private by documentation rather than by a #: leading underscore -- see :class:`AbsentType`'s own docstring. diff --git a/pcapkit/protocols/internet/internet.py b/pcapkit/protocols/internet/internet.py index a64b5c769e..5e986a7a83 100644 --- a/pcapkit/protocols/internet/internet.py +++ b/pcapkit/protocols/internet/internet.py @@ -182,7 +182,7 @@ def register(cls, code: 'Enum_TransType', protocol: 'ModuleDescriptor[ProtocolBa Arguments: code: protocol code as in :class:`~pcapkit.const.reg.transtype.TransType` protocol: module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass Raises: pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a diff --git a/pcapkit/protocols/internet/ipv6_ext.py b/pcapkit/protocols/internet/ipv6_ext.py index 3c36e311c1..06a149815f 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:`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 ` diff --git a/pcapkit/protocols/link/link.py b/pcapkit/protocols/link/link.py index 3b6a580571..228a38b71b 100644 --- a/pcapkit/protocols/link/link.py +++ b/pcapkit/protocols/link/link.py @@ -138,7 +138,7 @@ def register(cls, code: 'Enum_EtherType', protocol: 'ModuleDescriptor[ProtocolBa Arguments: code: protocol code as in :class:`~pcapkit.const.reg.ethertype.EtherType` protocol: module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass Raises: pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a @@ -158,7 +158,7 @@ def register(cls, code: 'Enum_EtherType', protocol: 'ModuleDescriptor[ProtocolBa Takes the protocol as one ``protocol`` argument, a :class:`~pcapkit.corekit.module.ModuleDescriptor` or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass, in place of + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass, in place of the ``module`` and ``class_`` names. """ diff --git a/pcapkit/protocols/protocol.py b/pcapkit/protocols/protocol.py index e17bc274a7..867f7eded4 100644 --- a/pcapkit/protocols/protocol.py +++ b/pcapkit/protocols/protocol.py @@ -1006,7 +1006,7 @@ def analyze(cls, proto: 'int', payload: 'bytes', **kwargs: 'Any') -> 'ProtocolBa **kwargs: Arbitrary keyword arguments. Returns: - Parsed payload as a :class:`~pcapkit.protocols.protocol.Protocol` + Parsed payload as a :class:`~pcapkit.protocols.protocol.ProtocolBase` instance. """ @@ -1039,7 +1039,7 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor | Type[ProtocolBase]' Arguments: code: protocol code protocol: module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass Raises: pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a diff --git a/pcapkit/protocols/schema/link/ethernet.py b/pcapkit/protocols/schema/link/ethernet.py index 02221eeaf6..a99871c8f7 100644 --- a/pcapkit/protocols/schema/link/ethernet.py +++ b/pcapkit/protocols/schema/link/ethernet.py @@ -29,7 +29,7 @@ def callback_payload(self: 'PayloadField', packet: 'dict[str, Any]') -> 'None': :obj:`None`; the resolved class is assigned to ``self.protocol``. Important: - The lookup goes through :meth:`ProtocolBase._lookup_next_layer + The lookup goes through :meth:`Protocol._lookup_next_layer ` rather than subscripting the registry. :attr:`Ethernet.__proto__ ` *is* diff --git a/pcapkit/protocols/schema/schema.py b/pcapkit/protocols/schema/schema.py index 0bbf3e1045..6d79c11507 100644 --- a/pcapkit/protocols/schema/schema.py +++ b/pcapkit/protocols/schema/schema.py @@ -1231,7 +1231,7 @@ class _EnumRegistry(collections.defaultdict): The fallback itself is deliberate -- it is how an unknown option, chunk or block falls back to its ``Unknown*``/``Unassigned*`` schema -- so this subclass keeps returning it and just stops recording it. - :meth:`ProtocolBase._lookup_registry + :meth:`Protocol._lookup_registry ` does the same for the protocol-layer ``__proto__`` family. diff --git a/pcapkit/protocols/transport/sctp.py b/pcapkit/protocols/transport/sctp.py index 041411cfd1..45d217f73a 100644 --- a/pcapkit/protocols/transport/sctp.py +++ b/pcapkit/protocols/transport/sctp.py @@ -734,7 +734,7 @@ def register(cls, code: 'Enum_PayloadProtocolIdentifier | int', protocol: 'Modul code: payload protocol identifier (PPID), as in :class:`~pcapkit.const.sctp.payload_protocol_identifier.PayloadProtocolIdentifier` protocol: module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass Important: SCTP overrides :meth:`Transport.register diff --git a/pcapkit/protocols/transport/transport.py b/pcapkit/protocols/transport/transport.py index efb96647e1..dbe5134fdf 100644 --- a/pcapkit/protocols/transport/transport.py +++ b/pcapkit/protocols/transport/transport.py @@ -79,7 +79,7 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor[ProtocolBase] | Type[ Arguments: code: port number protocol: module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass Important: This method must be called from a non-abstract class, as the @@ -115,7 +115,7 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor[ProtocolBase] | Type[ Takes the protocol as one ``protocol`` argument, a :class:`~pcapkit.corekit.module.ModuleDescriptor` or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass, in place of + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass, in place of the ``module`` and ``class_`` names. """ @@ -141,7 +141,7 @@ def analyze(cls, ports: 'tuple[int, int]', payload: 'bytes', **kwargs: 'Any') -> **kwargs: Arbitrary keyword arguments. Returns: - Parsed payload as a :class:`~pcapkit.protocols.protocol.Protocol` + Parsed payload as a :class:`~pcapkit.protocols.protocol.ProtocolBase` instance. """ diff --git a/pcapkit/vendor/ipx/packet.py b/pcapkit/vendor/ipx/packet.py index 2297fca217..7b5b14cfd7 100644 --- a/pcapkit/vendor/ipx/packet.py +++ b/pcapkit/vendor/ipx/packet.py @@ -86,7 +86,8 @@ #: ``IPX_PACKET_TYPE_IPX 0``, ``_RIP 1``, ``_ECHO 2``, ``_ERROR 3``, ``_PEP 4``, #: ``_SPX 5``, ``_NCP 17`` and ``_WANBCAST 20`` -- the same values in the #: same order. Types 1-5 are inherited from Xerox XNS IDP, which IPX was derived -#: from; no RFC assigns them, and the notes below say so where that is the case. +#: from, and -- type 4 apart -- no RFC assigns them. The note on each row below +#: says which sources do and do not carry its number. DATA = { # NOTE: not a protocol assignment. The scraped table listed 0 as "Unknown", # and it doubles as the IPX protocol's own default for the ``type`` field. diff --git a/pcapkit/vendor/reg/linktype.py b/pcapkit/vendor/reg/linktype.py index dd089913fa..97f225cbee 100644 --- a/pcapkit/vendor/reg/linktype.py +++ b/pcapkit/vendor/reg/linktype.py @@ -36,10 +36,10 @@ class LinkType(Vendor): LINK = 'http://www.tcpdump.org/linktypes.html' #: Destinations of a bare ``LinkType`` member; see - #: :attr:`~pcapkit.vendor.default.Vendor.PROTO_DEST`. Two of them, because a - #: link-layer header type keys the next-layer registry of both capture file - #: formats, and :func:`~pcapkit.foundation.registry.protocols.register_linktype` - #: already registers into both by hand. + #: :attr:`~pcapkit.vendor.default.Vendor.PROTO_DEST`. One per capture file + #: format, because a link-layer header type keys the next-layer registry of + #: each, and :func:`~pcapkit.foundation.registry.protocols.register_linktype` + #: already registers into every one of them by hand. PROTO_DEST = ( ModuleDescriptor('pcapkit.protocols.misc.pcap.frame', 'Frame'), ModuleDescriptor('pcapkit.protocols.misc.pcapng', 'PCAPNG'), diff --git a/util/changelog_md.py b/util/changelog_md.py index 6a7014d4ec..39686923a2 100644 --- a/util/changelog_md.py +++ b/util/changelog_md.py @@ -178,11 +178,11 @@ _RFC_ROLE = re.compile(r':rfc:`(\d+)(?:#([\w.-]+))?`') #: Anchor prefixes Sphinx renders as words rather than as part of the target, -#: from ``sphinx.roles._format_rfc_target`` (read from Sphinx 9.1.0, the version -#: this repository builds its documentation with): ``#section-3`` is titled -#: ``Section 3``, and an anchor of any other shape is left exactly as written. -#: Kept level with Sphinx so the Markdown link and the rendered documentation say -#: the same thing about the same page, which is the whole point of rule 2. +#: from ``sphinx.roles._format_rfc_target`` (read from Sphinx 9.1.0): ``#section-3`` +#: is titled ``Section 3``, and an anchor of any other shape is left exactly as +#: written. Kept level with the Sphinx that ``docs/requirements.txt`` pins, so the +#: Markdown link and the rendered documentation say the same thing about the same +#: page, which is the whole point of rule 2. _RFC_ANCHORS = frozenset({'appendix', 'page', 'section'}) #: The Sphinx configuration whose ``extlinks`` table :data:`EXTLINKS` mirrors.