From 81d4ab2847eed7374ab2057e103273d5795f141d Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Mon, 26 Oct 2026 16:21:30 -0400 Subject: [PATCH] docs(const): move the TransportProtocol 1.5.0 note to the docs and restate get (#2315) Problem: the apptype template's TransportProtocol comment kept a "Before 1.5.0 ..." release note, and TransportProtocol.get's docstring kept decision history ("So there was no policy here to preserve"), both mirrored in the generated module. Fix: drop the release note from the template and the generated module (edited identically, no re-crawl), carry its bare-``4``-resolves-as-``dccp`` hazard into the reg.rst ``versionchanged:: 1.5.0`` note, and restate get's exception passage as current behaviour. Test: the #1884 guard also refuses release-dated prose ("Before 1.5.0") in pcapkit/const and in vendor template text, and pins the moved note on reg.rst. --- docs/source/pcapkit/const/reg.rst | 9 ++-- pcapkit/const/reg/apptype/apptype.py | 20 +++----- pcapkit/vendor/reg/apptype/apptype.py | 20 +++----- ...irectives_in_generated_tables_1884_unit.py | 48 +++++++++++++++++-- 4 files changed, 63 insertions(+), 34 deletions(-) diff --git a/docs/source/pcapkit/const/reg.rst b/docs/source/pcapkit/const/reg.rst index 5894a5255f..fffa98590b 100644 --- a/docs/source/pcapkit/const/reg.rst +++ b/docs/source/pcapkit/const/reg.rst @@ -84,9 +84,12 @@ registry is one enumeration per transport protocol sharing a memberless base. ``aenum.IntEnum`` rather than an ``aenum.IntFlag`` (:issue:`808`), numbered sequentially: ``sctp`` is ``3`` and ``dccp`` is ``4``, where they were ``4`` and ``8``, and no :class:`int` is decoded as a ``|`` composite - (:issue:`860`). Its ``get()`` refuses a name no member declares, with - :exc:`~pcapkit.utilities.exceptions.EnumKeyError`, rather than minting a - member for it (:issue:`808`, :issue:`923`). + (:issue:`860`). An :class:`int` kept from the old numbering is refused + unless a member carries it, and where one does it can name a different + transport: ``3``, once ``tcp | udp``, is ``sctp``, and a bare ``4`` is + ``dccp``, not ``sctp``. ``TransportProtocol.get()`` refuses a name no member + declares, with :exc:`~pcapkit.utilities.exceptions.EnumKeyError`, rather + than minting a member for it (:issue:`808`, :issue:`923`). .. autoclass:: pcapkit.const.reg.apptype.apptype.TransportProtocol :members: diff --git a/pcapkit/const/reg/apptype/apptype.py b/pcapkit/const/reg/apptype/apptype.py index b26f91e54e..31fab56809 100644 --- a/pcapkit/const/reg/apptype/apptype.py +++ b/pcapkit/const/reg/apptype/apptype.py @@ -58,10 +58,6 @@ class TransportProtocol(EnumLookup, IntEnum): # value it already is; a plain ``dict.get`` lookup cannot tell a composed # ``int`` from any other one that happens to equal it. So ``tcp | udp`` # (``3``) resolves as ``sctp``'s own value, silently, with no exception. - # Before 1.5.0 the class was an ``IntFlag`` with ``sctp`` at ``4`` and - # ``dccp`` at ``8``, so an integer held from that numbering (composed or - # not) can land on a different real transport's registry rather than - # merely be refused: a bare ``4`` resolves as ``dccp``'s. #: No transport protocol. ``TransportProtocol(0) is undefined`` and #: ``bool(undefined)`` is ``False``; it is the ``proto`` sentinel default @@ -99,15 +95,13 @@ def get(cls, key: 'int | str', default: 'Any' = NO_DEFAULT) -> 'TransportProtoco that the exception follows what stdlib's ``Enum`` would raise in the circumstances, :exc:`ValueError` or :exc:`KeyError` as the case may be, and comes from :mod:`pcapkit.utilities.exceptions` rather than - being a builtin. A - stdlib ``E['nosuch']`` raises :exc:`KeyError`, and :issue:`923`'s census - found :class:`~pcapkit.corekit.enum.EnumLookup` subclasses answering a - name miss that way. The exceptions were :class:`AppType` and its - per-transport registries, and they answered with :exc:`ValueError` only - because their own ``get()`` takes an :class:`int` port and never accepts - a name at all, rather than from any name-miss policy. So there was no policy here to - preserve, and a name miss reaches the caller as - :exc:`~pcapkit.utilities.exceptions.EnumKeyError` from the base. The + being a builtin. A stdlib ``E['nosuch']`` raises :exc:`KeyError`, so a + name miss reaches the caller as + :exc:`~pcapkit.utilities.exceptions.EnumKeyError` from the base. + :class:`AppType` and its per-transport registries raise + :exc:`ValueError` from their own ``get()`` because it takes an + :class:`int` port and never accepts a name; that is not a name-miss + policy, so it sets none for this class to follow. The maintainer's ruling given in review of the work for :issue:`808`, that :class:`TransportProtocol` is not to be extended at all, holds either way: the refusal is a refusal and mints nothing, whichever exception diff --git a/pcapkit/vendor/reg/apptype/apptype.py b/pcapkit/vendor/reg/apptype/apptype.py index dcec05a057..3fe2b99f1e 100644 --- a/pcapkit/vendor/reg/apptype/apptype.py +++ b/pcapkit/vendor/reg/apptype/apptype.py @@ -150,10 +150,6 @@ class TransportProtocol(EnumLookup, IntEnum): # value it already is; a plain ``dict.get`` lookup cannot tell a composed # ``int`` from any other one that happens to equal it. So ``tcp | udp`` # (``3``) resolves as ``sctp``'s own value, silently, with no exception. - # Before 1.5.0 the class was an ``IntFlag`` with ``sctp`` at ``4`` and - # ``dccp`` at ``8``, so an integer held from that numbering (composed or - # not) can land on a different real transport's registry rather than - # merely be refused: a bare ``4`` resolves as ``dccp``'s. #: No transport protocol. ``TransportProtocol(0) is undefined`` and #: ``bool(undefined)`` is ``False``; it is the ``proto`` sentinel default @@ -191,15 +187,13 @@ def get(cls, key: 'int | str', default: 'Any' = NO_DEFAULT) -> 'TransportProtoco that the exception follows what stdlib's ``Enum`` would raise in the circumstances, :exc:`ValueError` or :exc:`KeyError` as the case may be, and comes from :mod:`pcapkit.utilities.exceptions` rather than - being a builtin. A - stdlib ``E['nosuch']`` raises :exc:`KeyError`, and :issue:`923`'s census - found :class:`~pcapkit.corekit.enum.EnumLookup` subclasses answering a - name miss that way. The exceptions were :class:`AppType` and its - per-transport registries, and they answered with :exc:`ValueError` only - because their own ``get()`` takes an :class:`int` port and never accepts - a name at all, rather than from any name-miss policy. So there was no policy here to - preserve, and a name miss reaches the caller as - :exc:`~pcapkit.utilities.exceptions.EnumKeyError` from the base. The + being a builtin. A stdlib ``E['nosuch']`` raises :exc:`KeyError`, so a + name miss reaches the caller as + :exc:`~pcapkit.utilities.exceptions.EnumKeyError` from the base. + :class:`AppType` and its per-transport registries raise + :exc:`ValueError` from their own ``get()`` because it takes an + :class:`int` port and never accepts a name; that is not a name-miss + policy, so it sets none for this class to follow. The maintainer's ruling given in review of the work for :issue:`808`, that :class:`TransportProtocol` is not to be extended at all, holds either way: the refusal is a refusal and mints nothing, whichever exception diff --git a/tests/project/test_no_version_directives_in_generated_tables_1884_unit.py b/tests/project/test_no_version_directives_in_generated_tables_1884_unit.py index 6a0ed27ac6..b81bb7e3de 100644 --- a/tests/project/test_no_version_directives_in_generated_tables_1884_unit.py +++ b/tests/project/test_no_version_directives_in_generated_tables_1884_unit.py @@ -20,6 +20,10 @@ checks on a sample module which literals count as template text. * :class:`TestDocsPages` pins the notes the docs pages carry for the tables (#1884). +The same holds for a release-versioned note written as prose rather than as a +directive, such as "Before 1.5.0 the class was ..." (#2315), so both scans also +refuse :data:`RELEASE_PROSE`. + This module is unit-tier and reads no capture. """ @@ -41,6 +45,11 @@ #: A version directive, as Sphinx spells it: reST allows any whitespace after the ``..`` (#1902). DIRECTIVE = re.compile(r'\.\.\s+version(?:added|changed|removed)::.*') +#: A note dated to a release in prose (#2315): a lead-in such as ``Before`` and +#: a three-part release number. Python versions are spelled with two parts here. +RELEASE_PROSE = re.compile(r'\b(?:[Bb]efore|[Ss]ince|[Uu]ntil|[Aa]s of|[Pp]rior to)\s+' + r'(?:(?:PyPCAPKit|pcapkit|version|release)\s+)?v?\d+\.\d+\.\d+') + #: The crawler modules #1884 names, whose templates must carry no directive. ISSUE_VENDORS = ( 'ftp/command.py', @@ -65,6 +74,7 @@ ('reg.rst', 'versionchanged:: 1.1.1', 'port number'), ('reg.rst', 'versionchanged:: 1.5.0', ':issue:`732`'), ('reg.rst', 'versionchanged:: 1.5.0', ':issue:`808`'), + ('reg.rst', 'versionchanged:: 1.5.0', 'a bare ``4``'), ('tcp.rst', 'versionchanged:: 1.5.0', '16-bit'), ) @@ -127,11 +137,12 @@ def template_literals(tree: 'ast.Module', crawlers: 'frozenset[str]') -> 'list[a return literals -def template_directives(tree: 'ast.Module', crawlers: 'frozenset[str]') -> 'list[tuple[int, str]]': - """Every directive in the template text, as ``(literal's first line, directive)``.""" +def template_directives(tree: 'ast.Module', crawlers: 'frozenset[str]', + pattern: 're.Pattern[str]' = DIRECTIVE) -> 'list[tuple[int, str]]': + """Every ``pattern`` match in the template text, as ``(literal's first line, match)``.""" return [(node.lineno, match.group().strip()) for node in template_literals(tree, crawlers) - for match in DIRECTIVE.finditer(node.value)] + for match in pattern.finditer(node.value)] def _vendor_trees() -> 'dict[pathlib.Path, ast.Module]': @@ -141,7 +152,7 @@ def _vendor_trees() -> 'dict[pathlib.Path, ast.Module]': class TestGeneratedModules(unittest.TestCase): - """No module under :mod:`pcapkit.const` carries a version directive.""" + """No module under :mod:`pcapkit.const` carries a version note.""" def test_no_const_module_carries_a_directive(self) -> 'None': paths = sorted((ROOT / 'pcapkit' / 'const').rglob('*.py')) @@ -154,9 +165,19 @@ def test_no_const_module_carries_a_directive(self) -> 'None': found.append(f'{path.relative_to(ROOT)}:{lineno}: {match.group().strip()}') self.assertEqual(found, []) + def test_no_const_module_dates_a_note_to_a_release(self) -> 'None': + # #2315: "Before 1.5.0 ..." is a version note in prose. + found = [] # type: list[str] + for path in sorted((ROOT / 'pcapkit' / 'const').rglob('*.py')): + for lineno, line in enumerate(path.read_text(encoding='utf-8').splitlines(), start=1): + match = RELEASE_PROSE.search(line) + if match is not None: + found.append(f'{path.relative_to(ROOT)}:{lineno}: {match.group()}') + self.assertEqual(found, []) + class TestVendorTemplates(unittest.TestCase): - """No crawler's template text carries a version directive.""" + """No crawler's template text carries a version note.""" def test_no_template_carries_a_directive(self) -> 'None': trees = _vendor_trees() @@ -166,6 +187,23 @@ def test_no_template_carries_a_directive(self) -> 'None': for lineno, directive in template_directives(tree, crawlers)] self.assertEqual(found, []) + def test_no_template_dates_a_note_to_a_release(self) -> 'None': + # #2315: "Before 1.5.0 ..." is a version note in prose. + trees = _vendor_trees() + crawlers = vendor_classes(trees.values()) + found = [f'{path.relative_to(ROOT)}:{lineno}: {match}' + for path, tree in trees.items() + for lineno, match in template_directives(tree, crawlers, RELEASE_PROSE)] + self.assertEqual(found, []) + + def test_release_prose_is_matched(self) -> 'None': + for text in ('Before 1.5.0 the class', 'since PyPCAPKit 1.4.1', 'Prior to\nv1.3.0'): + with self.subTest(text=text): + self.assertIsNotNone(RELEASE_PROSE.search(text)) + for text in ('from 3.9 only', 'before 3.9', 'RFC 1.5'): + with self.subTest(text=text): + self.assertIsNone(RELEASE_PROSE.search(text)) + def test_the_issue_templates_are_read(self) -> 'None': # Guards the scan above against passing because it read nothing. trees = _vendor_trees()