diff --git a/docs/source/pcapkit/const/tcp.rst b/docs/source/pcapkit/const/tcp.rst index 9492a0e38..5a713da71 100644 --- a/docs/source/pcapkit/const/tcp.rst +++ b/docs/source/pcapkit/const/tcp.rst @@ -69,7 +69,7 @@ which is automatically generated from :class:`pcapkit.vendor.tcp.flags.Flags`. .. versionchanged:: 1.5.0 A value outside the 16-bit field raises :exc:`ValueError`, where - :mod:`aenum` composed a pseudo-member for any integer. + ``aenum`` composed a pseudo-member for any integer. .. autoclass:: pcapkit.const.tcp.flags.Flags :members: diff --git a/docs/source/pcapkit/dumpkit/common.rst b/docs/source/pcapkit/dumpkit/common.rst index de7d55dbd..032946438 100644 --- a/docs/source/pcapkit/dumpkit/common.rst +++ b/docs/source/pcapkit/dumpkit/common.rst @@ -4,7 +4,7 @@ Common Utilities .. module:: pcapkit.dumpkit.common :mod:`pcapkit.dumpkit.common` holds the utilities shared by -:mod:`pcapkit.dumpkit`, chiefly customised hooks for :class:`dictdumper.Dumper` +:mod:`pcapkit.dumpkit`, chiefly customised hooks for :class:`dictdumper.dumper.Dumper` classes. .. autofunction:: pcapkit.dumpkit.common.make_dumper diff --git a/docs/source/pcapkit/foundation/engines/3rdparty.rst b/docs/source/pcapkit/foundation/engines/3rdparty.rst index 12592a313..c392e0bb1 100644 --- a/docs/source/pcapkit/foundation/engines/3rdparty.rst +++ b/docs/source/pcapkit/foundation/engines/3rdparty.rst @@ -14,7 +14,7 @@ support, as is used by :class:`pcapkit.foundation.extraction.Extractor`. .. note:: - Constructing this engine imports :mod:`scapy.all`, which populates `Scapy`_'s + Constructing this engine imports ``scapy.all``, which populates `Scapy`_'s layer registries -- ``conf.l2types`` and the ``bind_layers`` payload table, both of which exist only as import side effects of the layer modules. Importing a narrower submodule leaves them empty, and @@ -26,9 +26,9 @@ support, as is used by :class:`pcapkit.foundation.extraction.Extractor`. ``Ethernet / IP / UDP / Raw``, and such a list would go stale silently as `Scapy`_ gains layer types. - One side effect: :mod:`scapy.all` loads :mod:`scapy.layers.dcerpc`, which + One side effect: ``scapy.all`` loads :mod:`scapy.layers.dcerpc`, which reaches `Scapy`_'s TLS layer and there triggers a - ``CryptographyDeprecationWarning`` from :mod:`cryptography` about + ``CryptographyDeprecationWarning`` from ``cryptography`` about finite-field Diffie-Hellman. It concerns a key-exchange code path :mod:`pcapkit` never executes, but it subclasses :exc:`UserWarning` rather than :exc:`DeprecationWarning`, so Python's default filters show it. @@ -193,7 +193,7 @@ support, as is used by :class:`pcapkit.foundation.extraction.Extractor`. .. important:: - `pcap-ct`_ installs the same top-level :mod:`pcap` module as `PyPCAP`_. This + `pcap-ct`_ installs the same top-level ``pcap`` module as `PyPCAP`_. This 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 @@ -308,8 +308,8 @@ support, as is used by :class:`pcapkit.foundation.extraction.Extractor`. interface rather than 1.3.0. Every attribute this engine touches -- the ``pcap.pcap(name=..., promisc=...)`` - constructor, :meth:`~pcap.pcap.datalink`, ``snaplen``, iteration yielding - ``(timestamp, bytes)``, and :meth:`~pcap.pcap.close` -- is present on both and + constructor, ``pcap.pcap.datalink``, ``snaplen``, iteration yielding + ``(timestamp, bytes)``, and ``pcap.pcap.close`` -- is present on both and behaves identically, byte for byte and timestamp for timestamp, on ``pcap-ct`` 1.3.0b3 against ``pypcap`` 1.3.0 -- a statement about those versions, not a guarantee from upstream. ``PCAP_CT`` is therefore not @@ -441,7 +441,7 @@ Backend Detection .. module:: pcapkit.foundation.engines._pcap_backend -Unrelated PyPI distributions install a top-level module named :mod:`pcap` -- +Unrelated PyPI distributions install a top-level module named ``pcap`` -- `PyPCAP`_, a Cython binding shipped as a single extension module, and `pcap-ct`_, a :mod:`ctypes` reimplementation shipped as a package. They therefore collide, and ``import pcap`` resolves to whichever the import system finds first. diff --git a/docs/source/pcapkit/protocols/index.rst b/docs/source/pcapkit/protocols/index.rst index b0dfc55c0..efc89b8b8 100644 --- a/docs/source/pcapkit/protocols/index.rst +++ b/docs/source/pcapkit/protocols/index.rst @@ -230,6 +230,8 @@ Internal Definitions :no-members: :show-inheritance: + .. autoproperty:: registry + .. autoclass:: pcapkit.protocols.schema.schema._EnumRegistry :no-members: :show-inheritance: diff --git a/docs/source/pcapkit/utilities/index.rst b/docs/source/pcapkit/utilities/index.rst index 6a3f05937..2ec32ded8 100644 --- a/docs/source/pcapkit/utilities/index.rst +++ b/docs/source/pcapkit/utilities/index.rst @@ -66,3 +66,18 @@ following objects and functions: - Python 3.10 * - :class:`types.UnionType`, or :data:`None` below it - Python 3.10 + +.. function:: final(f) + + :func:`typing.final` from Python 3.11 on, and ``typing_extensions.final`` + below it. Both record ``__final__ = True`` on the decorated object at + runtime, which :class:`~pcapkit.corekit.infoclass.Info` and + :class:`~pcapkit.protocols.schema.schema.Schema` read to refuse a class + marked ``@final`` without :func:`~pcapkit.corekit.infoclass.info_final` or + :func:`~pcapkit.protocols.schema.schema.schema_final`. Plain + :func:`typing.final` records nothing below 3.11 (gh-90500). + + .. versionchanged:: 1.5.0 + + Taken from ``typing_extensions`` below Python 3.11 rather than 3.8, so + that ``__final__`` is recorded on every supported version (:issue:`778`). diff --git a/docs/source/pcapkit/utilities/warnings.rst b/docs/source/pcapkit/utilities/warnings.rst index 3252ecfcd..be37430db 100644 --- a/docs/source/pcapkit/utilities/warnings.rst +++ b/docs/source/pcapkit/utilities/warnings.rst @@ -23,7 +23,7 @@ channel**: ``pcapkit`` logger sees every complaint whatever the warning filters say; 2. the standard :mod:`warnings` machinery, via :func:`warnings.warn`, subject to the filters -- so :func:`warnings.filterwarnings`, - :func:`warnings.catch_warnings`, :mod:`pytest`'s ``filterwarnings``, + :class:`warnings.catch_warnings`, :mod:`pytest`'s ``filterwarnings``, :option:`-W` and :envvar:`PYTHONWARNINGS` govern :mod:`pcapkit` warnings exactly as they govern any other library's (with one wrinkle in naming a category on the command line, noted below). @@ -91,7 +91,7 @@ configured separately, through the :mod:`logging` module:: the first snippet above restores the 1.4.x quiet. In 1.4.x, constructing a :class:`~pcapkit.utilities.warnings.BaseWarning` installed an ``ignore`` filter for its own class, so a caller could not re-enable them. The filter - went at the *front* of the process-global :data:`warnings.filters`, so it + went at the *front* of the process-global ``warnings.filters``, so it also overrode the host application's own configuration for those categories and made unrelated warnings re-fire (:issue:`364`). diff --git a/pcapkit/corekit/enum.py b/pcapkit/corekit/enum.py index fea478722..f8c8cf678 100644 --- a/pcapkit/corekit/enum.py +++ b/pcapkit/corekit/enum.py @@ -119,13 +119,13 @@ class EnumLookup: on top; a closed enumeration inherits this one directly and so is never handed a ``register`` it would have to refuse. - This is a plain mix-in rather than an :class:`~aenum.Enum` subclass, because + This is a plain mix-in rather than an ``aenum.Enum`` subclass, because an enumeration that already has members cannot be subclassed. Mixed in *before* the member type -- ``class Foo(EnumRegistry, IntFlag)``, or ``class Bar(EnumLookup, IntEnum)`` -- it contributes methods only, so - :mod:`aenum` still resolves the member data type from the enumeration base: - ``int`` for :class:`~aenum.IntEnum` and :class:`~aenum.IntFlag`, ``str`` for - :class:`~aenum.StrEnum`. That is what lets one base serve each of them, where a + ``aenum`` still resolves the member data type from the enumeration base: + ``int`` for ``aenum.IntEnum`` and ``aenum.IntFlag``, ``str`` for + ``aenum.StrEnum``. That is what lets one base serve each of them, where a generated template fragment would have needed a separate rendering per member type. @@ -134,20 +134,20 @@ class EnumLookup: ``class Foo(EnumRegistry, IntFlag)`` resolves as ``Foo -> EnumRegistry -> EnumLookup -> IntFlag -> int -> ...``, so ``_member_type_`` still comes from the enumeration base and not from anything in this module. Had this tier - subclassed :class:`~aenum.Enum` in order to "be an enum", it would have + subclassed ``aenum.Enum`` in order to "be an enum", it would have become the member type itself and broken every one of these shapes at once. The methods deliberately touch only ``_member_map_``, ``_member_names_`` and - ``_value2member_map_``, which both :mod:`enum` and :mod:`aenum` maintain, so - nothing on this tier depends on :mod:`aenum` internals at all -- the - :func:`~aenum.extend_enum` call in this module belongs to + ``_value2member_map_``, which both :mod:`enum` and ``aenum`` maintain, so + nothing on this tier depends on ``aenum`` internals at all -- the + ``aenum.extend_enum`` call in this module belongs to :class:`EnumRegistry`, which is the tier that mutates. """ if TYPE_CHECKING: #: The enumeration machinery's own lookup tables and member data type, - #: declared here because they are contributed by the :class:`~aenum.Enum` + #: declared here because they are contributed by the ``aenum.Enum`` #: base this mix-in is combined with rather than by the mix-in itself. _member_map_: 'dict[str, Self]' _member_names_: 'list[str]' @@ -303,7 +303,7 @@ def get(cls, key: 'Any', default: 'Any' = NO_DEFAULT) -> 'Self': :class:`~pcapkit.const.pcapng.tls_key_label.TLSKeyLabel`) mints on any path, so no live witness exists in this tree. :class:`~pcapkit.const.mh.cga_type.CGAType`, which still mints directly - via :func:`~aenum.extend_enum`, is :class:`int`-valued, so + via ``aenum.extend_enum``, is :class:`int`-valued, so a ``str`` name could not reach its mint branch even if this restriction did not exist; it is not an exception to it, just not reachable by it. This is about a future ``str``-valued registry (or a present one whose @@ -390,7 +390,7 @@ def get(cls, key: 'Any', default: 'Any' = NO_DEFAULT) -> 'Self': typically :exc:`~pcapkit.utilities.exceptions.EnumValueError` from a subclass's :meth:`_validate_value` -- is re-raised as it stands rather than wrapped, so the subclass's own message survives and no second - error is constructed. Only :mod:`aenum`'s and :mod:`enum`'s own "no + error is constructed. Only ``aenum``'s and :mod:`enum`'s own "no member carries this value" is converted, into a quiet :exc:`~pcapkit.utilities.exceptions.EnumValueError`. This is the same discrimination :meth:`EnumField.post_process @@ -520,8 +520,8 @@ def register(cls, value: 'Any', name: 'str') -> 'Self': Contrast :meth:`get` and ``_missing_``, which resolve without naming anything. Refuses a ``value`` that already has a member. Without this guard, - :func:`~aenum.extend_enum` does not mint anything for an already-taken - value -- :mod:`aenum` treats that as a request to *alias* the existing + ``aenum.extend_enum`` does not mint anything for an already-taken + value -- ``aenum`` treats that as a request to *alias* the existing member under the caller's ``name`` instead, silently: the call returns the *existing* member, ``name`` becomes reachable in ``__members__`` pointing at it, and ``_member_names_`` does not grow. That is @@ -565,7 +565,7 @@ def register(cls, value: 'Any', name: 'str') -> 'Self': :meth:`~EnumLookup._validate_value` rejects ``value``. The base implementation of that hook accepts everything, so this cannot arise on a registry that does not override it. - ValueError: If ``name`` is already taken. :mod:`aenum` reports that + ValueError: If ``name`` is already taken. ``aenum`` reports that as :exc:`TypeError`; it is translated so that the ways one call can fail are one exception type. @@ -583,7 +583,7 @@ def register(cls, value: 'Any', name: 'str') -> 'Self': @classmethod def _extend(cls, value: 'Any', name: 'str') -> 'Self': - """The raw :func:`~aenum.extend_enum` call, shared by :meth:`register` + """The raw ``aenum.extend_enum`` call, shared by :meth:`register` and :meth:`register_alias`. Neither public method calls the other: :meth:`register` refuses an @@ -603,7 +603,7 @@ def _extend(cls, value: 'Any', name: 'str') -> 'Self': The member now reachable under ``name``, new or existing. Raises: - ValueError: If ``name`` is already taken. :mod:`aenum` reports that + ValueError: If ``name`` is already taken. ``aenum`` reports that as :exc:`TypeError`; it is translated so that the ways one call can fail are one exception type. @@ -642,7 +642,7 @@ def register_alias(cls, value: 'Any', name: 'str') -> 'Self': :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 - :func:`~aenum.extend_enum`, so its iteration grows too. + ``aenum.extend_enum``, so its iteration grows too. Args: value: Value of the existing member to alias. @@ -674,7 +674,7 @@ def register_aliases(cls, value: 'Any', *names: 'str') -> 'tuple[Self, ...]': Raises: ValueError: As :meth:`register_alias`. Names before the failing one - stay registered -- :func:`~aenum.extend_enum` has no transaction + stay registered -- ``aenum.extend_enum`` has no transaction to roll back, and undoing it by hand would mean reaching further into enumeration internals than anything else here does. @@ -691,7 +691,7 @@ def _unregistered_member(cls, value: 'Any', name: 'str') -> 'Self': that does. The member is constructed through ``cls._member_type_``, which - :mod:`aenum` sets from the enumeration base, so this serves ``int``- and + ``aenum`` sets from the enumeration base, so this serves ``int``- and ``str``-valued registries alike without either having to say which it is. diff --git a/pcapkit/corekit/fields/numbers.py b/pcapkit/corekit/fields/numbers.py index b29f2fbb0..830b1cb41 100644 --- a/pcapkit/corekit/fields/numbers.py +++ b/pcapkit/corekit/fields/numbers.py @@ -693,7 +693,7 @@ def post_process(self, value: 'int | bytes', packet: 'dict[str, Any]') -> 'Stdli Notes: The registry is consulted through its constructor, which raises for a value no member and no ``_missing_`` rule accounts for. That raise - is :mod:`aenum`'s own bare :exc:`ValueError`: not one of + is ``aenum``'s own bare :exc:`ValueError`: not one of :mod:`pcapkit.utilities.exceptions`, so a caller cannot tell it from a bug of its own, and not an :exc:`EOFError`, so :meth:`Extractor.record_frames @@ -719,19 +719,19 @@ def post_process(self, value: 'int | bytes', packet: 'dict[str, Any]') -> 'Stdli ```` never takes -- and one an :class:`int`-keyed dispatch registry looks up by value like any declared member. It is built per value rather than grafted onto - the registry with :func:`aenum.extend_enum`, for two reasons: a + the registry with ``aenum.extend_enum``, for two reasons: a capture carrying many distinct unassigned codes would otherwise grow a process-global registry without bound, which is the growth :meth:`ProtocolBase._lookup_registry ` exists to avoid; and a stdlib :class:`enum.IntEnum` registry is then - handled exactly like an :class:`aenum.IntEnum` one. + handled exactly like an ``aenum.IntEnum`` one. Only a *foreign* rejection is absorbed. A registry rejecting a value with one of :mod:`pcapkit.utilities.exceptions` has made a deliberate decision that this layer -- which sees only that the value arrived in a field of some width -- is in no position to overrule, so - an in-library error propagates unchanged and it is only :mod:`aenum`'s + an in-library error propagates unchanged and it is only ``aenum``'s and :mod:`enum`'s "no member has this value" that becomes a pseudo-member. That is what keeps the fallback from being an unconditional ``except ValueError: pass``. @@ -761,7 +761,7 @@ def post_process(self, value: 'int | bytes', packet: 'dict[str, Any]') -> 'Stdli .. versionchanged:: 1.5.0 A value the registry has no member for resolves to a nameless - pseudo-member, where :mod:`aenum`'s bare :exc:`ValueError` escaped + pseudo-member, where ``aenum``'s bare :exc:`ValueError` escaped (:issue:`701`). """ @@ -798,7 +798,7 @@ def _pseudo_member(self, value: 'int') -> 'StdlibEnum | AenumEnum': Returns: A single-member, throwaway :class:`enum.IntEnum` instance, built - fresh per call rather than :func:`~aenum.extend_enum`-ed onto + fresh per call rather than ``aenum.extend_enum``-ed onto ``self._namespace``, per this method's own docstring above. """ @@ -828,15 +828,14 @@ def _unregistered_member(namespace: 'Type[StdlibEnum] | Type[AenumEnum]', ``cls.__members_ns__[...] = ...`` line every registry in this package uses to record a member it mints. No entry is added to ``_member_map_`` or ``_value2member_map_`` either, since those are - only ever touched by the metaclass machinery :func:`aenum.extend_enum` + only ever touched by the metaclass machinery ``aenum.extend_enum`` drives, which this bypasses completely. Note: Building a member this way, rather than as some other type altogether, is why :meth:`PortEnumField.post_process` and - :meth:`OptionEnumField.post_process - ` - need this rather than :meth:`_pseudo_member`: + ``OptionEnumField.post_process`` (in + :mod:`pcapkit.protocols.schema.misc.pcapng`) 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 @@ -1035,7 +1034,7 @@ class PortEnumField(EnumField): Important: This class is specifically designed for :class:`~pcapkit.const.reg.apptype.apptype.AppType` - as it is actually a :class:`~aenum.StrEnum` class. + as it is actually an ``aenum.StrEnum`` class. .. versionadded:: 1.5.0 diff --git a/pcapkit/corekit/infoclass.py b/pcapkit/corekit/infoclass.py index 555e90484..9900c0072 100644 --- a/pcapkit/corekit/infoclass.py +++ b/pcapkit/corekit/infoclass.py @@ -588,8 +588,8 @@ def items(self, multi: 'bool' = False) -> 'ItemsView[str, VT] | Iterator[tuple[s Args: multi: If set to :obj:`True`, an iterator with a pair for each value of each key, as :meth:`to_dict` writes them. Otherwise a view of - the pairs of the first value of each key, as - :meth:`Mapping.items ` returns. + the pairs of the first value of each key, as the ``items`` + method of :class:`collections.abc.Mapping` returns. """ if not multi: diff --git a/pcapkit/corekit/io.py b/pcapkit/corekit/io.py index 9818b236f..ff49c090d 100644 --- a/pcapkit/corekit/io.py +++ b/pcapkit/corekit/io.py @@ -857,7 +857,7 @@ def __getattr__(self, name: 'str') -> 'Any': class NamedStream: """Read-only proxy that gives a stream the ``name`` attribute. - :func:`pcapfile.savefile.load_savefile` dereferences ``input_file.name`` + ``pcapfile.savefile.load_savefile`` dereferences ``input_file.name`` unconditionally, on the way into its trace helper. That is fine for the :class:`~io.BufferedReader` :class:`~pcapkit.foundation.extraction.Extractor` normally holds, but not for the :class:`~pcapkit.corekit.io.SeekableReader` diff --git a/pcapkit/corekit/sentinels.py b/pcapkit/corekit/sentinels.py index 73d3e4b21..ded221b9c 100644 --- a/pcapkit/corekit/sentinels.py +++ b/pcapkit/corekit/sentinels.py @@ -551,7 +551,7 @@ def __bool__(self) -> 'Literal[False]': #: AbsentType: Absent-versus-:obj:`None` sentinel for -#: :func:`pcapkit.protocols.protocol._declared_keywords` to read a class's +#: ``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/dumpkit/common.py b/pcapkit/dumpkit/common.py index 2a7d7e5a7..b09c53ecb 100644 --- a/pcapkit/dumpkit/common.py +++ b/pcapkit/dumpkit/common.py @@ -6,7 +6,7 @@ :mod:`pcapkit.dumpkit.common` is the collection of common utility functions for :mod:`pcapkit.dumpkit` implementation, which is -generally the customised hooks for :class:`dictdumper.Dumper` +generally the customised hooks for :class:`dictdumper.dumper.Dumper` classes. """ @@ -233,7 +233,7 @@ def render_enum(o: 'enum.Enum | aenum.Enum') -> 'str': cannot be mistaken for a member name, since a Python identifier may not begin with a digit. - This is *not* an :mod:`aenum` quirk. A stdlib :class:`enum.IntFlag` built + This is *not* an ``aenum`` quirk. A stdlib :class:`enum.IntFlag` built from the same members answers ``name is None`` identically on CPython 3.14.7, so the guard belongs here rather than in a choice of enumeration library. @@ -498,7 +498,8 @@ def object_hook(self, o: 'Any') -> 'Any': way out reaches every string the writer will ever interpolate raw, not merely the ones built directly in this method. The one exception is a mapping *key*: - :meth:`~dictdumper.dumper.Dumper._append_dict` writes those + :meth:`~dictdumper.plist.PLIST._append_dict` and + :meth:`~dictdumper.json.JSON._append_object` write those straight from the mapping without ever calling this method on them, so both branches that hand the writer a mapping -- a :class:`~pcapkit.corekit.multidict.MultiDict` and a plain diff --git a/pcapkit/foundation/engines/__init__.py b/pcapkit/foundation/engines/__init__.py index 532603fa7..00127f84b 100644 --- a/pcapkit/foundation/engines/__init__.py +++ b/pcapkit/foundation/engines/__init__.py @@ -7,10 +7,14 @@ :mod:`pcapkit.foundation.engines` is a collection of engines support for :mod:`pcapkit`, including but not limited to the built-in PCAP and `PCAP-NG`_ file support, :mod:`Scapy `, -:mod:`PyShark `, :mod:`DPKT `, :mod:`PyPCAP `, -`pcap-ct`_ and :mod:`PyPCAPFile ` 3rd party engine support. +`PyShark`_, `DPKT`_, `PyPCAP`_, `pcap-ct`_ and `PyPCAPFile`_ 3rd party +engine support. +.. _PyShark: https://kiminewt.github.io/pyshark +.. _DPKT: https://dpkt.readthedocs.io +.. _PyPCAP: https://github.com/pynetwork/pypcap .. _pcap-ct: https://pypi.org/project/pcap-ct/ +.. _PyPCAPFile: https://github.com/kisom/pypcapfile .. _PCAP-NG: https://wiki.wireshark.org/Development/PcapNg diff --git a/pcapkit/foundation/engines/_pcap_backend.py b/pcapkit/foundation/engines/_pcap_backend.py index ed7c6282e..6d946bd52 100644 --- a/pcapkit/foundation/engines/_pcap_backend.py +++ b/pcapkit/foundation/engines/_pcap_backend.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -"""Which distribution owns the :mod:`pcap` module? +"""Which distribution owns the ``pcap`` module? ===================================================== .. module:: pcapkit.foundation.engines._pcap_backend -Unrelated PyPI distributions install a top-level module named :mod:`pcap`: +Unrelated PyPI distributions install a top-level module named ``pcap``: * `PyPCAP`_ -- a Cython binding, shipped as a single extension module (:file:`pcap.cpython-*.so`), installable only up to Python 3.11. @@ -51,7 +51,7 @@ PYPCAP = 'pypcap' #: Distribution name of `pcap-ct`_. PCAP_CT = 'pcap-ct' -#: Every distribution known to provide :mod:`pcap`, in a fixed order so that +#: Every distribution known to provide ``pcap``, in a fixed order so that #: messages naming several of them read the same way every time. DISTRIBUTIONS = (PYPCAP, PCAP_CT) #: The ``engine=`` string that drives each distribution. Kept here rather than in @@ -65,7 +65,7 @@ @info_final class Probe(Info): - """What one attempt to import :mod:`pcap` found.""" + """What one attempt to import ``pcap`` found.""" #: Distribution that provided the imported module -- :data:`PYPCAP`, #: :data:`PCAP_CT`, or :data:`None` when the import did not succeed. @@ -104,9 +104,9 @@ def describe(self) -> 'str': def identify(module: 'ModuleType') -> 'str': - """Which distribution does an imported :mod:`pcap` module come from? + """Which distribution does an imported ``pcap`` module come from? - `pcap-ct`_ ships :mod:`pcap` as a package whose ``__init__`` does + `pcap-ct`_ ships ``pcap`` as a package whose ``__init__`` does ``from ._pcap import *``, which binds the submodule as an attribute; upstream `PyPCAP`_ ships a single extension module, which has no such attribute. That is a structural difference rather than a cosmetic one, which is why it is @@ -119,7 +119,7 @@ def identify(module: 'ModuleType') -> 'str': present on upstream ``pypcap`` 1.3.0 as well. Args: - module: An already-imported :mod:`pcap` module. + module: An already-imported ``pcap`` module. Returns: :data:`PCAP_CT` or :data:`PYPCAP`. @@ -134,7 +134,7 @@ def identify(module: 'ModuleType') -> 'str': def installed_distributions() -> 'tuple[str, ...]': """Which of :data:`DISTRIBUTIONS` are installed, per package metadata. - Asked of the metadata rather than of :mod:`pcap` itself, because that is the + Asked of the metadata rather than of ``pcap`` itself, because that is the only way to see the distribution the import did *not* resolve to -- which is exactly the collision worth reporting. @@ -190,7 +190,7 @@ def _purge() -> 'None': def probe() -> 'Probe': - """Import :mod:`pcap` and report what was found. + """Import ``pcap`` and report what was found. Not cached. The cost after a successful first call is a :data:`sys.modules` lookup, and a cache would make the answer depend on when it was first asked -- @@ -234,7 +234,7 @@ def probe() -> 'Probe': def wrong_backend_reason(wanted: 'str', found: 'Probe') -> 'Optional[str]': - """Why ``wanted`` cannot run, when some *other* distribution owns :mod:`pcap`. + """Why ``wanted`` cannot run, when some *other* distribution owns ``pcap``. Args: wanted: The distribution the calling engine drives -- :data:`PYPCAP` or diff --git a/pcapkit/foundation/engines/dpkt.py b/pcapkit/foundation/engines/dpkt.py index 31be55e33..47d4c3e90 100644 --- a/pcapkit/foundation/engines/dpkt.py +++ b/pcapkit/foundation/engines/dpkt.py @@ -80,7 +80,7 @@ class _DPKTLoopbackModule(Protocol): def Loopback(self) -> 'Type[DPKTLoopback]': ... class _DPKTPackage(Protocol): - """:mod:`dpkt`, as :class:`DPKT` reads it through :attr:`DPKT._expkg` (#1878). + """``dpkt``, as :class:`DPKT` reads it through :attr:`DPKT._expkg` (#1878). Each attribute is a submodule, described by what the engine reads of it. mypy matches a module with a protocol at the top level only, so the @@ -116,7 +116,7 @@ def loopback(self) -> '_DPKTLoopbackModule': ... class PCAPNGReader: """PCAP-NG reader for the DPKT engine, aware of every interface. - :class:`dpkt.pcapng.Reader` reads only the first Interface Description + ``dpkt.pcapng.Reader`` reads only the first Interface Description Block (IDB) of a file, and applies its link type and timestamp resolution to every packet (:issue:`1379`). This reader walks the blocks itself instead: it parses each Section Header Block and IDB with :mod:`pcapkit`'s @@ -126,7 +126,7 @@ class PCAPNGReader: blocks are still decoded by `DPKT`_. It yields Enhanced Packet Blocks, (obsolete) Packet Blocks and Simple Packet - Blocks, and skips every other block. :class:`dpkt.pcapng.Reader` skips the + Blocks, and skips every other block. ``dpkt.pcapng.Reader`` skips the Simple Packet Block too, and `DPKT`_ has no type for one, so that block is parsed with :class:`~pcapkit.protocols.misc.pcapng.PCAPNG` as well (:issue:`1520`). @@ -329,7 +329,7 @@ class _Reader(dpkt.pcap.Reader): def __init__(self, fileobj: 'IO[bytes]') -> 'None': super().__init__(fileobj) missing = [name for name in private if not hasattr(self, name)] - #: Whether to read with :meth:`dpkt.pcap.Reader.__iter__` unchanged. + #: Whether to read with ``dpkt.pcap.Reader.__iter__`` unchanged. self._upstream = bool(missing) if missing: warn(f"dpkt {getattr(dpkt, '__version__', '(unknown version)')}: " @@ -338,7 +338,7 @@ def __init__(self, fileobj: 'IO[bytes]') -> 'None': 'PCAP record gives its captured length instead', DPKTWarning, stacklevel=stacklevel()) - # NOTE: the loop is :meth:`dpkt.pcap.Reader.__iter__`'s, over the stream + # NOTE: the loop is ``dpkt.pcap.Reader.__iter__``'s, over the stream # and the record header type that method reads them through. A generator, # so ``dpkt.pcap.Reader.__init__`` calling it runs none of it before # :attr:`_upstream` is set. The timestamp is a ``Decimal`` for a @@ -428,7 +428,7 @@ class DPKT(EngineBase['DPKTPacket']): """DPKT engine support. A PCAP-NG file is read by :class:`PCAPNGReader` rather than by - :class:`dpkt.pcapng.Reader`, so each packet takes the link type and the + ``dpkt.pcapng.Reader``, so each packet takes the link type and the timestamp resolution and offset of the interface it was captured on. It reads the Enhanced, the Simple and the (obsolete) Packet Block, and @@ -442,7 +442,7 @@ class DPKT(EngineBase['DPKTPacket']): """ if TYPE_CHECKING: - #: Engine extraction package, i.e. :mod:`dpkt`. + #: Engine extraction package, i.e. ``dpkt``. _expkg: '_DPKTPackage' #: Engine extraction temporary storage. _extmp: 'Reader' @@ -477,7 +477,7 @@ def run(self) -> 'None': """Call :class:`dpkt.pcap.Reader` to extract PCAP files. This method assigns :attr:`self._expkg ` - as :mod:`dpkt` and :attr:`self._extmp ` + as ``dpkt`` and :attr:`self._extmp ` as an iterator from :class:`dpkt.pcap.Reader`, or from :class:`PCAPNGReader` for a PCAP-NG file. diff --git a/pcapkit/foundation/engines/engine.py b/pcapkit/foundation/engines/engine.py index c6aa0535d..52fd9d387 100644 --- a/pcapkit/foundation/engines/engine.py +++ b/pcapkit/foundation/engines/engine.py @@ -291,14 +291,14 @@ def __init_subclass__(cls, /, engine: 'Optional[str]' = None, *args: 'Any', **kw Note: The keyword is ``engine`` rather than ``name`` because ``name`` cannot be passed as a class keyword at all on Python 3.10: - :meth:`abc.ABCMeta.__new__` takes ``mcls``, ``name``, ``bases`` and + ``abc.ABCMeta.__new__`` takes ``mcls``, ``name``, ``bases`` and ``namespace`` as positional-*or-keyword* parameters before 3.11, so a class keyword by any of those names collides with one of them and the class statement raises :exc:`TypeError` from the metaclass before this method is reached. ``engine`` is outside that set, so the documented registration path works on every supported version. Measured on 3.10.21, 3.11.15 and 3.14.7; those - names are the whole of the :meth:`abc.ABCMeta.__new__` collision + names are the whole of the ``abc.ABCMeta.__new__`` collision surface. Separately, and for an unrelated reason that holds on every version, ``metaclass`` is not usable as a class keyword either: a ``class`` statement consumes it to choose the metaclass, so it never diff --git a/pcapkit/foundation/engines/pcap_ct.py b/pcapkit/foundation/engines/pcap_ct.py index f984e7fa5..e2d7d3360 100644 --- a/pcapkit/foundation/engines/pcap_ct.py +++ b/pcapkit/foundation/engines/pcap_ct.py @@ -31,13 +31,13 @@ from pcapkit.foundation.extraction import Extractor class _PCAPCTPackage(Protocol): - """:mod:`pcap`, as :class:`PCAP_CT` reads it through :attr:`PCAP_CT._expkg` (#1878).""" + """``pcap``, as :class:`PCAP_CT` reads it through :attr:`PCAP_CT._expkg` (#1878).""" @property def pcap(self) -> 'Type[Handle]': ... #: A `pcap-ct`_ "frame": the ``(timestamp, bytes)`` pair that - #: :class:`pcap.pcap` iteration yields. Deliberately *not* named ``Frame``, + #: ``pcap.pcap`` iteration yields. Deliberately *not* named ``Frame``, #: so that it is not mistaken for #: :class:`pcapkit.protocols.misc.pcap.frame.Frame`, which is what the #: built-in engines return. @@ -51,7 +51,7 @@ class PCAP_CT(EngineBase['RawFrame']): the `libpcap`_ package, which supplies the :manpage:`libpcap(3)` bindings (and ships a vendored copy of the library that it does not, by default, use -- see the warning below). Both distributions install a - top-level :mod:`pcap` module and expose the same interface, so this engine is + top-level ``pcap`` module and expose the same interface, so this engine is a sibling of :class:`~pcapkit.foundation.engines.pypcap.PyPCAP` rather than a replacement for it -- see :attr:`__engine_module__` for how the two are told apart, and :doc:`the engine documentation @@ -84,7 +84,7 @@ class PCAP_CT(EngineBase['RawFrame']): rely on it. Setting ``LIBPCAP = tcpdump`` in :file:`libpcap.cfg` selects the vendored copy instead -- verified loading, and reporting libpcap 1.10.6. - Being the same interface, it has the same limits. Offline, :mod:`pcap` is a + Being the same interface, it has the same limits. Offline, ``pcap`` is a savefile reader and nothing more: iteration yields the ``(timestamp, bytes)`` pair from :c:func:`pcap_next_ex` and no protocol dissection is performed. Consequently this engine @@ -117,7 +117,7 @@ class PCAP_CT(EngineBase['RawFrame']): """ if TYPE_CHECKING: - #: Engine extraction package, i.e. :mod:`pcap`. + #: Engine extraction package, i.e. ``pcap``. _expkg: '_PCAPCTPackage' #: Capture handle, kept separately from the iterator it is read through #: so that :meth:`close` does not depend on ``iter(handle) is handle``. @@ -128,7 +128,7 @@ class PCAP_CT(EngineBase['RawFrame']): _dlink: 'Enum_LinkType' #: Closed flag, so that the handle is not closed twice. _closed: 'bool' - #: What the :mod:`pcap` import actually found, cf. :attr:`backend`. + #: What the ``pcap`` import actually found, cf. :attr:`backend`. _backend: '_pcap_backend.Probe' ########################################################################## @@ -139,12 +139,12 @@ class PCAP_CT(EngineBase['RawFrame']): __engine_name__ = 'PCAP_CT' #: Engine module name. Note that this is ``pcap._pcap``, not ``pcap``: - #: `pcap-ct`_ and upstream `PyPCAP`_ both install a top-level :mod:`pcap`, + #: `pcap-ct`_ and upstream `PyPCAP`_ both install a top-level ``pcap``, #: so importing that name cannot tell which of the two is present, and #: :meth:`Extractor.import_test #: ` decides engine #: availability by import alone. ``pcap._pcap`` is the `pcap-ct`_ - #: implementation module; upstream ships :mod:`pcap` as a single extension + #: implementation module; upstream ships ``pcap`` as a single extension #: module rather than a package, so the submodule import fails there and the #: two engines stay distinguishable. __engine_module__ = 'pcap._pcap' @@ -189,7 +189,7 @@ def unsupported_reason(cls) -> 'Optional[str]': Consulted by :meth:`pcapkit.foundation.extraction.Extractor.run` *before* the import test, and it answers questions the import test cannot. - **Which distribution owns** :mod:`pcap`. Upstream `PyPCAP`_ owns that name + **Which distribution owns** ``pcap``. Upstream `PyPCAP`_ owns that name just as legitimately, and ``import pcap._pcap`` -- what :attr:`__engine_module__` names -- separates them only as long as upstream keeps shipping a single extension module. Asking @@ -218,7 +218,7 @@ def unsupported_reason(cls) -> 'Optional[str]': which the import test then reports correctly on its own. Returns: - A phrase naming the real cause, or :data:`None`. A :mod:`pcap` that is + A phrase naming the real cause, or :data:`None`. A ``pcap`` that is merely absent returns :data:`None`, since the import test reports that case in its own words. @@ -242,7 +242,7 @@ def dlink(self) -> 'Enum_LinkType': def backend(self) -> 'str': """The distribution, version and location this engine is driving. - More than one distribution provides :mod:`pcap`, so "the pcap-ct engine failed" is + More than one distribution provides ``pcap``, so "the pcap-ct engine failed" is not a complete statement of what ran. This says which one it was, e.g. ``pcap-ct 1.3.0b3 (/.../pcap/__init__.py)``. @@ -257,7 +257,7 @@ def __init__(self, extractor: 'Extractor') -> 'None': """Initialise the engine. Warns: - EngineWarning: If both distributions that provide :mod:`pcap` are + EngineWarning: If both distributions that provide ``pcap`` are installed. Only one of them can win the import -- measured on Python 3.10 with both present, the ``pcap-ct`` package wins and upstream's extension module is shadowed -- so the other is @@ -310,11 +310,11 @@ def __init__(self, extractor: 'Extractor') -> 'None': ########################################################################## def run(self) -> 'None': - """Call :class:`pcap.pcap` to extract PCAP files. + """Call ``pcap.pcap`` to extract PCAP files. This method assigns :attr:`self._expkg ` - as :mod:`pcap`, :attr:`self._handle ` as the - :class:`pcap.pcap` capture handle and :attr:`self._extmp + as ``pcap``, :attr:`self._handle ` as the + ``pcap.pcap`` capture handle and :attr:`self._extmp ` as an iterator over it. Warns: @@ -417,7 +417,7 @@ def read_frame(self) -> 'RawFrame': """Read frames with pcap-ct engine. Returns: - The ``(timestamp, bytes)`` pair as yielded by :class:`pcap.pcap`. + The ``(timestamp, bytes)`` pair as yielded by ``pcap.pcap``. Note: The timestamp is seconds since the epoch as a :class:`float`. @@ -471,14 +471,14 @@ def read_frame(self) -> 'RawFrame': def close(self) -> 'None': """Close engine. - This method closes the underlying :class:`pcap.pcap` handle. It is + This method closes the underlying ``pcap.pcap`` handle. It is idempotent, as :meth:`Extractor._cleanup ` and :meth:`Extractor.__exit__ ` may both reach it. Note: - ``pcap-ct`` 1.3.0b3's own :meth:`pcap.pcap.close` happens to tolerate + ``pcap-ct`` 1.3.0b3's own ``pcap.pcap.close`` happens to tolerate a second call, where upstream ``pypcap`` 1.3.0 segfaults on one. The :attr:`_closed ` flag is kept regardless: it is not this engine's business to depend on a pre-release's undocumented diff --git a/pcapkit/foundation/engines/pypcap.py b/pcapkit/foundation/engines/pypcap.py index d620be29e..a979ede7d 100644 --- a/pcapkit/foundation/engines/pypcap.py +++ b/pcapkit/foundation/engines/pypcap.py @@ -38,12 +38,12 @@ from pcapkit.foundation.extraction import Extractor class _PyPCAPPackage(Protocol): - """:mod:`pcap`, as :class:`PyPCAP` reads it through :attr:`PyPCAP._expkg` (#1878).""" + """``pcap``, as :class:`PyPCAP` reads it through :attr:`PyPCAP._expkg` (#1878).""" @property def pcap(self) -> 'Type[Handle]': ... - #: A PyPCAP "frame": the ``(timestamp, bytes)`` pair that :class:`pcap.pcap` + #: A PyPCAP "frame": the ``(timestamp, bytes)`` pair that ``pcap.pcap`` #: iteration yields. Deliberately *not* named ``Frame``, so that it is not #: mistaken for :class:`pcapkit.protocols.misc.pcap.frame.Frame`, which is #: what the built-in engines return. @@ -76,7 +76,7 @@ class PyPCAP(EngineBase['RawFrame']): interface that does install there, and :class:`~pcapkit.foundation.engines.pcap_ct.PCAP_CT` is the engine for it. - Because `pcap-ct`_ installs the same top-level :mod:`pcap` module, this + Because `pcap-ct`_ installs the same top-level ``pcap`` module, this engine checks which distribution it got and refuses the other one rather than running under the wrong name -- see :meth:`__init__`. @@ -87,12 +87,12 @@ class PyPCAP(EngineBase['RawFrame']): extractor: :class:`~pcapkit.foundation.extraction.Extractor` instance. Raises: - UnsupportedCall: If the installed :mod:`pcap` is `pcap-ct`_ rather than + UnsupportedCall: If the installed ``pcap`` is `pcap-ct`_ rather than upstream `PyPCAP`_. """ if TYPE_CHECKING: - #: Engine extraction package, i.e. :mod:`pcap`. + #: Engine extraction package, i.e. ``pcap``. _expkg: '_PyPCAPPackage' #: Engine extraction temporary storage. _extmp: 'Iterator[RawFrame]' @@ -100,7 +100,7 @@ class PyPCAP(EngineBase['RawFrame']): _dlink: 'Enum_LinkType' #: Closed flag, so that the handle is not closed twice. _closed: 'bool' - #: What the :mod:`pcap` import actually found, cf. :attr:`backend`. + #: What the ``pcap`` import actually found, cf. :attr:`backend`. _backend: '_pcap_backend.Probe' ########################################################################## @@ -157,7 +157,7 @@ def unsupported_reason(cls) -> 'Optional[str]': distributions install happily and either could be the one in place. Returns: - A phrase naming the real cause, or :data:`None`. An absent :mod:`pcap` + A phrase naming the real cause, or :data:`None`. An absent ``pcap`` returns :data:`None`, since the import test reports that case in its own words and duplicating it would produce two warnings for one problem. @@ -181,7 +181,7 @@ def dlink(self) -> 'Enum_LinkType': def backend(self) -> 'str': """The distribution, version and location this engine is driving. - More than one distribution provides :mod:`pcap`, so "the PyPCAP engine failed" is + More than one distribution provides ``pcap``, so "the PyPCAP engine failed" is not a complete statement of what ran. This says which one it was, e.g. ``pypcap 1.3.0 (/.../pcap.cpython-310-x86_64-linux-gnu.so)``. @@ -196,14 +196,14 @@ def __init__(self, extractor: 'Extractor') -> 'None': """Initialise the engine. Warns: - EngineWarning: If both distributions that provide :mod:`pcap` are + EngineWarning: If both distributions that provide ``pcap`` are installed. Only one of them can win the import, so the other is shadowed and unreachable -- a state worth naming, because ``engine=`` then selects an engine that cannot run and nothing else would say why. Raises: - UnsupportedCall: If the installed :mod:`pcap` is `pcap-ct`_ rather + UnsupportedCall: If the installed ``pcap`` is `pcap-ct`_ rather than upstream `PyPCAP`_. This is the same condition :meth:`unsupported_reason` reports, kept here as well because that hook is only consulted by @@ -252,11 +252,11 @@ def __init__(self, extractor: 'Extractor') -> 'None': ########################################################################## def run(self) -> 'None': - """Call :class:`pcap.pcap` to extract PCAP files. + """Call ``pcap.pcap`` to extract PCAP files. This method assigns :attr:`self._expkg ` - as :mod:`pcap` and :attr:`self._extmp ` - as an iterator from :class:`pcap.pcap`. + as ``pcap`` and :attr:`self._extmp ` + as an iterator from ``pcap.pcap``. Warns: AttributeWarning: Warns under the following circumstances: @@ -330,7 +330,7 @@ def read_frame(self) -> 'RawFrame': """Read frames with PyPCAP engine. Returns: - The ``(timestamp, bytes)`` pair as yielded by :class:`pcap.pcap`. + The ``(timestamp, bytes)`` pair as yielded by ``pcap.pcap``. See Also: Please refer to :meth:`PCAP.read_frame ` @@ -373,11 +373,11 @@ def read_frame(self) -> 'RawFrame': def close(self) -> 'None': """Close engine. - This method closes the underlying :class:`pcap.pcap` handle. It is + This method closes the underlying ``pcap.pcap`` handle. It is idempotent, as :meth:`Extractor._cleanup ` and :meth:`Extractor.__exit__ ` - may both reach it, and :meth:`pcap.pcap.close` is not safe to call twice. + may both reach it, and ``pcap.pcap.close`` is not safe to call twice. """ if self._closed or self._extmp is None: diff --git a/pcapkit/foundation/engines/pypcapfile.py b/pcapkit/foundation/engines/pypcapfile.py index 02a676d76..a4d93952d 100644 --- a/pcapkit/foundation/engines/pypcapfile.py +++ b/pcapkit/foundation/engines/pypcapfile.py @@ -37,24 +37,24 @@ from pcapkit.foundation.extraction import Extractor class _PCAPFileLinkLayerModule(Protocol): - """:mod:`pcapfile.linklayer`, as :class:`PyPCAPFile` reads it.""" + """``pcapfile.linklayer``, as :class:`PyPCAPFile` reads it.""" def clookup(self, ll_type: 'int') -> 'Optional[Type[PCAPFileEthernet]]': ... class _PCAPFileSavefileModule(Protocol): - """:mod:`pcapfile.savefile`, as :class:`PyPCAPFile` reads it.""" + """``pcapfile.savefile``, as :class:`PyPCAPFile` reads it.""" def load_savefile(self, input_file: 'NamedStream', layers: 'int' = ..., verbose: 'bool' = ..., lazy: 'bool' = ...) -> 'PCAPFileSavefile': ... class _PCAPFileStructsModule(Protocol): - """:mod:`pcapfile.structs`, as :class:`PyPCAPFile` reads it.""" + """``pcapfile.structs``, as :class:`PyPCAPFile` reads it.""" @property def pcap_packet(self) -> 'Type[PCAPFilePacket]': ... class _PCAPFilePackage(Protocol): - """:mod:`pcapfile`, as :class:`PyPCAPFile` reads it through :attr:`PyPCAPFile._expkg` (#1878). + """``pcapfile``, as :class:`PyPCAPFile` reads it through :attr:`PyPCAPFile._expkg` (#1878). Each attribute is a submodule, described by what the engine reads of it. mypy matches a module with a protocol at the top level only, so the @@ -104,14 +104,14 @@ class PyPCAPFile(EngineBase['PCAPFilePacket']): """ if TYPE_CHECKING: - #: Engine extraction package, i.e. :mod:`pcapfile`. + #: Engine extraction package, i.e. ``pcapfile``. _expkg: '_PCAPFilePackage' #: Engine extraction temporary storage. _extmp: 'Iterator[PCAPFilePacket]' #: Data link layer protocol, from the savefile header. _dlink: 'Enum_LinkType' #: Link layer decoder for this savefile, or :data:`None` when - #: :mod:`pcapfile` has none for its link layer type. + #: ``pcapfile`` has none for its link layer type. _declf: 'Optional[Callable[..., Any]]' #: Number of layers to descend while decoding, i.e. link plus network. See @@ -120,7 +120,7 @@ class PyPCAPFile(EngineBase['PCAPFilePacket']): #: First Python version `PyPCAPFile`_ does not work on, as a #: ``(major, minor)`` pair. Released 0.12.0 imports :mod:`imp` from - #: :mod:`pcapfile.linklayer`, and :mod:`imp` was removed in Python 3.12. + #: ``pcapfile.linklayer``, and :mod:`imp` was removed in Python 3.12. PYTHON_CEILING = (3, 12) @classmethod @@ -129,9 +129,9 @@ def unsupported_reason(cls) -> 'Optional[str]': Consulted by :meth:`pcapkit.foundation.extraction.Extractor.run` *before* the import test, because the import test cannot answer this question. - :mod:`pcapfile`'s top-level package imports perfectly well on Python 3.12 - and newer -- it is :mod:`pcapfile.linklayer` that fails, and - :mod:`pcapfile.savefile` imports it -- so a guard that only tries + ``pcapfile``'s top-level package imports perfectly well on Python 3.12 + and newer -- it is ``pcapfile.linklayer`` that fails, and + ``pcapfile.savefile`` imports it -- so a guard that only tries ``import pcapfile`` is satisfied and the :exc:`ModuleNotFoundError` then escapes from :meth:`__init__` as a hard error instead of degrading to the default engine with a warning, which is what happens when the package is @@ -140,7 +140,7 @@ def unsupported_reason(cls) -> 'Optional[str]': The version is checked rather than the import attempted so that the answer does not depend on which of the package's submodules happens to be imported first, and so it is the same answer on a machine that has never - installed :mod:`pcapfile` at all. + installed ``pcapfile`` at all. Returns: A short phrase naming the limitation, or :data:`None`. @@ -193,22 +193,22 @@ def __init__(self, extractor: 'Extractor') -> 'None': ########################################################################## def run(self) -> 'None': - """Call :func:`pcapfile.savefile.load_savefile` to extract PCAP files. + """Call ``pcapfile.savefile.load_savefile`` to extract PCAP files. This method assigns :attr:`self._expkg ` - as :mod:`pcapfile` and :attr:`self._extmp ` + as ``pcapfile`` and :attr:`self._extmp ` as an iterator over the lazily generated savefile packets. - :func:`pcapfile.savefile.load_savefile` loads and validates the savefile + ``pcapfile.savefile.load_savefile`` loads and validates the savefile header. The records are then read by :meth:`_read_records`, not by the packet generator it returns, which drops records (see there). Like - :func:`pcapfile.savefile._read_a_packet` with ``layers=0``, + ``pcapfile.savefile._read_a_packet`` with ``layers=0``, :meth:`_read_records` does *not* hand back each frame's bytes verbatim -- it hexlifies the whole frame into ASCII text and stops there, exactly as - :mod:`pcapfile` does for any layer at which it runs out of layers left to + ``pcapfile`` does for any layer at which it runs out of layers left to descend. It is :meth:`_decode` that un-hexlifies and then decodes each - frame to :attr:`LAYERS` depth using :func:`pcapfile.linklayer.clookup` -- - the very call :mod:`pcapfile` makes internally. Doing it this way round is + frame to :attr:`LAYERS` depth using ``pcapfile.linklayer.clookup`` -- + the very call ``pcapfile`` makes internally. Doing it this way round is what lets the link layer type be inspected (and reported on) *before* the first frame is decoded. @@ -219,14 +219,14 @@ def run(self) -> 'None': and/or :attr:`self.extractor._exptl ` is provided as the PyPCAPFile engine does not support such operations. - * if IPv6 reassembly is enabled, as :mod:`pcapfile` has no IPv6 + * if IPv6 reassembly is enabled, as ``pcapfile`` has no IPv6 decoder. - * if :mod:`pcapfile` has no decoder for the savefile's link layer + * if ``pcapfile`` has no decoder for the savefile's link layer type, in which case frames are left undecoded. Raises: FormatError: If the file format is not supported, i.e., not a PCAP - file. :mod:`pcapfile` reads libpcap savefiles only. + file. ``pcapfile`` reads libpcap savefiles only. """ from pcapkit.foundation.engines.pcap import PCAP # isort:skip @@ -330,13 +330,13 @@ def read_frame(self) -> 'PCAPFilePacket': ########################################################################## def _get_decoder(self, linktype: 'int') -> 'Optional[Callable[..., Any]]': - """Return the :mod:`pcapfile` link layer decoder for a link layer type. + """Return the ``pcapfile`` link layer decoder for a link layer type. Args: linktype: Link layer type code, from the savefile header. Returns: - The decoder class, or :data:`None` when :mod:`pcapfile` has none. + The decoder class, or :data:`None` when ``pcapfile`` has none. Warns: AttributeWarning: If no decoder is available, as frames will then be @@ -360,7 +360,7 @@ def _read_records(self, file: 'NamedStream', header: 'PCAPFileHeader') -> 'Iterator[PCAPFilePacket]': """Read the savefile's records, every one of them. - :func:`pcapfile.savefile._read_a_packet` (pypcapfile 0.12.0) drops + ``pcapfile.savefile._read_a_packet`` (pypcapfile 0.12.0) drops records: * It picks the byte order of each record header by testing @@ -372,19 +372,19 @@ def _read_records(self, file: 'NamedStream', than its capture length, so a zero-length record drops itself and every record after it, and a truncated final record is dropped (:issue:`1571`). - This reads each record in the byte order of the header :mod:`pcapfile` - loaded, into the :class:`pcapfile.structs.pcap_packet` that function + This reads each record in the byte order of the header ``pcapfile`` + loaded, into the ``pcapfile.structs.pcap_packet`` that function builds with ``layers=0`` -- the frame hexlified, pointing at the savefile's own header -- so :meth:`_decode` and - :mod:`pcapkit.toolkit.pypcapfile` see what :mod:`pcapfile` hands them. + :mod:`pcapkit.toolkit.pypcapfile` see what ``pcapfile`` hands them. It stops at a tail too short for a record header, and keeps a record whose capture length runs past the data: the capture length as declared, the octets present as the frame. Args: file: The savefile, just past its header, where - :func:`pcapfile.savefile.load_savefile` leaves it. - header: The savefile header :mod:`pcapfile` loaded. + ``pcapfile.savefile.load_savefile`` leaves it. + header: The savefile header ``pcapfile`` loaded. Yields: Undecoded savefile packets, one per record. @@ -414,12 +414,12 @@ def _read_records(self, file: 'NamedStream', def _decode(self, packet: 'PCAPFilePacket', frnum: 'int') -> 'PCAPFilePacket': """Decode a raw savefile packet down to :attr:`LAYERS` depth. - A new :class:`pcapfile.structs.pcap_packet` is built rather than the + A new ``pcapfile.structs.pcap_packet`` is built rather than the given one mutated, so that a decoding failure leaves the original intact. Args: packet: Undecoded savefile packet, i.e. as loaded with ``layers=0``, - whose :attr:`packet.packet ` + whose ``packet.packet`` is therefore the hexlified frame (see :meth:`run`), not the raw bytes. frnum: Frame number, for the warning message below. @@ -429,7 +429,7 @@ def _decode(self, packet: 'PCAPFilePacket', frnum: 'int') -> 'PCAPFilePacket': decoded, or holds no octets at all. Warns: - AttributeWarning: If :mod:`pcapfile` could not decode the frame, e.g. + AttributeWarning: If ``pcapfile`` could not decode the frame, e.g. because the capture is truncated. One bad frame should not abort the extraction, but it should not pass silently either. @@ -441,9 +441,10 @@ def _decode(self, packet: 'PCAPFilePacket', frnum: 'int') -> 'PCAPFilePacket': try: # ``packet.packet`` is hexlified ASCII text, not raw bytes -- see - # :meth:`run`. ``self._declf`` (e.g. :class:`pcapfile.protocols - # .linklayer.ethernet.Ethernet`) unpacks its own header straight out - # of its first argument via :func:`struct.unpack`, so it must be + # :meth:`run`. ``self._declf`` (e.g. + # ``pcapfile.protocols.linklayer.ethernet.Ethernet``) unpacks its + # own header straight out of its first argument via + # :func:`struct.unpack`, so it must be # un-hexlified back to raw bytes first, or every field it decodes # comes out garbage despite raising nothing. decoded = self._declf(binascii.unhexlify(packet.packet), layers=self.LAYERS - 1) diff --git a/pcapkit/foundation/engines/pyshark.py b/pcapkit/foundation/engines/pyshark.py index 2e7401545..8793f21d7 100644 --- a/pcapkit/foundation/engines/pyshark.py +++ b/pcapkit/foundation/engines/pyshark.py @@ -31,7 +31,7 @@ from pcapkit.foundation.extraction import Extractor class _PySharkPackage(Protocol): - """:mod:`pyshark`, as :class:`PyShark` reads it through :attr:`PyShark._expkg` (#1878).""" + """``pyshark``, as :class:`PyShark` reads it through :attr:`PyShark._expkg` (#1878).""" @property def FileCapture(self) -> 'Type[PySharkFileCapture]': ... @@ -88,7 +88,7 @@ class PyShark(EngineBase['PySharkPacket']): """ if TYPE_CHECKING: - #: Engine extraction package, i.e. :mod:`pyshark`. + #: Engine extraction package, i.e. ``pyshark``. _expkg: '_PySharkPackage' #: Engine extraction temporary storage. _extmp: 'PySharkFileCapture' @@ -214,11 +214,11 @@ def __init__(self, extractor: 'Extractor') -> 'None': ########################################################################## def run(self) -> 'None': - """Call :class:`pyshark.FileCapture` to extract PCAP files. + """Call ``pyshark.FileCapture`` to extract PCAP files. This method assigns :attr:`self._expkg ` - as :mod:`pyshark` and :attr:`self._extmp ` - as an iterator from :class:`pyshark.FileCapture`. + as ``pyshark`` and :attr:`self._extmp ` + as an iterator from ``pyshark.FileCapture``. The global header is parsed and dumped first, by :meth:`self.extractor.record_header `. diff --git a/pcapkit/foundation/engines/scapy.py b/pcapkit/foundation/engines/scapy.py index c57683ca0..cb8552645 100644 --- a/pcapkit/foundation/engines/scapy.py +++ b/pcapkit/foundation/engines/scapy.py @@ -11,15 +11,15 @@ .. note:: - Constructing this engine imports :mod:`scapy.all`, which is what populates + Constructing this engine imports ``scapy.all``, which is what populates `Scapy`_'s layer registries -- see :meth:`Scapy.__init__` for why anything narrower silently returns undissected frames. - One side effect is worth knowing about in advance: :mod:`scapy.all` loads + One side effect is worth knowing about in advance: ``scapy.all`` loads :mod:`scapy.layers.dcerpc`, which reaches `Scapy`_'s TLS layer and there - triggers a ``CryptographyDeprecationWarning`` from :mod:`cryptography` about + triggers a ``CryptographyDeprecationWarning`` from ``cryptography`` about finite-field Diffie-Hellman. It is emitted by any complete registry load, not - by :mod:`scapy.all` in particular, and it concerns a key-exchange code path + by ``scapy.all`` in particular, and it concerns a key-exchange code path :mod:`pcapkit` never executes -- but it subclasses :exc:`UserWarning`, not :exc:`DeprecationWarning`, so Python's default filters show it. @@ -27,7 +27,7 @@ to emit and the consumer's to silence, on the same footing as every other category (see :mod:`pcapkit.utilities.warnings`); hiding a third-party deprecation notice from inside a constructor would suppress the only advance - warning that a future :mod:`cryptography` release breaks `Scapy`_'s TLS layer. + warning that a future ``cryptography`` release breaks `Scapy`_'s TLS layer. To silence it, filter it as usual:: import warnings @@ -60,7 +60,7 @@ from pcapkit.foundation.extraction import Extractor class _ScapyPackage(Protocol): - """:mod:`scapy.all`, as :class:`Scapy` reads it through :attr:`Scapy._expkg` (#1878). + """``scapy.all``, as :class:`Scapy` reads it through :attr:`Scapy._expkg` (#1878). Typed by scapy itself, which ships its own type information. @@ -126,7 +126,7 @@ def _reader_type() -> 'Type[PcapReader]': :func:`pcapkit.toolkit.scapy.attach_resolution` and :func:`pcapkit.toolkit.scapy.attach_linktype`: from the global header of a PCAP, and from the interface of each PCAP-NG packet block, which - :meth:`~scapy.utils.RawPcapNgReader._read_block` is the last place to see. + ``scapy.utils.RawPcapNgReader._read_block`` is the last place to see. The PCAP-NG subclass also starts each section with no interfaces, as the PCAP-NG specification defines a section: an interface ID counts the Interface @@ -292,7 +292,7 @@ class Scapy(EngineBase['ScapyPacket']): """ if TYPE_CHECKING: - #: Engine extraction package, i.e. :mod:`scapy.all`. + #: Engine extraction package, i.e. ``scapy.all``. _expkg: '_ScapyPackage' #: Engine extraction temporary storage. _extmp: 'Iterator[ScapyPacket]' @@ -318,7 +318,7 @@ def __init__(self, extractor: 'Extractor') -> 'None': extractor: :class:`~pcapkit.foundation.extraction.Extractor` instance. """ - # NOTE: :mod:`scapy.all`, not :mod:`scapy.sendrecv`, and the difference is + # NOTE: ``scapy.all``, not :mod:`scapy.sendrecv`, and the difference is # load-bearing rather than cosmetic. Scapy dispatches on two registries that # exist only as *import side effects* of its layer modules: ``conf.l2types``, # mapping a capture's link type onto a link-layer class, and the @@ -335,9 +335,9 @@ def __init__(self, extractor: 'Extractor') -> 'None': # ``dhcp.pcapng`` still comes back as ``Ethernet / IP / UDP / Raw`` rather # than ``... / UDP / BOOTP / DHCP options``. Enumerating them here would also # go stale silently -- a link or payload type added by a later scapy would - # regress to ``Raw`` with no error -- whereas :mod:`scapy.all` defers to + # regress to ``Raw`` with no error -- whereas ``scapy.all`` defers to # ``conf.load_layers``, which scapy itself keeps current. The cost is the - # layer loading, not the façade: importing :mod:`scapy.layers.all` alone + # layer loading, not the façade: importing ``scapy.layers.all`` alone # measures the same, so there is no complete-but-cheaper option to prefer. # # This stays inside ``__init__`` rather than moving to module scope so that @@ -363,7 +363,7 @@ def run(self) -> 'None': This method assigns :attr:`self._extmp ` as an iterator from :func:`scapy.sendrecv.sniff`, reached through :attr:`self._expkg ` -- which :meth:`__init__` binds to - :mod:`scapy.all`, since that is the import that populates the layer + ``scapy.all``, since that is the import that populates the layer registries ``sniff`` needs to dissect anything. The global header is parsed and dumped first, by diff --git a/pcapkit/protocols/internet/esp.py b/pcapkit/protocols/internet/esp.py index 456171449..3772f1b9f 100644 --- a/pcapkit/protocols/internet/esp.py +++ b/pcapkit/protocols/internet/esp.py @@ -197,7 +197,7 @@ from pcapkit.protocols.protocol import ProtocolBase -#: Cached :mod:`cryptography` primitives, see :func:`load_cryptography`. +#: Cached ``cryptography`` primitives, see :func:`load_cryptography`. #: :data:`NotImplemented` means the import has not been attempted yet, and #: :data:`None` that it was attempted and |cryptography|_ is not installed, and #: a tuple holds the loaded primitives, so a missing dependency is not retried diff --git a/pcapkit/protocols/protocol.py b/pcapkit/protocols/protocol.py index aaa4533db..566673ba8 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 :func:`_declared_keywords` cannot find them and +#: signature declares them, so ``_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 :func:`_declared_keywords`, keyed by protocol class. Signatures do +#: Cache for ``_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 - :func:`_declared_keywords` collects for ``cls``. + ``_declared_keywords()`` collects for ``cls``. Warns: UnknownFieldWarning: The same finding when not ``strict``. @@ -2008,7 +2008,7 @@ def _make_index_namespace(cls, name: 'str', matches if it is accompanied by an ``arg_namespace`` argument that is :data:`None`, its current value is a :obj:`str` equal to ``name``, and its default - value is an :class:`~enum.IntEnum` (or :class:`aenum.IntEnum`) + value is an :class:`~enum.IntEnum` (or ``aenum.IntEnum``) member. The candidate namespace of a match is the class of that default value. diff --git a/pcapkit/protocols/schema/misc/pcapng.py b/pcapkit/protocols/schema/misc/pcapng.py index 8ad707334..c32f76eab 100644 --- a/pcapkit/protocols/schema/misc/pcapng.py +++ b/pcapkit/protocols/schema/misc/pcapng.py @@ -752,7 +752,7 @@ def post_process(self, value: 'int | bytes', packet: 'dict[str, Any]') -> 'Enum_ :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 :class:`aenum.Enum` subclasses) defines + :class:`enum.Enum` (which ``aenum.Enum`` subclasses) defines ``__copy__``/``__deepcopy__`` to return ``self`` outright, so deep-copying any member never reaches ``__reduce_ex__``. diff --git a/pcapkit/protocols/schema/schema.py b/pcapkit/protocols/schema/schema.py index 2f62dbd08..cb4bbfe6f 100644 --- a/pcapkit/protocols/schema/schema.py +++ b/pcapkit/protocols/schema/schema.py @@ -277,7 +277,7 @@ def __prepare__(cls, name: 'str', bases: 'tuple[type, ...]', /, **kwds: 'Any') - #: Collisions of different kinds, all reserved: #: #: ``mcls``, ``name``, ``bases``, ``namespace`` collide with - #: :meth:`abc.ABCMeta.__new__` -- a *different* function from this one, one + #: ``abc.ABCMeta.__new__`` -- a *different* function from this one, one #: level up the ``super().__new__(...)`` call below. Before Python 3.11 #: those are positional-or-keyword there (from 3.11 they are #: positional-only, ``def __new__(mcls, name, bases, namespace, /, @@ -333,7 +333,7 @@ def __new__(cls, name: 'str', bases: 'tuple[type, ...]', attrs: 'dict[str, Any]' Raises: SchemaError: If a class keyword in ``**kwargs`` collides with a - parameter of :meth:`abc.ABCMeta.__new__` or of + parameter of ``abc.ABCMeta.__new__`` or of ``__init_subclass__`` -- see :attr:`_RESERVED_CLASS_KWARGS`. """ @@ -1196,8 +1196,10 @@ class _EnumRegistry(collections.defaultdict): """A registry :class:`collections.defaultdict` that never inserts a miss. :attr:`EnumSchema.registry` (and its class-level twin, - :attr:`EnumMeta.registry`) is read with a bare ``registry[code]`` at many - call sites across the schema layer, e.g. ``Option.registry[type]``. A + :attr:`EnumMeta.registry + `) is read with a bare + ``registry[code]`` at many call sites across the schema layer, e.g. + ``Option.registry[type]``. A plain :class:`collections.defaultdict` inserts whatever :attr:`~collections.defaultdict.default_factory` returns the *first time* an unregistered ``code`` is looked up -- and since the registry lives on @@ -1330,7 +1332,9 @@ def registry(self) -> 'DefaultDict[_ET, Type[Self]]': mapping is served by ``EnumMeta.registry``. Important: - See :attr:`EnumMeta.registry`: the returned mapping is a + See :attr:`EnumMeta.registry + `: the + returned mapping is a :class:`_EnumRegistry`, so looking up an unregistered ``code`` returns the default schema without recording ``code`` as if it had been registered. diff --git a/pcapkit/protocols/transport/tcp.py b/pcapkit/protocols/transport/tcp.py index eeaa3b988..10ef15f15 100644 --- a/pcapkit/protocols/transport/tcp.py +++ b/pcapkit/protocols/transport/tcp.py @@ -524,7 +524,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_TCP': # segment whose flags octet is all zero: the ``|=`` below is the only promotion and # never runs, and ``Enum_Flags.SYN in self._flags`` would raise ``TypeError: # argument of type 'int' is not a container or iterable``. A segment with any flag - # set would mask that. :class:`Enum_Flags` is an :class:`aenum.IntFlag` with no + # set would mask that. :class:`Enum_Flags` is an ``aenum.IntFlag`` with no # ``_missing_`` of its own, so ``Enum_Flags(0)`` is a valid flagless member that # compares equal to ``0`` and ORs as usual. This is the type :attr:`connection` # advertises. @@ -644,7 +644,7 @@ def make(self, # self._flags`` would raise ``TypeError: argument of type 'int' is not a # container or iterable`` instead of reaching ``_make_mptcp_join``'s own # ``ProtocolError: ... invalid flags combination``. :class:`Enum_Flags` is an - # :class:`aenum.IntFlag`, so ``Enum_Flags(0)`` is a valid flagless member that + # ``aenum.IntFlag``, so ``Enum_Flags(0)`` is a valid flagless member that # compares equal to ``0`` and ORs as usual. :meth:`read` seeds itself the same # way, rather than relying on ``mptcp_data_selector`` rejecting a flagless # MP_JOIN before ``_read_mptcp_join`` runs, which would leave the ``TypeError`` diff --git a/pcapkit/toolkit/dpkt.py b/pcapkit/toolkit/dpkt.py index 2340e76dc..1088cd930 100644 --- a/pcapkit/toolkit/dpkt.py +++ b/pcapkit/toolkit/dpkt.py @@ -63,7 +63,7 @@ #: Attribute a frame's record octets are stashed under. #: #: A `DPKT`_ packet keeps no copy of the octets it was parsed from, and -#: serialising it again is not the same thing: :meth:`dpkt.ip.IP.__bytes__` +#: serialising it again is not the same thing: ``dpkt.ip.IP.__bytes__`` #: recomputes a zeroed checksum and length and writes them back into the #: packet, as do the transport layers beneath it. So the adapters below slice #: the wire octets out of this buffer instead, which @@ -351,10 +351,10 @@ def _ipv6_ext_hdrs(ipv6: 'IP6') -> 'list[Any]': ipv6: DPKT IPv6 packet. Returns: - :attr:`dpkt.ip6.IP6.all_extension_headers` where `DPKT`_ recorded it, + ``dpkt.ip6.IP6.all_extension_headers`` where `DPKT`_ recorded it, which keeps the on-wire order and any header type that occurs twice; - otherwise the values of :attr:`~dpkt.ip6.IP6.extension_hdrs`, which - :meth:`dpkt.ip6.IP6.__len__` falls back to in the same way. + otherwise the values of ``dpkt.ip6.IP6.extension_hdrs``, which + ``dpkt.ip6.IP6.__len__`` falls back to in the same way. .. _DPKT: https://dpkt.readthedocs.io @@ -370,20 +370,20 @@ def _wire_hdr_len(packet: 'Packet', payload: 'Packet') -> 'int': Args: packet: DPKT packet, i.e. one layer of a frame. - payload: That layer's payload, i.e. :attr:`packet.data `. + payload: That layer's payload, i.e. ``packet.data``. Returns: The advance :meth:`~dpkt.dpkt.Packet.unpack` itself made to reach the payload. For IPv6 that is the fixed header plus each extension header's - :attr:`~dpkt.ip6.IP6ExtensionHeader.length`, which is what + ``dpkt.ip6.IP6ExtensionHeader.length``, which is what :meth:`dpkt.ip6.IP6.unpack` walks the buffer by. It cannot be taken from - ``len()``: :meth:`dpkt.ip6.IP6.__len__` sums ``len()`` of the extension + ``len()``: ``dpkt.ip6.IP6.__len__`` sums ``len()`` of the extension headers instead, and that is 12 octets plus the *rest of the frame* for an Authentication header -- :class:`dpkt.ip6.IP6AHHeader` trims only into - :attr:`~dpkt.ip6.IP6AHHeader.auth_data` and leaves - :attr:`~dpkt.dpkt.Packet.data` running to the end of the buffer -- and 8 + ``dpkt.ip6.IP6AHHeader.auth_data`` and leaves + ``dpkt.dpkt.Packet.data`` running to the end of the buffer -- and 8 octets short of the wire for a Routing header whose Hdr Ext Len is odd (:class:`dpkt.ip6.IP6RoutingHeader` keeps whole addresses only). @@ -591,10 +591,10 @@ def ipv4_reassembly(packet: 'Packet', timestamp: 'float', *, The header and payload are sliced out of the record octets :class:`~pcapkit.foundation.engines.dpkt.DPKT` attached to the frame, so they are the captured ones. A packet built by hand carries no such - buffer, and its octets are then :mod:`dpkt`'s re-serialisation of it, in + buffer, and its octets are then ``dpkt``'s re-serialisation of it, in which a zeroed checksum or length comes back recomputed -- :func:`attach_buffer` is what makes them the wire's again. They are that - re-serialisation too where :mod:`dpkt`'s own lengths misplace the layer + re-serialisation too where ``dpkt``'s own lengths misplace the layer in the record, as behind some stacks of VLAN tags (:issue:`1646`). The packet itself is left untouched either way. @@ -678,10 +678,10 @@ def ipv6_reassembly(packet: 'Packet', timestamp: 'float', *, The header and payload are sliced out of the record octets :class:`~pcapkit.foundation.engines.dpkt.DPKT` attached to the frame, so they are the captured ones. A packet built by hand carries no such - buffer, and its octets are then :mod:`dpkt`'s re-serialisation of it, in + buffer, and its octets are then ``dpkt``'s re-serialisation of it, in which a zeroed checksum or length comes back recomputed -- :func:`attach_buffer` is what makes them the wire's again. They are that - re-serialisation too where :mod:`dpkt`'s own lengths misplace the layer + re-serialisation too where ``dpkt``'s own lengths misplace the layer in the record, as behind some stacks of VLAN tags (:issue:`1646`). The packet itself is left untouched either way. @@ -774,7 +774,7 @@ def tcp_reassembly(packet: 'Packet', timestamp: 'float', *, """Make data for TCP reassembly. A packet can be reassembled if its network layer carries a TCP layer - (:class:`dpkt.tcp.TCP`), behind any IPv6 extension headers :mod:`dpkt` decodes. TCP + (:class:`dpkt.tcp.TCP`), behind any IPv6 extension headers ``dpkt`` decodes. TCP in an IP-in-IP tunnel, or behind an AH or IPv6 extension header carried in IPv4, is not read. @@ -793,10 +793,10 @@ def tcp_reassembly(packet: 'Packet', timestamp: 'float', *, The header and payload are sliced out of the record octets :class:`~pcapkit.foundation.engines.dpkt.DPKT` attached to the frame, so they are the captured ones. A packet built by hand carries no such - buffer, and its octets are then :mod:`dpkt`'s re-serialisation of it, in + buffer, and its octets are then ``dpkt``'s re-serialisation of it, in which a zeroed checksum or length comes back recomputed -- :func:`attach_buffer` is what makes them the wire's again. They are that - re-serialisation too where :mod:`dpkt`'s own lengths misplace the layer + re-serialisation too where ``dpkt``'s own lengths misplace the layer in the record, as behind some stacks of VLAN tags (:issue:`1646`). The packet itself is left untouched either way. @@ -850,7 +850,7 @@ def tcp_traceflow(packet: 'Packet', timestamp: 'float', *, """Trace packet flow for TCP. A packet can be traced if its network layer carries a TCP layer - (:class:`dpkt.tcp.TCP`), behind any IPv6 extension headers :mod:`dpkt` decodes. TCP + (:class:`dpkt.tcp.TCP`), behind any IPv6 extension headers ``dpkt`` decodes. TCP in an IP-in-IP tunnel, or behind an AH or IPv6 extension header carried in IPv4, is not read. @@ -871,10 +871,10 @@ def tcp_traceflow(packet: 'Packet', timestamp: 'float', *, The header and payload are sliced out of the record octets :class:`~pcapkit.foundation.engines.dpkt.DPKT` attached to the frame, so they are the captured ones. A packet built by hand carries no such - buffer, and its octets are then :mod:`dpkt`'s re-serialisation of it, in + buffer, and its octets are then ``dpkt``'s re-serialisation of it, in which a zeroed checksum or length comes back recomputed -- :func:`attach_buffer` is what makes them the wire's again. They are that - re-serialisation too where :mod:`dpkt`'s own lengths misplace the layer + re-serialisation too where ``dpkt``'s own lengths misplace the layer in the record, as behind some stacks of VLAN tags (:issue:`1646`). The packet itself is left untouched either way. diff --git a/pcapkit/toolkit/pcap.py b/pcapkit/toolkit/pcap.py index fe31ed69c..7ddd2b8d5 100644 --- a/pcapkit/toolkit/pcap.py +++ b/pcapkit/toolkit/pcap.py @@ -129,7 +129,7 @@ def tcp_segment(frame: 'ProtocolBase') -> 'Optional[TCPSegment]': design (:issue:`1404`), so the segment arrives as :class:`~pcapkit.protocols.misc.raw.Raw`. The ports, numbers and flags are all in the fixed header, so the segment still belongs to its flow, - as it does for :mod:`dpkt`, :mod:`scapy` and Wireshark (:issue:`1518`). + as it does for ``dpkt``, :mod:`scapy` and Wireshark (:issue:`1518`). The header and payload are then the captured octets either side of the Data Offset. Inside a tunnel, this holds of the IP layer carrying the segment, as it does without one. diff --git a/pcapkit/toolkit/pcap_ct.py b/pcapkit/toolkit/pcap_ct.py index d9f674f42..ce49eedc2 100644 --- a/pcapkit/toolkit/pcap_ct.py +++ b/pcapkit/toolkit/pcap_ct.py @@ -27,7 +27,7 @@ :mod:`pcapkit.toolkit.pypcap` is the same adapter for upstream `PyPCAP`_, kept apart from this module because the engines are: the distributions are - independent projects that happen to share the :mod:`pcap` module name, and + independent projects that happen to share the ``pcap`` module name, and each engine names its own adapter so that a change made for one cannot quietly alter the other. @@ -61,7 +61,7 @@ def packet2chain(packet: 'bytes', *, data_link: 'Enum_LinkType') -> 'str': """Fetch pcap-ct packet protocol chain. Args: - packet: Raw packet bytes, as returned by :class:`pcap.pcap` iteration. + packet: Raw packet bytes, as returned by ``pcap.pcap`` iteration. data_link: Data link type, from the capture handle. Returns: @@ -82,8 +82,8 @@ def packet2dict(packet: 'bytes', timestamp: 'float', *, """Convert pcap-ct packet into :obj:`dict`. Args: - packet: Raw packet bytes, as returned by :class:`pcap.pcap` iteration. - timestamp: Timestamp of packet, as returned by :class:`pcap.pcap` iteration. + packet: Raw packet bytes, as returned by ``pcap.pcap`` iteration. + timestamp: Timestamp of packet, as returned by ``pcap.pcap`` iteration. data_link: Data link type, from the capture handle. Returns: diff --git a/pcapkit/toolkit/pypcap.py b/pcapkit/toolkit/pypcap.py index 204015d1f..02796581b 100644 --- a/pcapkit/toolkit/pypcap.py +++ b/pcapkit/toolkit/pypcap.py @@ -60,7 +60,7 @@ def packet2chain(packet: 'bytes', *, data_link: 'Enum_LinkType') -> 'str': """Fetch PyPCAP packet protocol chain. Args: - packet: Raw packet bytes, as returned by :class:`pcap.pcap` iteration. + packet: Raw packet bytes, as returned by ``pcap.pcap`` iteration. data_link: Data link type, from the capture handle. Returns: @@ -81,8 +81,8 @@ def packet2dict(packet: 'bytes', timestamp: 'float', *, """Convert PyPCAP packet into :obj:`dict`. Args: - packet: Raw packet bytes, as returned by :class:`pcap.pcap` iteration. - timestamp: Timestamp of packet, as returned by :class:`pcap.pcap` iteration. + packet: Raw packet bytes, as returned by ``pcap.pcap`` iteration. + timestamp: Timestamp of packet, as returned by ``pcap.pcap`` iteration. data_link: Data link type, from the capture handle. Returns: diff --git a/pcapkit/toolkit/pypcapfile.py b/pcapkit/toolkit/pypcapfile.py index 86f829d1e..838cd6198 100644 --- a/pcapkit/toolkit/pypcapfile.py +++ b/pcapkit/toolkit/pypcapfile.py @@ -36,7 +36,7 @@ stops at the network layer, which keeps the transport segment verbatim so that the header/payload split this module reports is exact. The transport header is decoded on demand below, using `PyPCAPFile`_'s own - :class:`~pcapfile.protocols.transport.tcp.TCP` class rather than a + ``pcapfile.protocols.transport.tcp.TCP`` class rather than a reimplementation. """ @@ -150,8 +150,8 @@ def _parse_ipv4_address(value: 'Any') -> 'IPv4Address': """Parse a raw PyPCAPFile IPv4 ``src``/``dst`` field. Args: - value: Raw value of an :attr:`IP.src ` - or :attr:`IP.dst ` field. + value: Raw value of an ``IP.src`` + or ``IP.dst`` field. Returns: The parsed address. @@ -192,9 +192,8 @@ def _parse_mac_address(value: 'Any') -> 'str': """Parse a raw PyPCAPFile Ethernet ``src``/``dst`` field. Args: - value: Raw value of an :attr:`Ethernet.src - ` or - :attr:`Ethernet.dst ` + value: Raw value of an ``Ethernet.src`` or + ``Ethernet.dst`` field. Returns: @@ -239,8 +238,8 @@ def _maybe_unhex(value: 'bytes') -> 'bytes': Note: The released `PyPCAPFile`_ (0.12.0) stores an IPv4 packet's options - (:attr:`IP.opt `) and payload - (:attr:`IP.payload `) as + (``IP.opt``) and payload + (``IP.payload``) as :func:`binascii.hexlify`'d ASCII text once decoding stops short of the transport layer -- which is exactly how :class:`~pcapkit.foundation.engines.pypcapfile.PyPCAPFile` calls it -- @@ -321,7 +320,7 @@ def _network(packet: 'Packet') -> 'Optional[IP]': packet: PyPCAPFile packet. Returns: - The decoded :class:`~pcapfile.protocols.network.ip.IP` layer, or + The decoded ``pcapfile.protocols.network.ip.IP`` layer, or :data:`None` when the frame was not decoded that far -- which is the case for every non-IPv4 frame, `PyPCAPFile`_ having no other network layer decoder -- or its header length (IHL) runs past the captured @@ -389,7 +388,7 @@ def _capture_of(packet: 'Packet') -> 'tuple[int, Any]': anchor is alive, no packet of another capture has the same key. Note: - :attr:`pcap_packet.header ` is a + ``pcap_packet.header`` is a :mod:`ctypes` pointer to the savefile header. Each read of the field builds a new pointer object, but every one of them points at the same header, so the header's address is the key. The pointer object read @@ -618,8 +617,8 @@ def _layer2dict(layer: 'Any') -> 'dict[str, Any]': layer: Decoded PyPCAPFile layer, or raw bytes. Note: - `PyPCAPFile`_'s own :class:`~pcapfile.protocols.linklayer.ethernet.Ethernet` - and :class:`~pcapfile.protocols.network.ip.IP` decoders hex-encode the + `PyPCAPFile`_'s own ``pcapfile.protocols.linklayer.ethernet.Ethernet`` + and ``pcapfile.protocols.network.ip.IP`` decoders hex-encode the payload they retain rather than decoding it further (see :func:`_maybe_unhex`), so a raw payload reached by recursing *from* one of those classes is un-hexed before being reported here. Without that, @@ -724,7 +723,7 @@ def ipv4_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'IP_Packet[IPv4Ad """Make data for IPv4 reassembly. A packet can be reassembled if it contains an IPv4 layer - (:class:`pcapfile.protocols.network.ip.IP`) whose header was captured whole (see + (``pcapfile.protocols.network.ip.IP``) whose header was captured whole (see :func:`_network`), and the **DF** flag is :data:`False`. Args: diff --git a/pcapkit/toolkit/pyshark.py b/pcapkit/toolkit/pyshark.py index a9b4d20ad..04ad39c88 100644 --- a/pcapkit/toolkit/pyshark.py +++ b/pcapkit/toolkit/pyshark.py @@ -31,7 +31,7 @@ __all__ = ['packet2dict', 'tcp_traceflow', 'ENCAP_TYPE_TO_LINKTYPE', 'FILTER_NAME_TO_LINKTYPE'] -#: ``frame.encap_type`` -- Wireshark's internal ``WTAP_ENCAP_*`` number, which :mod:`pyshark` +#: ``frame.encap_type`` -- Wireshark's internal ``WTAP_ENCAP_*`` number, which ``pyshark`` #: exposes as ``packet.frame_info.encap_type`` -- to the matching #: :class:`~pcapkit.const.reg.linktype.LinkType` member. This is :func:`tcp_traceflow`'s primary #: link-type source; ``FILTER_NAME_TO_LINKTYPE`` below is only its fallback. @@ -51,7 +51,7 @@ #: # key := the frame.encap_type that `tshark -r out.pcap -c 1 -T pdml` then reports #: #: so each pair is one round trip through Wireshark's own ``wiretap/pcap-common.c``, in the read -#: direction :mod:`pyshark` actually exercises. In the source, the trailing comment on each line +#: direction ``pyshark`` actually exercises. In the source, the trailing comment on each line #: names the ``editcap -T`` encapsulation that produced it. #: #: What the sweep does **not** cover, since that bounds this table: of the 226 encapsulations @@ -243,7 +243,7 @@ #: cannot be mapped at any size: ``user_dlt`` (16 DLTs), ``bluetooth`` (6), ``usb`` (5), #: ``usbll`` (4), ``raw`` (3), and ``arcnet``, ``gfp``, ``i2c``, ``lapd``, ``mtp2``, #: ``netanalyzer``, ``null``, ``ppp``, ``sll``, ``wlan``, ``wpan`` (2 each). -#: * **Pseudo-protocols** -- ``fake-field-wrapper``, which :mod:`pyshark` reports as ``data`` and +#: * **Pseudo-protocols** -- ``fake-field-wrapper``, which ``pyshark`` reports as ``data`` and #: which was the root for 29 of the swept encapsulations (no dissector), and ``_ws.malformed``, #: the root for the one capture ``tshark`` could not parse. Neither names a link type. #: * **Unswept** -- the 69 encapsulations ``editcap -T`` would not write as pcap from an Ethernet diff --git a/pcapkit/toolkit/scapy.py b/pcapkit/toolkit/scapy.py index 6846261ab..e528c5d78 100644 --- a/pcapkit/toolkit/scapy.py +++ b/pcapkit/toolkit/scapy.py @@ -32,7 +32,7 @@ Populating the registries before ``sniff`` is :class:`~pcapkit.foundation.engines.scapy.Scapy`'s job, and it does it by - importing :mod:`scapy.all` in its constructor. + importing ``scapy.all`` in its constructor. """ import decimal @@ -457,7 +457,7 @@ def ipv4_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'IP_Packet[IPv4Ad A packet can be reassembled if it contains an IPv4 layer (:class:`scapy.layers.inet.IP`) whose header -- as many octets as its IHL counts, and at least 20 -- lies inside the frame and every IP datagram carrying it (see - :func:`_within_datagrams`), and the **DF** (:attr:`scapy.layers.inet.IP.flags.DF`) + :func:`_within_datagrams`), and the **DF** (``scapy.layers.inet.IP.flags.DF``) flag is :data:`False`. A zero-length frame has none, though `Scapy`_ hands it back as an IPv4 layer of defaults at the ``RAW`` and ``IPV4`` link types (see :func:`_frame`). diff --git a/pcapkit/utilities/warnings.py b/pcapkit/utilities/warnings.py index b5595ffd8..9beea5758 100644 --- a/pcapkit/utilities/warnings.py +++ b/pcapkit/utilities/warnings.py @@ -14,7 +14,7 @@ ``pcapkit`` logger sees every complaint whatever the warning filters say; 2. the standard :mod:`warnings` machinery, via :func:`warnings.warn`, subject to the filters -- so :func:`warnings.filterwarnings`, - :func:`warnings.catch_warnings`, :mod:`pytest`'s ``filterwarnings``, + :class:`warnings.catch_warnings`, :mod:`pytest`'s ``filterwarnings``, :option:`-W` and :envvar:`PYTHONWARNINGS` govern :mod:`pcapkit` warnings exactly as they govern any other library's (see below for the one wrinkle in naming a category on the command line). @@ -118,7 +118,7 @@ def warn(message: 'Union[str, Warning]', category: 'Type[Warning]', The warning is reported once on the :data:`~pcapkit.utilities.logging.logger` logger, then once through :func:`warnings.warn`. The logger call does not - consult :data:`warnings.filters`, so a log-based consumer sees the complaint + consult ``warnings.filters``, so a log-based consumer sees the complaint even when the application has filtered the category out; the :func:`warnings.warn` call is filtered normally, so the application keeps full control of that channel -- including turning the warning into an error @@ -259,8 +259,8 @@ class hold_warnings: # pylint: disable=invalid-name have (:issue:`1580`). Note: - Nothing here touches :data:`warnings.filters` or a module's - ``__warningregistry__``; :func:`warnings.catch_warnings` would, which is + Nothing here touches ``warnings.filters`` or a module's + ``__warningregistry__``; :class:`warnings.catch_warnings` would, which is what made unrelated warnings re-fire up to v1.4.1 (see :class:`BaseWarning`). @@ -336,7 +336,7 @@ class BaseWarning(UserWarning): The constructor has no side effects (:issue:`364`). In 1.4.x it called ``warnings.simplefilter('ignore', type(self))`` outside development mode, which inserted an entry at the front of the process-global - :data:`warnings.filters` -- overriding whatever the host application, + ``warnings.filters`` -- overriding whatever the host application, :option:`-W` or the test runner had configured, and invalidating every module's ``__warningregistry__`` so that unrelated warnings re-fired. It also logged the warning a second time under diff --git a/pcapkit/vendor/default.py b/pcapkit/vendor/default.py index 5c5b3d64b..334db2143 100644 --- a/pcapkit/vendor/default.py +++ b/pcapkit/vendor/default.py @@ -583,8 +583,8 @@ def _dest_path(cls) -> 'str': to obtain it. That lets a caller resolve a crawler's destination *without* instantiating it, and so without triggering :meth:`Vendor.__init__`'s network fetch, render and write -- - :func:`pcapkit.vendor.__main__.run`'s pre-run snapshot (GitHub issue - :issue:`872`) is exactly such a caller. Every crawler defined in this + the pre-run snapshot that ``run()`` in :mod:`pcapkit.vendor.__main__` + takes (GitHub issue :issue:`872`) is exactly such a caller. Every crawler defined in this codebase inherits this implementation unchanged; none overrides ``_dest_path``, so the classmethod is authoritative for all of them. diff --git a/tests/project/test_docs_unresolvable_roles_1919_unit.py b/tests/project/test_docs_unresolvable_roles_1919_unit.py new file mode 100644 index 000000000..8858aebc3 --- /dev/null +++ b/tests/project/test_docs_unresolvable_roles_1919_unit.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- +"""Pins the hand-written cross-references that cannot resolve (#1919, #1920). + +Sphinx builds without ``nitpicky``, so a role whose target exists in no inventory +and on no page renders as an unlinked chip and warns about nothing. #934 part C +settled the policy for those, and :file:`docs/source/conf.py` records it next to +``intersphinx_mapping``: retarget where a real target exists, add a documented +entry where the name is contract, and otherwise demote the role to a literal. + +* :class:`InventorylessPackageRoleTests` -- ``aenum``, ``pyshark``, ``pcap`` + (PyPCAP and pcap-ct), ``pcapfile`` (PyPCAPFile) and ``emoji`` publish no + ``objects.inv`` with ``py:`` objects in it, so no role naming them can resolve. + ``AenumRoleExclusionTests`` in :file:`test_conventions_doc_claims.py` checks + the conventions pages; this class checks every hand-written source under + :file:`docs/` and :file:`pcapkit/`. +* :class:`DeadTargetTests` -- targets in a package that has an inventory, but + absent from that inventory because the role, the path or the name is wrong + (#1919). Each is pinned in the form that fails to resolve. +* :class:`DocumentedEntryTests` -- the names #1919 decided are contract, which + have an entry rather than a demoted role. + +Scoped to hand-written sources. The class formatter in :file:`docs/source/conf.py` +covers the generated half of #1920 by writing a literal for a package outside +``intersphinx_mapping``; its output exists only at build time, so this scan reads +the file's own source like any other. ``pcapkit/const/`` and the vendor +templates that generate it are excluded, and are handled separately; the rest of +``pcapkit/vendor/`` is scanned. + +""" + +from __future__ import annotations + +import pathlib +import re +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[2] + +#: Paths, relative to :data:`ROOT`, that the scan does not read -- see the module +#: docstring for why each is out of scope. +EXCLUDED = ( + 'pcapkit/vendor/pcapng/option_type.py', + 'pcapkit/vendor/reg/apptype/apptype.py', + 'pcapkit/const/', +) + +#: Top-level packages with no ``py:`` objects in any published inventory, per the +#: note beside ``intersphinx_mapping`` in :file:`docs/source/conf.py`. +INVENTORYLESS = frozenset({'aenum', 'pyshark', 'pcap', 'pcapfile', 'emoji'}) + +#: A Python-domain role, in either the bare or the explicit-title form. The body +#: may wrap across lines, as an explicit title often does. +ROLE = re.compile( + r':(?:py:)?(mod|class|meth|func|attr|exc|obj|data|const|type|property|deco):`([^`]+)`') + + +def _sources() -> 'list[pathlib.Path]': + """Every hand-written ``.rst`` under :file:`docs/` and ``.py`` under :file:`pcapkit/`.""" + paths = sorted((ROOT / 'docs').rglob('*.rst')) + sorted((ROOT / 'docs').rglob('*.py')) \ + + sorted((ROOT / 'pcapkit').rglob('*.py')) + kept = [] + for path in paths: + rel = path.relative_to(ROOT).as_posix() + if any(rel == ex or rel.startswith(ex) for ex in EXCLUDED): + continue + kept.append(path) + return kept + + +def _roles() -> 'list[tuple[str, int, str, str]]': + """``(path, line, role, target)`` for every Python-domain role in :func:`_sources`. + + The target is what Sphinx looks up: the part in angle brackets for an explicit + title, with any ``~``/``!``/``.`` prefix and a trailing ``()`` removed. + + """ + found = [] + for path in _sources(): + text = path.read_text(encoding='utf-8') + for match in ROLE.finditer(text): + role, body = match.groups() + title = re.search(r'<([^>]+)>\s*$', body) + target = (title.group(1) if title else body).strip().lstrip('~!.') + if target.endswith('()'): + target = target[:-2] + line = text.count('\n', 0, match.start()) + 1 + found.append((path.relative_to(ROOT).as_posix(), line, role, target)) + return found + + +class InventorylessPackageRoleTests(unittest.TestCase): + """No role names a package that has nothing to resolve it against.""" + + def test_the_scan_reads_the_pages_and_modules_it_claims_to(self) -> 'None': + """An empty or misrooted scan would pass the next test vacuously.""" + names = {path.relative_to(ROOT).as_posix() for path in _sources()} + for expected in ('docs/source/pcapkit/foundation/engines/3rdparty.rst', + 'docs/source/pcapkit/toolkit/3rdparty.rst', + 'pcapkit/corekit/fields/numbers.py', + 'pcapkit/corekit/enum.py', + 'pcapkit/foundation/engines/pypcap.py', + 'pcapkit/vendor/default.py', + 'docs/source/conf.py'): + self.assertIn(expected, names) + for excluded in EXCLUDED: + self.assertFalse(any(name == excluded or name.startswith(excluded) + for name in names), excluded) + + def test_the_target_parser_sees_through_an_explicit_title(self) -> 'None': + """``:mod:`PyPCAP ``` names ``pcap`` even though its text does not.""" + body = ROLE.search(':mod:`PyPCAP\n `').group(2) + title = re.search(r'<([^>]+)>\s*$', body) + self.assertEqual(title.group(1), 'pcap') + + def test_no_role_names_an_inventoryless_package(self) -> 'None': + """A reintroduced ``:mod:`pcap``` or ``:func:`~aenum.extend_enum``` fails here. + + Matched on the target's first dotted component, so ``pcapkit`` and + ``pcapng`` are not mistaken for ``pcap``. The plain double-backtick + literal is allowed: it is the form #934 part C prescribes for these roles. + + """ + offenders = [f'{path}:{line}: :{role}:`{target}`' + for path, line, role, target in _roles() + if target.split('.')[0] in INVENTORYLESS] + self.assertEqual(offenders, [], + 'these roles name a package whose objects.inv has no py: ' + 'objects, so they can never resolve; per #934 part C, write ' + 'them as double-backtick literals instead') + + +class DeadTargetTests(unittest.TestCase): + """The targets #1919 found missing from an inventory that does exist.""" + + #: ``(role, target, why)``: the exact pairing that renders as a chip. A + #: :data:`None` role bans the target under any role. + DEAD = ( + ('data', 'warnings.filters', + 'not in the Python inventory; write it as a literal'), + ('func', 'warnings.catch_warnings', + 'a py:class in the Python inventory; use :class:'), + (None, 'abc.ABCMeta.__new__', + 'not in the Python inventory; write it as a literal'), + (None, 'collections.abc.Mapping.items', + 'not in the Python inventory; name collections.abc.Mapping'), + (None, 'dictdumper.Dumper', + 'dictdumper documents it as dictdumper.dumper.Dumper'), + (None, 'dictdumper.dumper.Dumper._append_dict', + 'Dumper has no _append_dict; PLIST._append_dict and JSON._append_object do'), + ('mod', 'dpkt', + "dpkt's inventory has no top-level module; write it as a literal"), + (None, 'scapy.all', + "scapy's inventory has no scapy.all; write it as a literal"), + (None, 'scapy.layers.all', + "scapy's inventory has no scapy.layers.all; write it as a literal"), + ('mod', 'cryptography', + "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', + 'unqualified, it does not resolve from the pages that cite it; qualify it'), + ) + + def test_no_dead_target_appears(self) -> 'None': + """No retargeted or demoted pairing from #1919 appears.""" + roles = _roles() + offenders = [] + for want_role, want_target, why in self.DEAD: + for path, line, role, target in roles: + if target == want_target and want_role in (None, role): + offenders.append(f'{path}:{line}: :{role}:`{target}` -- {why}') + self.assertEqual(offenders, []) + + +class DocumentedEntryTests(unittest.TestCase): + """The names cited as contract (#1919) have an entry of their own.""" + + def test_compat_final_has_an_entry_under_its_module(self) -> 'None': + """``pcapkit.utilities.compat.final`` is what ``@info_final`` tells callers to use. + + Written bare under the page's ``.. module:: pcapkit.utilities.compat``, so + it registers as that dotted name rather than as a doubled one (#1775). + + """ + page = (ROOT / 'docs' / 'source' / 'pcapkit' / 'utilities' / 'index.rst').read_text( + encoding='utf-8') + module = page.index('.. module:: pcapkit.utilities.compat') + self.assertRegex(page[module:], r'(?m)^\.\. function:: final\(') + + def test_enummeta_registry_is_documented(self) -> 'None': + """The class-level ``registry`` that serves ``Option.registry[type]``.""" + page = (ROOT / 'docs' / 'source' / 'pcapkit' / 'protocols' / 'index.rst').read_text( + encoding='utf-8') + start = page.index('.. autoclass:: pcapkit.protocols.schema.schema.EnumMeta\n') + end = page.index('\n.. ', start + 1) + self.assertIn('.. autoproperty:: registry', page[start:end]) + + +if __name__ == '__main__': + unittest.main()