Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions docs/source/contributing/conventions/process.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,12 @@ commands down instead of a figure that will be stale by the next merge:

The grouping scheme was settled on :issue:`918`: **a section per top-level module, with**
``Added``/``Changed``/``Fixed`` **nested inside each** -- module granularity, not
per-file and not per-subpackage. The file carries **10** module-level sections holding
298 entries, and no entry carries an inline kind label::
per-file and not per-subpackage. No entry in the file carries an inline kind label::

$ grep -cE '^\* \*\*(Added|Changed|Fixed)\*\*' docs/source/changelog/1.5.0.rst
0

Nine of those ten name a module; the tenth, *Project infrastructure*, is for what
Each section names a module except *Project infrastructure*, which is for what
belongs to none. The map is one-to-one with the package list below -- every package,
:mod:`pcapkit.interface` included, has a section of its own:

Expand Down
42 changes: 20 additions & 22 deletions docs/source/contributing/conventions/registry-protocol.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ Three things about it are easy to get wrong:

Re-parenting every non-registry enumeration onto
:class:`~pcapkit.corekit.enum.EnumLookup` was **phase 2** of
:issue:`877`, and it is **complete**: the phase landed for 24 of the 24
non-registry enumerations. **Zero enumerations remain outside the hierarchy**,
measured by a runtime walk over both the :mod:`enum` and ``aenum`` flavours.
:issue:`877`, and it is **complete**: the phase landed for every non-registry
enumeration. **No enumeration remains outside the hierarchy**, measured by a
runtime walk over both the :mod:`enum` and ``aenum`` flavours.

Failed-Lookup Exceptions
~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -280,7 +280,7 @@ and it is the trap for whoever writes the next override: the base branches on
branched on ``isinstance(key, int)`` and fell through to the *name* path for anything
else. ``get(None)`` therefore raised a quiet
:exc:`~pcapkit.utilities.exceptions.EnumKeyError` on those two and a loud
:exc:`~pcapkit.utilities.exceptions.EnumValueError` on the other four of the six
:exc:`~pcapkit.utilities.exceptions.EnumValueError` on the other
locally-defined helpers in those two modules, and no prose
anywhere said so. Re-implementing the dispatch is how an override acquires a
divergence nobody wrote down; delegating to it is how it does not.
Expand Down Expand Up @@ -366,26 +366,24 @@ The Audit, per Class
registry added later has something to check itself against. The owner set its scope:
audit every registry first, and decide case-sensitivity per registry from that.

The population it covers, with the counting convention spelled out because the
figures move: **127** :class:`~pcapkit.corekit.enum.EnumRegistry` subclasses, every
one of them under :mod:`pcapkit.const`, across 124 files -- 117 :class:`int`-valued
(of which 5 are flag registries) and 10 ``aenum.StrEnum``-valued. Plus **24**
non-registry enumerations counted by a runtime walk over both the :mod:`enum` and
``aenum`` flavours and including nested classes: 17 top level (3 of them under
:mod:`pcapkit.const` itself) and 7 nested, the nested ones being
``FrameType.Flags`` in :mod:`pcapkit.protocols.schema.application.httpv2` plus its
6 concrete per-frame subclasses. 151 enumerations in total.

**The** :class:`int`\ **-valued tier, all 117, is case-sensitive, and the criterion is
vacuous on it rather than merely unmet.** A registry whose values are numbers has
The population it covers is every :class:`~pcapkit.corekit.enum.EnumRegistry`
subclass, every one of them under :mod:`pcapkit.const` and each either
:class:`int`-valued (the flag registries among them) or ``aenum.StrEnum``-valued,
plus every non-registry enumeration a runtime walk over both the :mod:`enum` and
``aenum`` flavours finds, nested classes included -- such as ``FrameType.Flags`` in
:mod:`pcapkit.protocols.schema.application.httpv2` and its concrete per-frame
subclasses.

**The** :class:`int`\ **-valued tier is case-sensitive throughout, and the criterion
is vacuous on it rather than merely unmet.** A registry whose values are numbers has
nothing for case to apply to; the only way a string reaches
:meth:`~pcapkit.corekit.enum.EnumLookup.get` on one is as a member *name*, and a name
is the Python identifier :meth:`pcapkit.vendor.default.Vendor.safe_name` derives from
the registry's own name column -- it preserves the registrar's casing exactly, but it
is not itself a value any specification states a comparison rule for. Measured across
all 151 enumerations: exactly **one** would collide if names were folded --
:class:`~pcapkit.const.hip.parameter.Parameter`, on ``R1_Counter`` against
``R1_COUNTER`` -- and **no** enumeration anywhere has two ``str`` *values* that
every enumeration: **only** :class:`~pcapkit.const.hip.parameter.Parameter` would
collide if names were folded, on ``R1_Counter`` against ``R1_COUNTER`` -- and
**no** enumeration anywhere has two ``str`` *values* that
collide when folded. So folding names is not merely unjustified, it is unsafe in a
measured case; folding values is safe but unjustified except where the table below
says otherwise.
Expand Down Expand Up @@ -466,16 +464,16 @@ That leaves the classes with something to decide:
``a`` 18, ``p`` 16, ``s/p`` 3, blank 1; ``o`` 28, ``m`` 27, ``h`` 7,
``m [1]`` 2). Both tallies cover all 64 rows.
- **open** -- see below
* - The 6 :mod:`~pcapkit.protocols.internet.mh` and
* - The :mod:`~pcapkit.protocols.internet.mh` and
:mod:`~pcapkit.protocols.application.ngap` helper enumerations
- IANA Mobility Header registries, 3GPP TS 38.413
- ``FastBindingAcknowledgmentStatus``, ``IPv6AddressPrefixCode``,
``LMAAddressCode``, ``LocalizedRoutingStatus``, ``Criticality`` and ``PDUKind``.
**Five** of the six carry numeric codes, so the criterion is vacuous on them
**All but** ``PDUKind`` carry numeric codes, so the criterion is vacuous on them
exactly as for the :class:`int` tier above; ``PDUKind``'s are :class:`str` ASN.1
identifiers from 3GPP TS 38.413, and ASN.1 identifiers are case-significant by
construction, so it lands the same way for a different reason.
**None** of the six defines a ``get`` of its own. Two did when this
**None** of them defines a ``get`` of its own. Two did when this
audit was taken --
``FastBindingAcknowledgmentStatus`` and ``IPv6AddressPrefixCode``, for
signature reasons (no ``default``, and an :class:`int`/:class:`str` dispatch)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ with no rule naming any of them wrong. GitHub issue :issue:`937` closed that gap
owner ruled for SCREAMING_SNAKE and accepted the resulting breaking change outright, with
no backport. So the object is named in SCREAMING_SNAKE and the type-naming rule above
derives from it mechanically -- title-case each underscore-separated word and append
``Type``, no per-sentinel exception needed. The four in the tree follow it:
``Type``, no per-sentinel exception needed. The sentinels in the tree follow it:

.. list-table::
:header-rows: 1
Expand Down
9 changes: 5 additions & 4 deletions docs/source/contributing/releasing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -331,13 +331,14 @@ checks *is* the one it is about to create:

* ``tag`` checks whether ``conda-<version>+0``, the tag *it* creates, already
exists.
* ``pypi`` compares PyPI's file count for the version against the 7 expected
-- the 6 supported legs' wheels plus the sdist kept only on the 3.15 leg; the
* ``pypi`` compares PyPI's file count for the version against the expected one
-- a wheel per supported leg plus the sdist kept only on the 3.15 leg; the
KTLO legs' wheels (`#1633 <https://github.com/JarryShaw/PyPCAPKit/issues/1633>`__) are not counted. This job is
already idempotent (``skip-existing: true`` on both upload steps), so the
check exists only to save the cost of the build matrix, not for correctness.
* ``conda`` compares Anaconda's aggregate count against the 10 expected (2 OS
x 5 Python versions) as the same kind of job-level cost saver, and *also*
* ``conda`` compares Anaconda's aggregate count against the expected one (a
file per OS and Python version of its matrix) as the same kind of job-level
cost saver, and *also*
checks, per matrix leg, whether that leg's own platform/Python distribution
is already there -- this second check is the actual correctness fix, because
``anaconda/actions/upload-package`` has no ``skip-existing`` equivalent, only
Expand Down
34 changes: 16 additions & 18 deletions docs/source/contributing/workflows.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ GitHub Actions Workflows

.. important::

The thirteen workflows under :file:`.github/workflows/` trigger each other, and
The workflows under :file:`.github/workflows/` trigger each other, and
the chain that results is not visible from any single file -- reading one
``on:`` block never shows what a *different* workflow's completion goes on
to start. This page is the repository-wide graph. The release *pipeline*
Expand Down Expand Up @@ -295,15 +295,13 @@ repository target the same reusable workflow, with the same input:
* ``.github/workflows/deploy-pages.yml:55`` -- job ``unit-tests`` calls
``./.github/workflows/unit-tests.yml`` with ``gate-only: true``.

``gate-only: true`` selects **three** of ``unit-tests.yml``'s **fourteen** jobs,
not one: ``gate`` (the full suite, on ``latest``), ``changelog``
(checks ``CHANGELOG.md`` against its source entry) and ``python-versions``,
which reads ``gate``'s version (see `Python Versions`_ below). It skips the other
**eleven** -- the eight matrix jobs, ``test`` (five Python legs), ``test-ktlo``
(four), ``integration`` (five), ``integration-ktlo`` (four),
``engine-tests`` (one job for each of five engines, each looping over six
Python versions), ``pypcap-parity`` (one), ``pypcap-parity-ktlo`` (one) and
``unittest-ordering`` (eleven matrix cells), each of which has already run once for this commit
``gate-only: true`` selects not only ``unit-tests.yml``'s ``gate`` (the full
suite, on ``latest``) but also ``changelog`` (checks ``CHANGELOG.md`` against its
source entry) and ``python-versions``, which reads ``gate``'s version (see
`Python Versions`_ below). It skips the rest -- the matrix jobs, ``test``,
``test-ktlo``, ``integration``, ``integration-ktlo``, ``engine-tests`` (a job per
engine, each looping over its Python versions), ``pypcap-parity``,
``pypcap-parity-ktlo`` and ``unittest-ordering``, each of which has already run once for this commit
from Unit Tests' own ``push`` trigger (and, all but ``unittest-ordering``,
which runs on ``main`` pushes only, from its ``pull_request`` trigger), so
running any of them again per caller would
Expand Down Expand Up @@ -603,7 +601,7 @@ in the supported tier, so its cells block on every run.
Required Status Checks
----------------------

Ruleset ``23497679`` on ``main`` requires six contexts, with
Ruleset ``23497679`` on ``main`` requires the contexts below, with
``strict_required_status_checks_policy: true`` (a branch must be up to date
with ``main`` before it can merge, not merely green). Each was found by
grepping every workflow file for its name:
Expand Down Expand Up @@ -640,14 +638,14 @@ between the tiers below means editing the ruleset in the same change.
``Required checks passed`` is defined by exactly one job
(``unit-tests.yml:1937``). ``Compat Python`` is defined by one, the
``compatibility`` job in ``python-compatibility.yml`` (``:60``), whose matrix
(``:74``) comes from :file:`.github/python-versions.toml`: the five required legs
are its supported tier (``python-versions.toml:20-24``), and its four KTLO legs
(``:74``) comes from :file:`.github/python-versions.toml`: the required legs
are its supported tier (``python-versions.toml:20-24``), and its KTLO legs
(``python-versions.toml:16-19``) have names ending in ``(KTLO)``, so no required
context matches one. A comment at
``unit-tests.yml:1849`` mentions the string without defining it.

``Required checks passed`` is an aggregate, not a single check run. The ruleset
requires it and the five ``Compat Python 3.11``-``3.15`` legs, nothing else, as
requires it and the ``Compat Python 3.11``-``3.15`` legs, nothing else, as
``unit-tests.yml``'s own comment (``unit-tests.yml:1848-1858``) records. It
``needs:`` the gating jobs of ``unit-tests.yml`` -- ``test``, ``integration``,
``engine-tests`` and ``pypcap-parity``, plus ``changes`` and ``project-tests``
Expand Down Expand Up @@ -721,8 +719,8 @@ are they in ``pypcap-parity``, which also runs on ``ubuntu-latest``; none of the
was measured on that job.

The **experimental** tier is free-threaded CPython and GraalPy, spelt as
setup-python reads them (``3.14t``, ``graalpy-25.4``). Each runs one leg of
``experimental`` (two), the one matrix job of :file:`.github/workflows/experimental.yml`
setup-python reads them (``3.14t``, ``graalpy-25.4``). Each runs a leg of
``experimental``, the matrix job of :file:`.github/workflows/experimental.yml`
("Experimental Tests"), named ``Python <version> (experimental)``, and nothing
else: no ``integration``, ``Compat``, wheel or conda leg, so never a required
context. They were legs of ``test`` until `#1732
Expand Down Expand Up @@ -856,8 +854,8 @@ through ``fromJSON``, and each single-interpreter job reads ``latest``.
off ``latest``, with its reason in the comment above it, such as the conda
jobs' ``!= '3.15'``. ``tests/project/test_python_versions_1660.py`` fails on a
pin the file does not list, on an exemption with no comment, and on a
classifier, doc or comment that no longer matches the file -- the coverage
leg's version, the release checks' counts. Its tests, and the other tests that
classifier, doc or comment that no longer matches the file -- a tier range,
the coverage leg's version. Its tests, and the other tests that
read the file, skip on the KTLO interpreters, which have no ``tomllib``. Two
edits it names rather than makes: a new ``pypi`` KTLO leg needs its own
``ktlo_*`` output for ``release_status``, and a pre-release leg needs
Expand Down
28 changes: 18 additions & 10 deletions tests/corekit/test_sentinel_exports_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,14 +376,17 @@ def test_every_sentinel_object_is_screaming_snake(self) -> 'None':
self.assertRegex(name, pattern)

def test_conventions_doc_lists_every_sentinel_in_the_tree(self) -> 'None':
"""The doc said "three" and listed three; ``_Absent`` was the fourth.

Asserting the names rather than only the count, because a count corrected
without the row -- or a row added without the count -- is the same defect
in a different place. The "Defined in" column now names
:data:`CANONICAL_MODULE` for every row, since GitHub issue #911's housing
move gave all four the same defining module -- checked once, outside the
loop, rather than once per row against a value that no longer varies.
"""The doc once stated a count and listed that many; ``_Absent`` was missing.

Asserting the names, because a row missing from the table is the defect, and
the page now states no count for a row to disagree with (#1103's ruling,
#1811). The table's instance column is read back against every singleton
:data:`CANONICAL_MODULE` defines, both ways, so a sentinel added there without
a row, or a row left for one removed, fails here. The "Defined in" column
names :data:`CANONICAL_MODULE` for every row, since GitHub issue #911's
housing move gave every sentinel the same defining module -- checked once,
outside the loop, rather than once per row against a value that no longer
varies.

"""
section = _sentinel_section()
Expand All @@ -394,8 +397,13 @@ def test_conventions_doc_lists_every_sentinel_in_the_tree(self) -> 'None':
self.assertIn(f'``{type_.__name__}``', section)
self.assertIn(CANONICAL_MODULE, section)

self.assertIn('four in the tree follow it', section)
self.assertNotIn('three in the tree follow it', section)
canonical = importlib.import_module(CANONICAL_MODULE)
defined = {name for name, value in vars(canonical).items()
if not isinstance(value, type) and type(value).__module__ == CANONICAL_MODULE}
# The list-table's body: the block after its options, up to the next blank line.
table = section.split('.. list-table::', 1)[1].split('\n\n')[1]
rows = set(re.findall(r'(?m)^ \* - ``(\w+)``$', table))
self.assertEqual(rows, defined)
self.assertNotIn('The three sentinels deliberately differ', section)

def test_conventions_doc_records_that_only_the_object_is_exported(self) -> 'None':
Expand Down
Loading
Loading