feat(reg)!: split AppType into per-transport registries, dropping the portless rows - #754
Conversation
| '''.strip() # type: Callable[[str, str, str, str, str, str, str], str] | ||
|
|
||
|
|
||
| class Record: |
There was a problem hiding this comment.
can be an Info class or dataclasses.dataclass.
There was a problem hiding this comment.
Done — Info, since it is the house idiom and it fits.
The one snag is that Info.__setattr__ raises UnsupportedCall, and the record is
accumulated across several CSV rows. Rather than reach for a mutable dataclass I made
every growing field a container appended to in place, which Info permits. That turned out
to be the better shape anyway: the #: comment used to be accumulated by string surgery
(if cmmt.startswith('-')), re-deriving from its own output whether it had already been
merged. It is now parts: list[str], one wrapped comment per registry row, joined at
emission — so the startswith branch is gone.
Verified the refactor changes no generated output: tcp.py, udp.py, sctp.py and
dccp.py are byte-identical before and after.
| return extend_enum(TransportProtocol, key.lower(), max_val * 2) | ||
|
|
||
| @classmethod | ||
| def _missing_(cls, value: 'int') -> 'TransportProtocol': |
There was a problem hiding this comment.
i dont think we need _missing_ for TransportProtocol. anything not existing should not be accepted.
There was a problem hiding this comment.
I think this one is backwards, and I have left it in place — please overrule me if so.
_missing_ is what implements "anything not existing should not be accepted". aenum on
its own is permissive. Measured on aenum 3.1.17, same class body with the method removed:
TransportProtocol(-1) -> <tcp|udp|sctp|dccp: 15> # #647's recorded defect, verbatim
TransportProtocol(16) -> <TransportProtocol: 16> # accepted, .name is None
TransportProtocol(32) -> accepted
versus, with it:
TransportProtocol(-1) / (16) / (32) -> ValueError
TransportProtocol(3) / (15) -> tcp|udp / tcp|udp|sctp|dccp (composition kept)
So removing it widens what is accepted, and fails three existing tests:
test_every_registry_rejects_a_negative_value, and TransportProtocol(0x10) / (0x20)
raising in test_the_other_two_flag_registries_compose and
test_transport_protocol_can_still_be_extended_at_runtime. ISSUE_647_OUTLIERS names this
class with exactly the symptom above. I have added a Note: to the docstring recording the
measurement, so the next reader does not have to ask.
Note the guard is not itself permissive: 0 <= value <= max(members) * 2 - 1 is precisely
"no undeclared bits", and it tracks extend_enum.
On get() minting (:143, one line up) — you are right that it is the same question, and
I read the two paths as genuinely different rather than inconsistent:
TransportProtocol(value)is the constructor over a closed space. The declared bits are
all the bits there are, so an undeclared bit is unanswerable and must be rejected.TransportProtocol.get(name)is fed IANA's transport column verbatim by the crawler, and
that space is open — it has carriedddp, and a new protocol appearing there should grow
the registry rather than crash the crawl. That is the sameextend_enumgrowth policy the
library's other registries have, and it is pinned by
test_transport_protocol_can_still_be_extended_at_runtime.
If you want the string path to reject too, say so and I will make it raise and re-pin that
test — it is a one-line change, but it is a behaviour change to a shared registry that this PR
otherwise only moves, so I have not taken it unasked.
There was a problem hiding this comment.
No only the specified protocols are accepted.
There was a problem hiding this comment.
Recorded: only the specified protocols are accepted. That overrules the author, and I think it also
settles the Flag → Enum question you were weighing — the two are the same decision.
Measured on 73f09aecb: AppType's 8,182 members carry 11 distinct .proto values — 0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 15. Five are declared members (undefined/tcp/udp/sctp/dccp = 0/1/2/4/8). The
other six — 3, 5, 7, 11, 12, 15 — are compositions, and they cover 5,354 members, 65% of the registry.
So "only the specified" forces a choice, and only one branch is viable:
- Reject compositions outright → 5,354 members cannot express their IANA transport at all. Not viable.
- Declare the compositions → the six observed combinations become named members alongside the five
singles, 11 in total, and then "only the specified are accepted" is exactly true with nothing lost.
This is the Enum move, and it is why the sparse-and-closed measurement matters: 11 of 16 occur, so
naming them is finite and complete.
Under that shape _missing_ is no longer load-bearing for rejection — the member list is the whitelist,
and an undeclared value has no member to resolve to. The author's measurement stands on today's IntFlag
(removing _missing_ there widens acceptance, since aenum turns -1 into all-bits-set — #647's recorded
defect), but it stops applying once compositions are named rather than computed. So the author was right
about the code as it is, and you are right about where it should go.
One consequence to accept deliberately: tcp | udp as an expression stops working, because there is no
bitwise composition to perform. Anything that builds a transport by OR-ing — including
register_apptype's fan-out — moves to an explicit ~11-entry membership map. The author enumerated those
sites; none is in this PR.
This is bigger than #754 and I am not folding it in. #754 moves AppType to a package; retyping
TransportProtocol changes a shared registry's public semantics. Say the word and I will file it as its own
issue with the 11 combinations and the migration sites enumerated, so #754 can merge on its current
behaviour-preserving footing.
… portless rows
- pcapkit/{const,vendor}/reg/apptype.py become packages: a memberless AppType
base plus one registry per transport protocol (TCP, UDP, SCTP, DCCP). aenum
refuses to subclass an enumeration that has members, so the base holds none;
isinstance(TCP.http, AppType) still holds, preserving all 7 isinstance sites.
- The 1,004 rows IANA assigns no port, and the 704 it assigns no transport
protocol, stop being members: they become list-table docstrings on the registry
that owns them, which autodoc publishes.
- __members_proto__ becomes a per-registry MultiDict keyed on port, so all three
services IANA registers on port 80 survive. extend_enum on an occupied port
appends instead of displacing what a lookup returned.
- get() answers with the canonical service, curated from /etc/services because
IANA names no precedence among them; get_all() reaches the aliases. get()'s dead
str branch is removed rather than carried over.
- AppType.get(port, proto=...) proxies to the registry proto names, so nothing
under pcapkit/protocols/ changes.
Re-pinned the const sweep to 9 string registries / 127 classes / 121 modules, and
extended the _dest_path sweep over the nested crawlers. tests/const 52,
tests/vendor + registry 88, tests/protocols/transport + dumpkit 164, all passing.
ea18db0 to
8744aa6
Compare
|
✅ GOOD TO MERGE @ Third independent cross-review (different model), landing after two others above already covered The follow-up (2 files, +61/-31) is documentation + crawler-refactor only, not a behaviour change:
Re-ran the relevant suite fresh at No new findings beyond the five wording corrections already flagged above (33 renamed + 2 removed, not 35; the ✅ GOOD TO MERGE @ |
|
✅ GOOD TO MERGE @ |
|
✅ GOOD TO MERGE @ Housekeeping: the 4 duplicate "@ ea18db0" comments above are stale — posted in error by two of my own subagents that overstepped their briefs. Disregard them; this is the current, authoritative verdict. Re-reviewed the 2-file diff only (
Commit message — leave it out. The byte-identity claim is durably recorded in the thread reply and here; a purely internal, output-neutral refactor is noise in Threads stay open for the maintainer to resolve — |
|
✅ GOOD TO MERGE @ |
|
Housekeeping addendum: this thread now has 8 automated review comments from 3 independent passes (mine, plus two subagents that posted despite being told not to — a coordination failure on my side, not a technical disagreement). All 4 comments at |
Follow-up, and fairly strongly — the retype would blind this PR's own safety argument#754's whole safety case is the behaviour comparison: 12,391 member-port lookups against pre-split, 28 Three more reasons, in order of weight:
And deferring is cheap, which is the deciding fact. The author measured regeneration as one crawler So: merge #754 as it stands, and I will file the retype with the 11 named combinations and the migration One caveat so the deferral is not silently permanent: until the retype lands, |
- `str | bytes | dict[...]` and three `dict[str, ModuleDescriptor[...] | ...]` fields resolved against `AppType`'s (now `TCP`'s) IANA service member named `dict`, not the builtin, because `PyXrefMixin.make_xref` sets `refspecific=True` unconditionally for hand-written `:type:` fields. - Qualified each as `python:dict`, the intersphinx explicit-inventory form: it fails the project's own domain lookup (no local object contains a colon) so `resolve_xref` returns `None`, and intersphinx's `resolve_reference_detect_inventory` then splits on the colon and resolves `dict` against the configured `python` inventory. Renders as plain `dict`, no warning. - Left `exec` alone: swept all 64 `:type:` lines and every `:type`/`:vartype` docstring field in `pcapkit/`, and it appears nowhere as a bare reference, so it has no live wrong link to fix today (it stays a latent registry-level collision, same class, different member). Verified by reading the installed Sphinx 9.1.0's resolution path and by a minimal standalone Sphinx project in /tmp reproducing the TCP/UDP `dict` ambiguity against a local intersphinx inventory: bare `dict` reproduced the "more than one target" warning and linked to the wrong member; `python:dict` resolved externally to the stub builtin with no warning. No full docs build was run (~759s post-#754, tracked in #744). Fixes #717.
…e stale pylint flags
Makefile:126's isort glob (`pcapkit/{const,vendor}/*/*.py`) stopped at a fixed
two levels, so #754's `pcapkit/const/reg/apptype/` package landed its
`__init__.py` a level deeper than either that line or line 125's
`--skip-glob '**/__init__.py'` reached -- sorted by no `make isort` line at
all. Widen it to `*/**/*.py` under `shopt -s globstar`, reaching any depth
below the first subdirectory while still skipping `pcapkit/const/__init__.py`
and `pcapkit/vendor/__init__.py` themselves, whose hand-grouped import order
isort would otherwise flatten. `tests/project/test_isort_clean.py`'s mirror of
that line is updated the same way. The identical two-level glob also lived in
`.github/workflows/cron-vendor.yml`, on the path that actually regenerates the
deep files; fixed there too, with `find -mindepth 2` rather than
`shopt -s globstar`, since that job's macOS runner does not provably have a
globstar-capable `bash` on PATH the way the Makefile's own resolution does.
`PYLINT_FLAGS` loaded `pylint.extensions.emptystring`, which pylint removed at
3.0 (`E0013 bad-plugin-value` every run), and named four `--disable=` checks
pylint has since removed (`old-division`, `no-absolute-import`,
`input-builtin`) or left unloaded (`eq-without-hash`, live in
`pylint.extensions.eq_without_hash` but never in `--load-plugins=`) -- five
stale entries, dropped. This is flag hygiene, not enablement: `E0013` did not
gate `compare-to-empty-string`, which reaches the core `refactoring` checker's
`use-implicit-booleaness-not-comparison-to-string` (C1804) via an `old_names`
alias regardless of the plugin's own load failure, confirmed with
`--list-msgs-enabled` (identical, 399 lines, before and after). `lint.yml`'s
header is corrected to match and re-pinned to 1a85269, since `pcapkit/` had
in fact moved from the previous pin (932cb48) in four files, even though
E/W/C read the same on both.
Fixes #765.
Fixes #767.
Build: `make isort` clean on all three lines; pylint re-measured once, output
redirected to a file and parsed (E 90, W 4765, C 542, R 648, exit 30).
…e stale pylint flags
Makefile:126's isort glob (`pcapkit/{const,vendor}/*/*.py`) stopped at a fixed
two levels, so #754's `pcapkit/const/reg/apptype/` package landed its
`__init__.py` a level deeper than either that line or line 125's
`--skip-glob '**/__init__.py'` reached -- sorted by no `make isort` line at
all. A first pass here widened it to `*/**/*.py` under `shopt -s globstar`,
which needs bash >= 4.0 and is not what the Makefile's own
`SHELL := $(shell command -v bash ...)` guarantees on a machine without a
brew bash ahead of macOS's system 3.2 -- silently: `shopt` on such a machine
just prints one stderr line and the next command still runs, under the old,
non-recursive glob, reopening the #765 hole one level up. Line 126 is now
`$(find pcapkit/const -mindepth 2 -name '*.py') $(find pcapkit/vendor
-mindepth 2 -name '*.py')` instead, needing no particular bash and no shell
option at all, with the same fix applied the same way in
`.github/workflows/cron-vendor.yml`, the one other place this glob lived, on
the path that actually regenerates the deep files.
`tests/project/test_isort_clean.py`'s mirror of line 126 is updated to match.
`PYLINT_FLAGS` loaded `pylint.extensions.emptystring`, which pylint removed at
3.0 (`E0013 bad-plugin-value` every run), and named four `--disable=` checks
pylint has since removed (`old-division`, `no-absolute-import`,
`input-builtin`) or left unloaded (`eq-without-hash`, live in
`pylint.extensions.eq_without_hash` but never in `--load-plugins=`) -- five
stale entries, dropped. This is flag hygiene, not enablement: `E0013` did not
gate `compare-to-empty-string`, which reaches the core `refactoring` checker's
`use-implicit-booleaness-not-comparison-to-string` (C1804) via an `old_names`
alias regardless of the plugin's own load failure, confirmed with
`--list-msgs-enabled` (identical, 399 lines, before and after). `lint.yml`'s
header is corrected to match, re-pinned to 1a85269 since `pcapkit/` had in
fact moved from the previous pin (932cb48) in four files even though E/W/C
read the same on both, and its R/total range is restated on a consistent
basis (the old range's own figures included the five stale messages too).
Fixes #765.
Fixes #767.
Build: `make isort` clean on all three lines; pylint re-measured once, output
redirected to a file and parsed (E 90, W 4765, C 542, R 648, exit 30).
…e stale pylint flags
Makefile:126's isort glob (`pcapkit/{const,vendor}/*/*.py`) stopped at a fixed
two levels, so #754's `pcapkit/const/reg/apptype/` package landed its
`__init__.py` a level deeper than either that line or line 125's
`--skip-glob '**/__init__.py'` reached -- sorted by no `make isort` line at
all. A first pass here widened it to `*/**/*.py` under `shopt -s globstar`,
which needs bash >= 4.0 and is not what the Makefile's own
`SHELL := $(shell command -v bash ...)` guarantees on a machine without a
brew bash ahead of macOS's system 3.2 -- silently: `shopt` on such a machine
just prints one stderr line and the next command still runs, under the old,
non-recursive glob, sorting none of the 32 depth-two `__init__.py` files it
used to cover (the two three levels down under `.../reg/apptype/` stay
reachable either way) -- the hole reopened one level up. A second pass
replaced that with a single `isort` invocation fed by
`$(find pcapkit/const -mindepth 2 -name '*.py')` and the `vendor` equivalent,
needing no particular bash and no shell option at all, but merging both
`find`s into one invocation meant only a fully-empty expansion would fail
loudly; a partially-empty one would exit 0 having quietly skipped half the
tree. Unreachable today -- neither directory can be absent where
`make isort` runs, and no path under either contains whitespace -- but the
same shape of latent gap as the original bug, so line 126 is now split into
two calls, one per directory, matching `.github/workflows/cron-vendor.yml`'s
own two calls exactly (the one other place this glob lived, on the path that
actually regenerates the deep files, fixed the same way for the same reason).
`tests/project/test_isort_clean.py` is restructured for the same four lines,
and the Makefile's own bash pin is re-justified: brace expansion, its
original reason, is gone from the file entirely.
`PYLINT_FLAGS` loaded `pylint.extensions.emptystring`, which pylint removed at
3.0 (`E0013 bad-plugin-value` every run), and named four `--disable=` checks
pylint has since removed (`old-division`, `no-absolute-import`,
`input-builtin`) or left unloaded (`eq-without-hash`, live in
`pylint.extensions.eq_without_hash` but never in `--load-plugins=`) -- five
stale entries, dropped. This is flag hygiene, not enablement: `E0013` did not
gate `compare-to-empty-string`, which reaches the core `refactoring` checker's
`use-implicit-booleaness-not-comparison-to-string` (C1804) via an `old_names`
alias regardless of the plugin's own load failure, confirmed with
`--list-msgs-enabled` (identical, 399 lines, before and after). `lint.yml`'s
header is corrected to match and re-pinned to 1a85269, since `pcapkit/` had
in fact moved from the previous pin (932cb48) in four files even though
E/W/C read the same on both; its R/total range is restated on a consistent
basis and labelled with the tree it was measured on.
Fixes #765.
Fixes #767.
Build: `make isort` clean on all four lines; pylint re-measured once last
round, output redirected to a file and parsed (E 90, W 4765, C 542, R 648,
exit 30) -- unchanged this round, no pcapkit/ file touched.
…e stale pylint flags
Makefile:126's isort glob (`pcapkit/{const,vendor}/*/*.py`) stopped at a fixed
two levels, so #754's `pcapkit/const/reg/apptype/` package landed its
`__init__.py` a level deeper than either that line or line 125's
`--skip-glob '**/__init__.py'` reached -- sorted by no `make isort` line at
all. A first pass here widened it to `*/**/*.py` under `shopt -s globstar`,
which needs bash >= 4.0 and is not what the Makefile's own
`SHELL := $(shell command -v bash ...)` guarantees on a machine without a
brew bash ahead of macOS's system 3.2 -- silently: `shopt` on such a machine
just prints one stderr line and the next command still runs, under the old,
non-recursive glob, sorting none of the 32 depth-two `__init__.py` files it
used to cover (the two three levels down under `.../reg/apptype/` stay
reachable either way) -- the hole reopened one level up. A second pass
replaced that with a single `isort` invocation fed by
`$(find pcapkit/const -mindepth 2 -name '*.py')` and the `vendor` equivalent,
needing no particular bash and no shell option at all, but merging both
`find`s into one invocation meant only a fully-empty expansion would fail
loudly; a partially-empty one would exit 0 having quietly skipped half the
tree. Unreachable today -- neither directory can be absent where
`make isort` runs, and no path under either contains whitespace -- but the
same shape of latent gap as the original bug, so the const/vendor line is now
split into two calls, one per directory, matching
`.github/workflows/cron-vendor.yml`'s own two calls exactly (the one other
place this glob lived, on the path that actually regenerates the deep files,
fixed the same way for the same reason). The Makefile's own bash pin is
re-justified: brace expansion, its original reason, is gone from the file
entirely.
`tests/project/test_isort_clean.py` is restructured for the resulting four
recipe lines, and its own naming is fixed along with it: it used to key its
target-resolution functions on line numbers (`_line_125_targets` and so on),
which is exactly the failure this file exists to catch, and expanding the
Makefile's bash-pin comment above the `isort:` target this same round moved
the real lines to 133-136 while the names still said 125-128 -- caught by a
reviewer's measurement, not by the test. Functions are now named for what
they target (`_pcapkit_tree_targets`, `_const_targets`, `_vendor_targets`,
`_util_examples_targets`), `MAKEFILE_LINES` is a plain list paired with line
numbers positionally rather than keyed by one, and a new
`_isort_recipe_line_numbers()` reads `Makefile` and returns those numbers
fresh every run -- verified by shifting the target down a line in a scratch
copy and confirming the resolver's output shifts with it (132->133,
133-136->134-137). Every prose reference to a specific line number in the
module docstring is swept to a descriptive name for the same reason.
`PYLINT_FLAGS` loaded `pylint.extensions.emptystring`, which pylint removed at
3.0 (`E0013 bad-plugin-value` every run), and named four `--disable=` checks
pylint has since removed (`old-division`, `no-absolute-import`,
`input-builtin`) or left unloaded (`eq-without-hash`, live in
`pylint.extensions.eq_without_hash` but never in `--load-plugins=`) -- five
stale entries, dropped. This is flag hygiene, not enablement: `E0013` did not
gate `compare-to-empty-string`, which reaches the core `refactoring` checker's
`use-implicit-booleaness-not-comparison-to-string` (C1804) via an `old_names`
alias regardless of the plugin's own load failure, confirmed with
`--list-msgs-enabled` (identical, 399 lines, before and after). `lint.yml`'s
header is corrected to match and re-pinned to 1a85269, since `pcapkit/` had
in fact moved from the previous pin (932cb48) in four files even though
E/W/C read the same on both; its R/total range is restated on a consistent
basis and labelled with the tree it was measured on.
Fixes #765.
Fixes #767.
Build: `make isort` clean on all four lines; pylint re-measured once several
rounds back, output redirected to a file and parsed (E 90, W 4765, C 542,
R 648, exit 30) -- unchanged since, no pcapkit/ file touched.
…e stale pylint flags
Makefile:126's isort glob (`pcapkit/{const,vendor}/*/*.py`) stopped at a fixed
two levels, so #754's `pcapkit/const/reg/apptype/` package landed its
`__init__.py` a level deeper than either that line or line 125's
`--skip-glob '**/__init__.py'` reached -- sorted by no `make isort` line at
all. A first pass here widened it to `*/**/*.py` under `shopt -s globstar`,
which needs bash >= 4.0 and is not what the Makefile's own
`SHELL := $(shell command -v bash ...)` guarantees on a machine without a
brew bash ahead of macOS's system 3.2 -- silently: `shopt` on such a machine
just prints one stderr line and the next command still runs, under the old,
non-recursive glob, sorting none of the 32 depth-two `__init__.py` files it
used to cover (the two three levels down under `.../reg/apptype/` stay
reachable either way) -- the hole reopened one level up. A second pass
replaced that with a single `isort` invocation fed by
`$(find pcapkit/const -mindepth 2 -name '*.py')` and the `vendor` equivalent,
needing no particular bash and no shell option at all, but merging both
`find`s into one invocation meant only a fully-empty expansion would fail
loudly; a partially-empty one would exit 0 having quietly skipped half the
tree. Unreachable today -- neither directory can be absent where
`make isort` runs, and no path under either contains whitespace -- but the
same shape of latent gap as the original bug, so the const/vendor line is now
split into two calls, one per directory, matching
`.github/workflows/cron-vendor.yml`'s own two calls exactly (the one other
place this glob lived, on the path that actually regenerates the deep files,
fixed the same way for the same reason). The Makefile's own bash pin is
re-justified: brace expansion, its original reason, is gone from the file
entirely.
`tests/project/test_isort_clean.py` is restructured for the resulting four
recipe lines, and its own naming is fixed along with it: it used to key its
target-resolution functions on line numbers (`_line_125_targets` and so on),
which is exactly the failure this file exists to catch, and expanding the
Makefile's bash-pin comment above the `isort:` target this same round moved
the real lines to 133-136 while the names still said 125-128 -- caught by a
reviewer's measurement, not by the test. Functions are now named for what
they target (`_pcapkit_tree_targets`, `_const_targets`, `_vendor_targets`,
`_util_examples_targets`), `MAKEFILE_LINES` is a plain list paired with line
numbers positionally rather than keyed by one, and a new
`_isort_recipe_line_numbers()` reads `Makefile` and returns those numbers
fresh every run -- verified by shifting the target down a line in a scratch
copy and confirming the resolver's output shifts with it (132->133,
133-136->134-137). Every prose reference to a specific line number in the
module docstring is swept to a descriptive name for the same reason.
`_isort_recipe_line_numbers()` itself needed one more fix: it broke on the
first line that was not tab-indented, which is not what ends a recipe as far
as `make` is concerned -- a blank line or a column-0 `#` comment inside the
recipe is transparent to `make` (measured with `make -n isort` as the
oracle: still four commands with either dropped between two recipe lines),
and the resolver undercounted both, silently, in exactly the way this PR
exists to stop. It now treats those two as transparent too. Documented, not
defended against: a `define`/`endef` block containing a bare `isort:` would
still fool it -- nothing in this Makefile uses `define`. The `MAKEFILE_LINES`
comment is reworded to separate why the list has to match the Makefile
(the "clean locally, red in CI" risk) from what actually checks that it does
(a length comparison, nothing about the flags or targets themselves), and to
note the asymmetry that bounds all of this: flags and targets are literals
in `MAKEFILE_LINES`, never read back out of the Makefile, so a line-number
bug can only mislabel where a failure points, never change which isort
invocation the test actually runs.
`PYLINT_FLAGS` loaded `pylint.extensions.emptystring`, which pylint removed at
3.0 (`E0013 bad-plugin-value` every run), and named four `--disable=` checks
pylint has since removed (`old-division`, `no-absolute-import`,
`input-builtin`) or left unloaded (`eq-without-hash`, live in
`pylint.extensions.eq_without_hash` but never in `--load-plugins=`) -- five
stale entries, dropped. This is flag hygiene, not enablement: `E0013` did not
gate `compare-to-empty-string`, which reaches the core `refactoring` checker's
`use-implicit-booleaness-not-comparison-to-string` (C1804) via an `old_names`
alias regardless of the plugin's own load failure, confirmed with
`--list-msgs-enabled` (identical, 399 lines, before and after). `lint.yml`'s
header is corrected to match and re-pinned to 1a85269, since `pcapkit/` had
in fact moved from the previous pin (932cb48) in four files even though
E/W/C read the same on both; its R/total range is restated on a consistent
basis and labelled with the tree it was measured on.
Fixes #765.
Fixes #767.
Build: `make isort` clean on all four lines; pylint re-measured once several
rounds back, output redirected to a file and parsed (E 90, W 4765, C 542,
R 648, exit 30) -- unchanged since, no pcapkit/ file touched.
…k included `main` moved from 73f09ae to 4530424 while this PR sat open, and the 1.5.0 section cited none of the 25 commits in between. Ten new bullets cover thirteen of them, appended in merge order, with the file's own `**a breaking change**` lead sentence on the three that are breaking: - #754 -- AppType split into per-transport registries; the 1,004 portless and 704 transportless rows stop being members. Breaking. - #764 -- an out-of-range port in `AppType.get` is refused, not minted, so `TCP.make(srcport=99999)` raises; per-transport `_missing_` spans. Breaking. - #778 -- `@final` enforced at runtime on `Info`/`Schema`: bare `@final` raises `InfoError`/`SchemaError` at first construction, deriving from a finalised class raises, and `SchemaError` is a `ValueError` where a caller may have been catching `TypeError`. Breaking. - #772 (with #790's docstring reword), #766, #759, #787, #794 (with #791's citation repoint), #792/#798 and #802 -- the remaining seven. Also re-ran the citation sweep against `origin/main` rather than the checkout. One stale line number fixed: the `httpv2.py` `header.length != 9` guard the `#692` entry calls out moved from `:562` to `:650` under #789 and #802. The preamble's "reaching #726" becomes #805, the new maximum reference. Verified unmoved on 4530424: `protocol.py:1411`, `schema/internet/ipv4.py:336`, `traceflow.py` 146/149/162/424, the four `:type:` fields in `engine.rst`, `reassembly.rst` and `traceflow.rst`, and `EXPECTED_FAILURES` at 43 entries. Carries the previous round's #651/#646 corrections unchanged. Two literals were reflowed so no ``literal`` wraps a line, which the generator's residual guard refuses. `changelog_md.py --check` exit 0; `test_changelog_md.py` 47 passed, 37 subtests.
`main` moved from 73f09ae to 3cbdf89 while this PR sat open. This commit (relative to its parent, 4233555) now names all 20 bullets it carries, not just the 8 this session added on top of the 10 already there -- a first draft of this message named only its own 8 and left the other 10 silent, which a cross-review caught. Four are breaking: - #754 -- AppType split into per-transport registries; the 1,004 portless and 704 transportless rows stop being members. - #764 -- an out-of-range port in `AppType.get` is refused, not minted. - #778 -- `@final` enforced at runtime on `Info`/`Schema`. - #575 -- four `.get()`-backed enum fields fall through to `_unregistered_member` instead of minting; 14 of 23 sample captures change output. - #772 (with #790's docstring reword), #766, #759, #787, #794 (with #791's citation repoint), #792/#798, #802, #782 (a further #745-hazard instance), #704, #723, #739, #743/#746 (cross-dependent, one bullet each), #805 (closes #802's own filed-as-out-of-scope), #796, #800 -- the other 16, non-breaking. Also restores a measurement an earlier round in this same diff dropped while updating an adjacent one: the #692 entry's "mypy is unmoved at 112 errors" silently lost its "and pylint ... 364 messages" half when `EXPECTED_FAILURES` was corrected 44 to 43 elsewhere in the same sentence. Restored to the last value earlier rounds signed off on rather than re-measured, since this branch's own `pcapkit/` tree predates several since-merged PRs and a fresh run would not be measuring the same thing the original round measured. Unmoved, and not silently dropped this time: `93 of the 95 sites` corrected to `94 of the 95` and `EXPECTED_FAILURES` 44 to 43 in the several other places that already carried the fix. On which PRs get a bullet: there is no clean "user-facing only" rule -- #766 and #791 are pure CI/test/lint-comment entries that are in, while #773 and #763 are the same kind of thing and are out. The real pattern across this file's 46 commits is closer to "each round's author judged it worth a reader's time," which is inconsistent by construction. This round leaves that inconsistency as found rather than trying to retrofit a rule, but did add #782 on reconsideration -- its own PR body names it as sharing #766's hazard, and pre-existing precedent already treats that hazard's instances as bullet-worthy. `changelog_md.py` regenerated `CHANGELOG.md`; `--check` exit 0. `test_changelog_md.py` 47 passed.
`main` moved from 73f09ae to 3cbdf89 while this PR sat open. This commit (relative to its parent, 4233555) now names all 20 bullets it carries, not just the 8 this session added on top of the 10 already there -- a first draft of this message named only its own 8 and left the other 10 silent, which a cross-review caught. Six are breaking, matching the crediting PRs' own `breaking` label in each case: - #754 -- AppType split into per-transport registries; the 1,004 portless and 704 transportless rows stop being members. - #764 -- an out-of-range port in `AppType.get` is refused, not minted. - #778 -- `@final` enforced at runtime on `Info`/`Schema`. - #575 -- four `.get()`-backed enum fields fall through to `_unregistered_member` instead of minting; 14 of 23 sample captures change output. - #759 -- `AppType._dispatch` on a multi-transport `proto` now raises `ProtocolError` instead of silently resolving to whichever transport owns the lowest set bit. - #805 -- `FieldBase.length` on a negative resolved length now raises `ProtocolError` instead of letting a bare `struct.error` escape. A second cross-review caught both: their crediting PRs (#783, #811) both carry GitHub's own `breaking` label, and neither bullet said so. - #772 (with #790's docstring reword), #766, #787, #794 (with #791's citation repoint), #792/#798, #802, #779 (via #782, a further #745-hazard instance), #704, #723, #739, #743/#746 (cross-dependent, one bullet each), #796, #800 -- the other 14, non-breaking. Also restores a measurement an earlier round in this same diff dropped while updating an adjacent one: the #692 entry's "mypy is unmoved at 112 errors" silently lost its "and pylint ... 364 messages" half when `EXPECTED_FAILURES` was corrected 44 to 43 elsewhere in the same sentence. Restored to the last value earlier rounds signed off on rather than re-measured, since this branch's own `pcapkit/` tree predates several since-merged PRs and a fresh run would not be measuring the same thing the original round measured. Unmoved, and not silently dropped this time: `93 of the 95 sites` corrected to `94 of the 95` and `EXPECTED_FAILURES` 44 to 43 in the several other places that already carried the fix. On which PRs get a bullet: there is no clean "user-facing only" rule -- #766 and #791 are pure CI/test/lint-comment entries that are in, while #773 and #763 are the same kind of thing and are out. The real pattern across this file's 46 commits is closer to "each round's author judged it worth a reader's time," which is inconsistent by construction. This round leaves that inconsistency as found rather than trying to retrofit a rule, but did add #779 (via #782) on reconsideration -- its own PR body names it as sharing #766's hazard, and pre-existing precedent already treats that hazard's instances as bullet-worthy. `changelog_md.py` regenerated `CHANGELOG.md`; `--check` exit 0. `test_changelog_md.py` 47 passed.
`main` moved from 73f09ae to 3cbdf89 while this PR sat open. This commit (relative to its parent, 4233555) now names all 20 bullets it carries, not just the 8 this session added on top of the 10 already there -- a first draft of this message named only its own 8 and left the other 10 silent, which a cross-review caught. Six are breaking, matching the crediting PRs' own `breaking` label in each case: - #754 -- AppType split into per-transport registries; the 1,004 portless and 704 transportless rows stop being members. - #764 -- an out-of-range port in `AppType.get` is refused, not minted. - #778 -- `@final` enforced at runtime on `Info`/`Schema`. - #575 -- four `.get()`-backed enum fields fall through to `_unregistered_member` instead of minting; 14 of 23 sample captures change output. - #759 -- `AppType._dispatch` on a multi-transport `proto` now raises `ProtocolError` instead of silently resolving to whichever transport owns the lowest set bit. - #805 -- `FieldBase.length` on a negative resolved length now raises `ProtocolError` instead of letting a bare `struct.error` escape. A second cross-review caught both: their crediting PRs (#783, #811) both carry GitHub's own `breaking` label, and neither bullet said so. - #772 (with #790's docstring reword), #766, #787, #794 (with #791's citation repoint), #792/#798, #802, #779 (via #782, a further #745-hazard instance), #704, #723, #739, #743/#746 (cross-dependent, one bullet each), #796, #800 -- the other 14, non-breaking. Also restores a measurement an earlier round in this same diff dropped while updating an adjacent one: the #692 entry's "mypy is unmoved at 112 errors" silently lost its "and pylint ... 364 messages" half when `EXPECTED_FAILURES` was corrected 44 to 43 elsewhere in the same sentence. Restored to the last value earlier rounds signed off on rather than re-measured, since this branch's own `pcapkit/` tree predates several since-merged PRs and a fresh run would not be measuring the same thing the original round measured. Unmoved, and not silently dropped this time: `93 of the 95 sites` corrected to `94 of the 95` and `EXPECTED_FAILURES` 44 to 43 in the several other places that already carried the fix. On which PRs get a bullet: there is no clean "user-facing only" rule -- #766 and #791 are pure CI/test/lint-comment entries that are in, while #773 and #763 are the same kind of thing and are out. The real pattern across this file's 46 commits is closer to "each round's author judged it worth a reader's time," which is inconsistent by construction. This round leaves that inconsistency as found rather than trying to retrofit a rule, but did add #779 (via #782) on reconsideration -- its own PR body names it as sharing #766's hazard, and pre-existing precedent already treats that hazard's instances as bullet-worthy. `changelog_md.py` regenerated `CHANGELOG.md`; `--check` exit 0. `test_changelog_md.py` 47 passed.
`main` moved from 73f09ae to 3cbdf89 while this PR sat open. This commit (relative to its parent, 4233555) now names all 20 bullets it carries, not just the 8 this session added on top of the 10 already there -- a first draft of this message named only its own 8 and left the other 10 silent, which a cross-review caught. Six are breaking, matching the crediting PRs' own `breaking` label in each case: - #754 -- AppType split into per-transport registries; the 1,004 portless and 704 transportless rows stop being members. - #764 -- an out-of-range port in `AppType.get` is refused, not minted. - #778 -- `@final` enforced at runtime on `Info`/`Schema`. - #575 -- four `.get()`-backed enum fields fall through to `_unregistered_member` instead of minting; 14 of 23 sample captures change output. - #759 -- `AppType._dispatch` on a multi-transport `proto` now raises `ProtocolError` instead of silently resolving to whichever transport owns the lowest set bit. - #805 -- `FieldBase.length` on a negative resolved length now raises `ProtocolError` instead of letting a bare `struct.error` escape. A second cross-review caught both: their crediting PRs (#783, #811) both carry GitHub's own `breaking` label, and neither bullet said so. - #772 (with #790's docstring reword), #766, #787, #794 (with #791's citation repoint), #792/#798, #802, #779 (via #782, a further #745-hazard instance), #704, #723, #739, #743/#746 (cross-dependent, one bullet each), #796, #800 -- the other 14, non-breaking. Also restores a measurement an earlier round in this same diff dropped while updating an adjacent one: the #692 entry's "mypy is unmoved at 112 errors" silently lost its "and pylint ... 364 messages" half when `EXPECTED_FAILURES` was corrected 44 to 43 elsewhere in the same sentence. Restored to the last value earlier rounds signed off on rather than re-measured, since this branch's own `pcapkit/` tree predates several since-merged PRs and a fresh run would not be measuring the same thing the original round measured. Unmoved, and not silently dropped this time: `93 of the 95 sites` corrected to `94 of the 95` and `EXPECTED_FAILURES` 44 to 43 in the several other places that already carried the fix. On which PRs get a bullet: there is no clean "user-facing only" rule -- #766 and #791 are pure CI/test/lint-comment entries that are in, while #773 and #763 are the same kind of thing and are out. The real pattern across this file's 46 commits is closer to "each round's author judged it worth a reader's time," which is inconsistent by construction. This round leaves that inconsistency as found rather than trying to retrofit a rule, but did add #779 (via #782) on reconsideration -- its own PR body names it as sharing #766's hazard, and pre-existing precedent already treats that hazard's instances as bullet-worthy. `changelog_md.py` regenerated `CHANGELOG.md`; `--check` exit 0. `test_changelog_md.py` 47 passed.
make pylint,make mypy,make isort) —isortclean;mypy"no issues found in 12 source files" (7 min over 12,391 declarations);pylintadds noE/Wovermainfor these filesmake testpasses, and a test case covers the changedocs/source/changelog/and regeneratedCHANGELOG.md, if the change is user-visible — N/A — changelog centralised in docs(changelog): shared 1.5.0 changelog — long-lived, merges last (#610, #616, #617, #618, #620) #657What is the purpose of your pull request?
fix— corrects a defectfeat— adds a featureperf— changes performance, not behaviourrefactor— changes neither behaviour nor performancetest— tests onlydocs— documentation onlyci— workflows or build toolingchore— anything elseDescription of your pull request and other information
Part of #732.pcapkit/{const,vendor}/reg/apptype.pybecome packages: a memberlessAppTypebase plusTCP/UDP/SCTP/DCCP.aenumrefuses to subclass an enumeration that has members, so the base holds none — andisinstance(TCP.http, AppType)still holds, so nothing underpcapkit/protocols/changes.list-tabledocstrings (704/366/122/2/1 rows, verified rendering)__members_proto__; last row won;extend_enumdisplaced itMultiDict; canonical member;extend_enumappends_value_with.port/__int__, per the ruling that int-parity is droppable. A plainIntEnumcannot hold two members on port 80:list()is 1 andwww.namereads'http'.get(port)answers with the/etc/servicesname (80→http,1701→l2tp,2049→nfs,3478→stun,113→**auth**, notident);get_all(port)returns it plus its aliases. Row order answers only 28 of the 44 colliding pairs that way, so__canonical__is curated and an uncurated collision warns.__members_proto__[undefined]fallback:get(51, proto=tcp)mints rather than answeringreserved.breaking: yes —APPTYPEis public (pcapkit/__init__.py:107) andAppType.<member>no longer resolves; 35AppType_*members becomeTCP_*/UDP_*/SCTP_*.sphinx-build -b dummyover these autoclasses: 680.3 s → 759.1 s (1.12×). The split worsens theget_type_hintscost by 11.6%; it does not relieve it. Identical 6 warnings before and after..get()sentinel/warning spec; docs: bare 'dict' in :type: fields resolves to AppType.dict, an IANA service enum member, not the builtin #717's:no-typesetting:stub;_make_port's deletion (its four files belong to another PR in flight);pep.rst's "out of 8182"; enum-keying__proto__.get(<str>)is dropped, not fixed — dead today and mintingport=-1junk; AppType.get(name) never matches an existing member and silently mints a port=-1 member instead #734 stays open.TransportProtocolstops being aFlag. 10,625 of the 12,391 declarations carry a multi-transport.proto, but regenerating them is one crawler command, so this PR need not wait.Tests run (not
make test— the full suite is out of budget here):tests/const52,tests/vendor59,tests/protocols/transport+tests/dumpkit164,tests/project+toolkit+corekit+utilities487 (9 skipped),tests/foundation/registry+test_protocol_code_registration_unit29. All passing.tests/const/test_const_apptype_split_unit.pyis new: 12 tests, all 12 shown failing against5c0df9248.