From 20d00990c824b27d607d3aa31c40381c1d1854a5 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Mon, 19 Oct 2026 13:29:04 -0400 Subject: [PATCH] docs(const): link aenum through the epilog in the apptype and option_type templates (#1920) - Write the package-level aenum mention in the AppType docstring as |aenum|_, the rst_epilog link (#1936), and demote member-level aenum roles in the AppType and OptionType templates to qualified literals (#934 part C). - Regenerate pcapkit/const/reg/apptype/apptype.py and pcapkit/const/pcapng/option_type.py byte-identically from the committed crawled data. - Lift the template and pcapkit/const/ exclusions from the #1919 and #1936 guards, so both now scan these files. Closes #1920 --- pcapkit/const/pcapng/option_type.py | 12 +++--- pcapkit/const/reg/apptype/apptype.py | 12 +++--- pcapkit/vendor/pcapng/option_type.py | 12 +++--- pcapkit/vendor/reg/apptype/apptype.py | 12 +++--- .../test_docs_epilog_links_1936_unit.py | 14 ++----- .../test_docs_unresolvable_roles_1919_unit.py | 40 ++++++------------- 6 files changed, 40 insertions(+), 62 deletions(-) diff --git a/pcapkit/const/pcapng/option_type.py b/pcapkit/const/pcapng/option_type.py index ec309571e1..72e9cd9a87 100644 --- a/pcapkit/const/pcapng/option_type.py +++ b/pcapkit/const/pcapng/option_type.py @@ -208,13 +208,13 @@ def _unregistered_member(cls, value: 'int', name: 'str') -> 'OptionType': :meth:`~pcapkit.corekit.enum.EnumRegistry._unregistered_member` calls :class:`str`'s ``__new__`` directly on the raw ``value``, but OptionType stores a *formatted* string (``'{name} [{value}]'``) as - its own :attr:`~aenum.Enum._value_`, not ``value`` itself, so that + its own ``aenum.Enum._value_``, not ``value`` itself, so that approach would build a member whose value looks nothing like this registry's real members. Deliberately does **not** add to :attr:`__members_ns__` either -- that dict is this registry's own second lookup table, alongside - :attr:`~aenum.Enum._value2member_map_`, and growing it would defeat + ``aenum.Enum._value2member_map_``, and growing it would defeat the whole point of an *unregistered* member exactly as growing ``_value2member_map_`` would. @@ -246,9 +246,9 @@ def register(cls, value: 'int', name: 'str') -> 'OptionType': files the member under the namespace that name's prefix gives (``if_rtaudit`` goes under ``if``), so ``name`` is passed through to it as well as used as the member's attribute name. - * :attr:`~aenum.Enum._value_` is the formatted ``'{name} [{value}]'`` + * ``aenum.Enum._value_`` is the formatted ``'{name} [{value}]'`` string, so the base's duplicate check against - :attr:`~aenum.Enum._value2member_map_` can never match an + ``aenum.Enum._value2member_map_`` can never match an :class:`int` code. The check runs against :attr:`__members_ns__` instead, the table :meth:`get` resolves codes through: both ``name``'s own namespace and ``opt``, whose codes are common to @@ -292,7 +292,7 @@ def register_alias(cls, value: 'int', name: 'str') -> 'OptionType': Overrides :meth:`~pcapkit.corekit.enum.EnumRegistry.register_alias` for the same reason as :meth:`register`: the base looks the - :class:`int` code up in :attr:`~aenum.Enum._value2member_map_`, whose + :class:`int` code up in ``aenum.Enum._value2member_map_``, whose keys are the formatted ``'{name} [{value}]'`` strings, so it could never find one. @@ -324,7 +324,7 @@ def register_alias(cls, value: 'int', name: 'str') -> 'OptionType': raise ValueError(f'{value!r} is not a registered {cls.__name__} in namespace ' f'{namespace!r} or opt; use {cls.__name__}.register() to mint one') - # NOTE: :func:`~aenum.extend_enum` builds a throwaway member through + # NOTE: ``aenum.extend_enum`` builds a throwaway member through # :meth:`__new__` to learn its value, and :meth:`__new__` files that # throwaway in :attr:`__members_ns__` over ``member``. The alias # resolves to ``member``, so ``member`` is put back. diff --git a/pcapkit/const/reg/apptype/apptype.py b/pcapkit/const/reg/apptype/apptype.py index 353f5e2cbe..861dd5b1ba 100644 --- a/pcapkit/const/reg/apptype/apptype.py +++ b/pcapkit/const/reg/apptype/apptype.py @@ -185,7 +185,7 @@ def get(cls, key: 'int | str', default: 'Any' = NO_DEFAULT) -> 'TransportProtoco # signature admits -- an unrecognised :class:`int` still reaches the # caller as a :exc:`ValueError`, now the base's own # :exc:`~pcapkit.utilities.exceptions.EnumValueError` since GitHub issue - # #923 rather than :mod:`aenum`'s bare one. It is not compatible for + # #923 rather than ``aenum``'s bare one. It is not compatible for # keys outside it: ``None``, a :class:`float` and an unhashable key # used to raise :exc:`AttributeError` from the ``key.lower()`` this # branch no longer reaches, and now raise :exc:`ValueError` (or, for a @@ -197,12 +197,12 @@ def get(cls, key: 'int | str', default: 'Any' = NO_DEFAULT) -> 'TransportProtoco return super().get(key, default) # NOTE: ``_missing_`` used to range-check ``value`` and then defer to - # :mod:`aenum`'s own ``Flag._missing_``, which is what composed an + # ``aenum``'s own ``Flag._missing_``, which is what composed an # unrecognised bit combination into a pseudo-member -- ``TransportProtocol(3)`` # returning ``tcp|udp`` -- GitHub issue #647's guard against that composing # *anything*, including values no member declares. GitHub issue #808 removed # the ``IntFlag`` base once nothing built a composite, and with it the only - # reason this method existed: a plain :class:`~aenum.IntEnum` already raises + # reason this method existed: a plain ``aenum.IntEnum`` already raises # ``ValueError`` for a value no member declares, with no ``_missing_`` of # its own needed to get there, so declaring one here would only be # reproducing what the base class already does. @@ -219,7 +219,7 @@ class AppType(EnumRegistry, StrEnum): :class:`~pcapkit.const.reg.apptype.udp.UDP`, :class:`~pcapkit.const.reg.apptype.sctp.SCTP` or :class:`~pcapkit.const.reg.apptype.dccp.DCCP`. Being memberless is also what - makes it subclassable at all, since :mod:`aenum` refuses to extend an + makes it subclassable at all, since |aenum|_ refuses to extend an enumeration that already has members. Note: @@ -2660,7 +2660,7 @@ def get(cls, key: 'int', *, # type: ignore[override] # pylint: disable=argument if member.svc == canonical: return member # NOTE: IANA's oldest row, for a port whose collision postdates - # :data:`__canonical__` -- including one :func:`~aenum.extend_enum` + # :data:`__canonical__` -- including one ``aenum.extend_enum`` # created, which must not displace what the registry already # answered with. return matched[0] @@ -2861,7 +2861,7 @@ def register_alias(cls, port: 'int', name: 'str') -> 'AppType': # pylint: disab IANA itself never registered it there. No new storage: the new member simply joins :data:`__registry__` at - ``port``, through the same :func:`~aenum.extend_enum` call + ``port``, through the same ``aenum.extend_enum`` call :meth:`register` uses to mint the first one. It is then reachable through :attr:`aliases` on every other member already on ``port``, and vice versa, with nothing further to keep in sync. diff --git a/pcapkit/vendor/pcapng/option_type.py b/pcapkit/vendor/pcapng/option_type.py index 26654fe14b..90d475e68c 100644 --- a/pcapkit/vendor/pcapng/option_type.py +++ b/pcapkit/vendor/pcapng/option_type.py @@ -118,13 +118,13 @@ def _unregistered_member(cls, value: 'int', name: 'str') -> '{NAME}': :meth:`~pcapkit.corekit.enum.EnumRegistry._unregistered_member` calls :class:`str`'s ``__new__`` directly on the raw ``value``, but {NAME} stores a *formatted* string (``'{{name}} [{{value}}]'``) as - its own :attr:`~aenum.Enum._value_`, not ``value`` itself, so that + its own ``aenum.Enum._value_``, not ``value`` itself, so that approach would build a member whose value looks nothing like this registry's real members. Deliberately does **not** add to :attr:`__members_ns__` either -- that dict is this registry's own second lookup table, alongside - :attr:`~aenum.Enum._value2member_map_`, and growing it would defeat + ``aenum.Enum._value2member_map_``, and growing it would defeat the whole point of an *unregistered* member exactly as growing ``_value2member_map_`` would. @@ -156,9 +156,9 @@ def register(cls, value: 'int', name: 'str') -> '{NAME}': files the member under the namespace that name's prefix gives (``if_rtaudit`` goes under ``if``), so ``name`` is passed through to it as well as used as the member's attribute name. - * :attr:`~aenum.Enum._value_` is the formatted ``'{{name}} [{{value}}]'`` + * ``aenum.Enum._value_`` is the formatted ``'{{name}} [{{value}}]'`` string, so the base's duplicate check against - :attr:`~aenum.Enum._value2member_map_` can never match an + ``aenum.Enum._value2member_map_`` can never match an :class:`int` code. The check runs against :attr:`__members_ns__` instead, the table :meth:`get` resolves codes through: both ``name``'s own namespace and ``opt``, whose codes are common to @@ -202,7 +202,7 @@ def register_alias(cls, value: 'int', name: 'str') -> '{NAME}': Overrides :meth:`~pcapkit.corekit.enum.EnumRegistry.register_alias` for the same reason as :meth:`register`: the base looks the - :class:`int` code up in :attr:`~aenum.Enum._value2member_map_`, whose + :class:`int` code up in ``aenum.Enum._value2member_map_``, whose keys are the formatted ``'{{name}} [{{value}}]'`` strings, so it could never find one. @@ -234,7 +234,7 @@ def register_alias(cls, value: 'int', name: 'str') -> '{NAME}': raise ValueError(f'{{value!r}} is not a registered {{cls.__name__}} in namespace ' f'{{namespace!r}} or opt; use {{cls.__name__}}.register() to mint one') - # NOTE: :func:`~aenum.extend_enum` builds a throwaway member through + # NOTE: ``aenum.extend_enum`` builds a throwaway member through # :meth:`__new__` to learn its value, and :meth:`__new__` files that # throwaway in :attr:`__members_ns__` over ``member``. The alias # resolves to ``member``, so ``member`` is put back. diff --git a/pcapkit/vendor/reg/apptype/apptype.py b/pcapkit/vendor/reg/apptype/apptype.py index 2af795b2b4..dd56f1153d 100644 --- a/pcapkit/vendor/reg/apptype/apptype.py +++ b/pcapkit/vendor/reg/apptype/apptype.py @@ -277,7 +277,7 @@ def get(cls, key: 'int | str', default: 'Any' = NO_DEFAULT) -> 'TransportProtoco # signature admits -- an unrecognised :class:`int` still reaches the # caller as a :exc:`ValueError`, now the base's own # :exc:`~pcapkit.utilities.exceptions.EnumValueError` since GitHub issue - # #923 rather than :mod:`aenum`'s bare one. It is not compatible for + # #923 rather than ``aenum``'s bare one. It is not compatible for # keys outside it: ``None``, a :class:`float` and an unhashable key # used to raise :exc:`AttributeError` from the ``key.lower()`` this # branch no longer reaches, and now raise :exc:`ValueError` (or, for a @@ -289,12 +289,12 @@ def get(cls, key: 'int | str', default: 'Any' = NO_DEFAULT) -> 'TransportProtoco return super().get(key, default) # NOTE: ``_missing_`` used to range-check ``value`` and then defer to - # :mod:`aenum`'s own ``Flag._missing_``, which is what composed an + # ``aenum``'s own ``Flag._missing_``, which is what composed an # unrecognised bit combination into a pseudo-member -- ``TransportProtocol(3)`` # returning ``tcp|udp`` -- GitHub issue #647's guard against that composing # *anything*, including values no member declares. GitHub issue #808 removed # the ``IntFlag`` base once nothing built a composite, and with it the only - # reason this method existed: a plain :class:`~aenum.IntEnum` already raises + # reason this method existed: a plain ``aenum.IntEnum`` already raises # ``ValueError`` for a value no member declares, with no ``_missing_`` of # its own needed to get there, so declaring one here would only be # reproducing what the base class already does. @@ -311,7 +311,7 @@ class {NAME}(EnumRegistry, StrEnum): :class:`~pcapkit.const.reg.apptype.udp.UDP`, :class:`~pcapkit.const.reg.apptype.sctp.SCTP` or :class:`~pcapkit.const.reg.apptype.dccp.DCCP`. Being memberless is also what - makes it subclassable at all, since :mod:`aenum` refuses to extend an + makes it subclassable at all, since |aenum|_ refuses to extend an enumeration that already has members. Note: @@ -614,7 +614,7 @@ def get(cls, key: 'int', *, # type: ignore[override] # pylint: disable=argument if member.svc == canonical: return member # NOTE: IANA's oldest row, for a port whose collision postdates - # :data:`__canonical__` -- including one :func:`~aenum.extend_enum` + # :data:`__canonical__` -- including one ``aenum.extend_enum`` # created, which must not displace what the registry already # answered with. return matched[0] @@ -815,7 +815,7 @@ def register_alias(cls, port: 'int', name: 'str') -> '{NAME}': # pylint: disabl IANA itself never registered it there. No new storage: the new member simply joins :data:`__registry__` at - ``port``, through the same :func:`~aenum.extend_enum` call + ``port``, through the same ``aenum.extend_enum`` call :meth:`register` uses to mint the first one. It is then reachable through :attr:`aliases` on every other member already on ``port``, and vice versa, with nothing further to keep in sync. diff --git a/tests/project/test_docs_epilog_links_1936_unit.py b/tests/project/test_docs_epilog_links_1936_unit.py index 94baa28936..331a1a1580 100644 --- a/tests/project/test_docs_epilog_links_1936_unit.py +++ b/tests/project/test_docs_epilog_links_1936_unit.py @@ -35,14 +35,6 @@ #: it, so no one project page is its target, and it stays a literal. LINKED = ('aenum', 'cryptography', 'dpkt', 'emoji', 'pcapfile', 'pyshark') -#: Paths, relative to :data:`ROOT`, whose literals the scan does not read: the -#: generated constants and the vendor templates that generate them. -EXCLUDED = ( - 'pcapkit/vendor/pcapng/option_type.py', - 'pcapkit/vendor/reg/apptype/apptype.py', - 'pcapkit/const/', -) - #: A list of literals that names engines, adapters or toolkits. ENGINE_LIST = re.compile(r'``\w+``(?:,\s+``\w+``)*(?:,?\s+(?:and|or)\s+``\w+``)?' r'\s+(?:engines?|toolkits?|adapters?)\b') @@ -162,7 +154,9 @@ def test_the_scan_reads_the_sources_it_claims_to(self) -> 'None': 'docs/source/index.rst', 'pcapkit/corekit/enum.py', 'pcapkit/foundation/engines/pypcapfile.py', - 'pcapkit/toolkit/pyshark.py'): + 'pcapkit/toolkit/pyshark.py', + 'pcapkit/vendor/reg/apptype/apptype.py', + 'pcapkit/const/reg/apptype/apptype.py'): self.assertIn(expected, names) self.assertFalse(any(name.startswith('docs/source/changelog/') for name in names)) @@ -179,8 +173,6 @@ def test_no_bare_literal_of_a_linked_package(self) -> 'None': offenders = [] for path in _sources(): rel = _rel(path) - if any(rel == ex or rel.startswith(ex) for ex in EXCLUDED): - continue text = _rendered(path) spans = [m.span() for m in ENGINE_LIST.finditer(text)] spans += [m.span() for m in TABLE_CELL.finditer(text)] diff --git a/tests/project/test_docs_unresolvable_roles_1919_unit.py b/tests/project/test_docs_unresolvable_roles_1919_unit.py index d6c4929c70..81fa25e6ff 100644 --- a/tests/project/test_docs_unresolvable_roles_1919_unit.py +++ b/tests/project/test_docs_unresolvable_roles_1919_unit.py @@ -11,20 +11,20 @@ (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/`. + the conventions pages; this class checks every 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 +Scoped to source files. The class formatter in :file:`docs/source/conf.py` +covers the build-time 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. +the file's own source like any other. The generated modules under +``pcapkit/const/`` are scanned along with the ``pcapkit/vendor/`` templates +that generate them, so a role a template writes fails in both. """ @@ -36,14 +36,6 @@ 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'}) @@ -55,16 +47,9 @@ 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')) \ + """Every ``.rst`` and ``.py`` under :file:`docs/` and ``.py`` under :file:`pcapkit/`.""" + return 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]]': @@ -100,11 +85,12 @@ def test_the_scan_reads_the_pages_and_modules_it_claims_to(self) -> 'None': 'pcapkit/corekit/enum.py', 'pcapkit/foundation/engines/pypcap.py', 'pcapkit/vendor/default.py', + 'pcapkit/vendor/pcapng/option_type.py', + 'pcapkit/vendor/reg/apptype/apptype.py', + 'pcapkit/const/pcapng/option_type.py', + 'pcapkit/const/reg/apptype/apptype.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."""