netprotocols 1.2.0__tar.gz → 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netprotocols-2.0.0/.github/ISSUE_TEMPLATE/bug_report.yml +60 -0
- netprotocols-2.0.0/.github/ISSUE_TEMPLATE/config.yml +10 -0
- netprotocols-2.0.0/.github/ISSUE_TEMPLATE/protocol_request.yml +66 -0
- netprotocols-2.0.0/.github/pull_request_template.md +40 -0
- netprotocols-2.0.0/.github/workflows/ci.yml +91 -0
- netprotocols-2.0.0/.github/workflows/release.yml +61 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/.gitignore +3 -0
- netprotocols-2.0.0/ARCHITECTURE.md +457 -0
- netprotocols-2.0.0/CHANGELOG.md +744 -0
- netprotocols-2.0.0/CONTRIBUTING.md +143 -0
- netprotocols-2.0.0/PKG-INFO +330 -0
- netprotocols-2.0.0/README.md +305 -0
- netprotocols-2.0.0/benchmarks/baseline.json +13 -0
- netprotocols-2.0.0/docs/CLAIMS.md +680 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/pyproject.toml +10 -1
- netprotocols-2.0.0/scripts/benchmark.py +449 -0
- netprotocols-2.0.0/scripts/capture_fixtures_dhcp.sh +94 -0
- netprotocols-2.0.0/scripts/capture_fixtures_dns_tcp.sh +112 -0
- netprotocols-2.0.0/scripts/capture_fixtures_gre.sh +112 -0
- netprotocols-2.0.0/scripts/capture_fixtures_vlan.sh +217 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/scripts/check_fixtures.py +44 -7
- {netprotocols-1.2.0 → netprotocols-2.0.0}/src/netprotocols/__init__.py +48 -6
- {netprotocols-1.2.0 → netprotocols-2.0.0}/src/netprotocols/_base.py +42 -4
- netprotocols-2.0.0/src/netprotocols/_defaults.py +105 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/src/netprotocols/_enums.py +8 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/src/netprotocols/checksum.py +30 -4
- netprotocols-2.0.0/src/netprotocols/flow.py +96 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/src/netprotocols/layer2/__init__.py +2 -1
- {netprotocols-1.2.0 → netprotocols-2.0.0}/src/netprotocols/layer2/arp.py +35 -15
- netprotocols-2.0.0/src/netprotocols/layer2/ethernet.py +97 -0
- netprotocols-2.0.0/src/netprotocols/layer2/vlan.py +103 -0
- netprotocols-2.0.0/src/netprotocols/layer3/__init__.py +28 -0
- netprotocols-2.0.0/src/netprotocols/layer3/gre.py +162 -0
- netprotocols-2.0.0/src/netprotocols/layer3/icmp.py +392 -0
- netprotocols-2.0.0/src/netprotocols/layer3/igmp.py +292 -0
- netprotocols-2.0.0/src/netprotocols/layer3/ip.py +460 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/src/netprotocols/layer3/ipv6_ext.py +136 -17
- netprotocols-2.0.0/src/netprotocols/layer4/__init__.py +4 -0
- netprotocols-2.0.0/src/netprotocols/layer4/_ports.py +66 -0
- netprotocols-2.0.0/src/netprotocols/layer4/tcp.py +326 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/src/netprotocols/layer4/udp.py +6 -4
- netprotocols-2.0.0/src/netprotocols/layer7/__init__.py +4 -0
- netprotocols-2.0.0/src/netprotocols/layer7/dhcp.py +299 -0
- netprotocols-2.0.0/src/netprotocols/layer7/dns.py +554 -0
- netprotocols-2.0.0/src/netprotocols/packet.py +174 -0
- netprotocols-2.0.0/src/netprotocols/registry.py +385 -0
- netprotocols-2.0.0/src/netprotocols/utils/exceptions.py +118 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/src/netprotocols/utils/ipv4.py +19 -3
- {netprotocols-1.2.0 → netprotocols-2.0.0}/src/netprotocols/utils/mac.py +22 -4
- netprotocols-2.0.0/src/netprotocols/walk.py +173 -0
- netprotocols-2.0.0/tests/fixtures/MANIFEST.md +96 -0
- netprotocols-2.0.0/tests/fixtures/dhcp.pcap +0 -0
- netprotocols-2.0.0/tests/fixtures/dns_tcp.pcap +0 -0
- netprotocols-2.0.0/tests/fixtures/gre.pcap +0 -0
- netprotocols-2.0.0/tests/fixtures/staging/dhcp.pcap +0 -0
- netprotocols-2.0.0/tests/fixtures/staging/dns_tcp.pcap +0 -0
- netprotocols-2.0.0/tests/fixtures/staging/gre.pcap +0 -0
- netprotocols-2.0.0/tests/fixtures/staging/vlan_icmp.pcap +0 -0
- netprotocols-2.0.0/tests/fixtures/vlan_icmp.pcap +0 -0
- netprotocols-2.0.0/tests/strategies.py +274 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/test_arp.py +16 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/test_checksum.py +82 -3
- netprotocols-2.0.0/tests/test_contract.py +217 -0
- netprotocols-2.0.0/tests/test_corpus.py +311 -0
- netprotocols-2.0.0/tests/test_dhcp.py +260 -0
- netprotocols-2.0.0/tests/test_diagnostics.py +552 -0
- netprotocols-2.0.0/tests/test_dns.py +547 -0
- netprotocols-2.0.0/tests/test_docs.py +115 -0
- netprotocols-2.0.0/tests/test_exports.py +82 -0
- netprotocols-2.0.0/tests/test_flow.py +174 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/test_fuzz.py +88 -0
- netprotocols-2.0.0/tests/test_gre.py +245 -0
- netprotocols-2.0.0/tests/test_icmp.py +265 -0
- netprotocols-2.0.0/tests/test_igmp.py +329 -0
- netprotocols-2.0.0/tests/test_ip.py +221 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/test_ipv6_ext.py +133 -0
- netprotocols-2.0.0/tests/test_packet.py +103 -0
- netprotocols-2.0.0/tests/test_pattern_matching.py +256 -0
- netprotocols-2.0.0/tests/test_registry.py +296 -0
- netprotocols-2.0.0/tests/test_tcp.py +204 -0
- netprotocols-2.0.0/tests/test_version.py +54 -0
- netprotocols-2.0.0/tests/test_vlan.py +107 -0
- netprotocols-2.0.0/tests/test_walk.py +414 -0
- netprotocols-2.0.0/tests/test_workflows.py +118 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/uv.lock +27 -1
- netprotocols-1.2.0/.github/workflows/ci.yml +0 -47
- netprotocols-1.2.0/.github/workflows/release.yml +0 -18
- netprotocols-1.2.0/ARCHITECTURE.md +0 -280
- netprotocols-1.2.0/CHANGELOG.md +0 -119
- netprotocols-1.2.0/PKG-INFO +0 -179
- netprotocols-1.2.0/README.md +0 -154
- netprotocols-1.2.0/src/netprotocols/layer2/ethernet.py +0 -66
- netprotocols-1.2.0/src/netprotocols/layer3/__init__.py +0 -21
- netprotocols-1.2.0/src/netprotocols/layer3/icmp.py +0 -135
- netprotocols-1.2.0/src/netprotocols/layer3/igmp.py +0 -95
- netprotocols-1.2.0/src/netprotocols/layer3/ip.py +0 -306
- netprotocols-1.2.0/src/netprotocols/layer4/__init__.py +0 -4
- netprotocols-1.2.0/src/netprotocols/layer4/_ports.py +0 -34
- netprotocols-1.2.0/src/netprotocols/layer4/tcp.py +0 -153
- netprotocols-1.2.0/src/netprotocols/layer7/__init__.py +0 -3
- netprotocols-1.2.0/src/netprotocols/layer7/dns.py +0 -234
- netprotocols-1.2.0/src/netprotocols/packet.py +0 -88
- netprotocols-1.2.0/src/netprotocols/utils/exceptions.py +0 -48
- netprotocols-1.2.0/tests/fixtures/MANIFEST.md +0 -51
- netprotocols-1.2.0/tests/test_contract.py +0 -104
- netprotocols-1.2.0/tests/test_corpus.py +0 -167
- netprotocols-1.2.0/tests/test_dns.py +0 -168
- netprotocols-1.2.0/tests/test_icmp.py +0 -55
- netprotocols-1.2.0/tests/test_igmp.py +0 -113
- netprotocols-1.2.0/tests/test_ip.py +0 -92
- netprotocols-1.2.0/tests/test_packet.py +0 -37
- netprotocols-1.2.0/tests/test_tcp.py +0 -80
- {netprotocols-1.2.0 → netprotocols-2.0.0}/LICENSE +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/scripts/capture_fixtures.sh +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/scripts/capture_fixtures_igmp.sh +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/scripts/capture_fixtures_supplement.sh +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/src/netprotocols/py.typed +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/src/netprotocols/utils/__init__.py +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/conftest.py +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/arp_exchange.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/icmpv4_echo_lo.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/icmpv4_external.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/icmpv4_ttl_exceeded.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/icmpv6_echo_lo.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/igmp.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/ipv4_fragments.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/ipv6_fragments.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/ipv6_mld.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/ipv6_ndp_mld.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/staging/arp_exchange.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/staging/icmpv4_echo_lo.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/staging/icmpv4_external.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/staging/icmpv4_ttl_exceeded.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/staging/icmpv6_echo_lo.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/staging/igmp.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/staging/ipv4_fragments.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/staging/ipv6_fragments.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/staging/ipv6_mld.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/staging/ipv6_ndp_mld.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/staging/tcp_http.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/staging/tcp_https.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/staging/udp_dns.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/tcp_http.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/tcp_https.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/fixtures/udp_dns.pcap +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/test_ethernet.py +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/test_udp.py +0 -0
- {netprotocols-1.2.0 → netprotocols-2.0.0}/tests/test_utils.py +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: A protocol decodes, encodes, or chains incorrectly, or raises the wrong error.
|
|
3
|
+
labels: ["bug"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Thanks for the report. netprotocols decodes real traffic, so the most
|
|
9
|
+
useful reports come with the actual bytes that trigger the problem.
|
|
10
|
+
Please search [existing issues](https://github.com/EONRaider/NETProtocols/issues)
|
|
11
|
+
first.
|
|
12
|
+
- type: input
|
|
13
|
+
id: version
|
|
14
|
+
attributes:
|
|
15
|
+
label: Version
|
|
16
|
+
description: >-
|
|
17
|
+
netprotocols version
|
|
18
|
+
(`python -c "import netprotocols; print(netprotocols.__version__)"`)
|
|
19
|
+
and your Python version.
|
|
20
|
+
placeholder: "netprotocols 1.2.0, Python 3.12.3"
|
|
21
|
+
validations:
|
|
22
|
+
required: true
|
|
23
|
+
- type: textarea
|
|
24
|
+
id: what-happened
|
|
25
|
+
attributes:
|
|
26
|
+
label: What happened
|
|
27
|
+
description: What did you decode or build, and what went wrong?
|
|
28
|
+
placeholder: "IPv4.decode(...) returned X, but the header on the wire says Y."
|
|
29
|
+
validations:
|
|
30
|
+
required: true
|
|
31
|
+
- type: textarea
|
|
32
|
+
id: repro
|
|
33
|
+
attributes:
|
|
34
|
+
label: Reproduction
|
|
35
|
+
description: >-
|
|
36
|
+
A minimal snippet. Include the raw bytes as a hex string so it runs
|
|
37
|
+
standalone — that is by far the fastest path to a fix. Rendered as
|
|
38
|
+
Python; no backticks needed.
|
|
39
|
+
render: python
|
|
40
|
+
placeholder: |
|
|
41
|
+
from netprotocols import Ethernet
|
|
42
|
+
|
|
43
|
+
frame = bytes.fromhex("ffffffffffff0007...")
|
|
44
|
+
print(Ethernet.decode(frame))
|
|
45
|
+
validations:
|
|
46
|
+
required: true
|
|
47
|
+
- type: textarea
|
|
48
|
+
id: expected
|
|
49
|
+
attributes:
|
|
50
|
+
label: Expected behavior
|
|
51
|
+
description: What should have happened instead?
|
|
52
|
+
validations:
|
|
53
|
+
required: true
|
|
54
|
+
- type: checkboxes
|
|
55
|
+
id: checks
|
|
56
|
+
attributes:
|
|
57
|
+
label: Checklist
|
|
58
|
+
options:
|
|
59
|
+
- label: I included the raw bytes (or a pcap) needed to reproduce this.
|
|
60
|
+
- label: I searched existing issues for a duplicate.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Freeform issues stay enabled (the roadmap and design discussions don't
|
|
2
|
+
# fit a form); the templates just make the common cases structured.
|
|
3
|
+
blank_issues_enabled: true
|
|
4
|
+
contact_links:
|
|
5
|
+
- name: Contributing guide
|
|
6
|
+
url: https://github.com/EONRaider/NETProtocols/blob/master/CONTRIBUTING.md
|
|
7
|
+
about: Dev setup, the QA ladder, and how to add a protocol.
|
|
8
|
+
- name: Protocol roadmap
|
|
9
|
+
url: https://github.com/EONRaider/NETProtocols/issues/22
|
|
10
|
+
about: Planned protocols and their ordering — check before requesting one.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: Protocol request
|
|
2
|
+
description: Request support for a protocol netprotocols does not decode yet.
|
|
3
|
+
labels: ["enhancement", "roadmap"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
The roadmap ([#22](https://github.com/EONRaider/NETProtocols/issues/22))
|
|
9
|
+
tracks planned protocols and their ordering — check it and existing
|
|
10
|
+
issues first. Adding a protocol follows the cookbook in
|
|
11
|
+
[ARCHITECTURE.md](https://github.com/EONRaider/NETProtocols/blob/master/ARCHITECTURE.md);
|
|
12
|
+
contributions are welcome.
|
|
13
|
+
- type: input
|
|
14
|
+
id: protocol
|
|
15
|
+
attributes:
|
|
16
|
+
label: Protocol
|
|
17
|
+
placeholder: "e.g. DHCP, GRE, 802.1X"
|
|
18
|
+
validations:
|
|
19
|
+
required: true
|
|
20
|
+
- type: input
|
|
21
|
+
id: spec
|
|
22
|
+
attributes:
|
|
23
|
+
label: Specification
|
|
24
|
+
description: The RFC or standard number(s) that define the header.
|
|
25
|
+
placeholder: "RFC 2131"
|
|
26
|
+
validations:
|
|
27
|
+
required: true
|
|
28
|
+
- type: dropdown
|
|
29
|
+
id: layer
|
|
30
|
+
attributes:
|
|
31
|
+
label: Layer
|
|
32
|
+
description: Roughly where it sits in the stack.
|
|
33
|
+
options:
|
|
34
|
+
- Link (2)
|
|
35
|
+
- Network (3)
|
|
36
|
+
- Transport (4)
|
|
37
|
+
- Application (7)
|
|
38
|
+
- Other / not sure
|
|
39
|
+
validations:
|
|
40
|
+
required: true
|
|
41
|
+
- type: textarea
|
|
42
|
+
id: dispatch
|
|
43
|
+
attributes:
|
|
44
|
+
label: How it is reached
|
|
45
|
+
description: >-
|
|
46
|
+
What carries it — which lower layer, and which dispatch value
|
|
47
|
+
(EtherType, IP protocol number, or port)?
|
|
48
|
+
placeholder: "Behind UDP on ports 67/68."
|
|
49
|
+
validations:
|
|
50
|
+
required: false
|
|
51
|
+
- type: textarea
|
|
52
|
+
id: sample
|
|
53
|
+
attributes:
|
|
54
|
+
label: Sample traffic
|
|
55
|
+
description: >-
|
|
56
|
+
A pcap or a hex dump of a real frame makes this far easier to
|
|
57
|
+
implement and test — the fixture corpus is captured from real traffic.
|
|
58
|
+
validations:
|
|
59
|
+
required: false
|
|
60
|
+
- type: checkboxes
|
|
61
|
+
id: checks
|
|
62
|
+
attributes:
|
|
63
|
+
label: Checklist
|
|
64
|
+
options:
|
|
65
|
+
- label: I checked the roadmap (#22) and existing issues.
|
|
66
|
+
- label: I can provide a real capture (pcap or hex) of this protocol.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Thanks for contributing to netprotocols! Fill in the sections below and
|
|
3
|
+
delete this comment. CONTRIBUTING.md has the full workflow; the short
|
|
4
|
+
version is: keep the QA ladder green and update the docs your change
|
|
5
|
+
touches. Relative links below resolve from the repo root once this
|
|
6
|
+
becomes the PR description.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
<!-- What does this change do, and why? Link any related issue, e.g. "Closes #22". -->
|
|
12
|
+
|
|
13
|
+
## What's included
|
|
14
|
+
|
|
15
|
+
<!-- The concrete changes, one bullet each: new/edited modules, tests, docs. -->
|
|
16
|
+
|
|
17
|
+
-
|
|
18
|
+
|
|
19
|
+
## Verification
|
|
20
|
+
|
|
21
|
+
<!-- Paste the QA-ladder output, or tick the boxes. All four gates run in CI. -->
|
|
22
|
+
|
|
23
|
+
- [ ] `uv run ruff check` and `uv run ruff format --check` are clean
|
|
24
|
+
- [ ] `uv run mypy` is clean (strict)
|
|
25
|
+
- [ ] `uv run pytest` passes locally (CI runs it on Python 3.12 / 3.13 / 3.14)
|
|
26
|
+
- [ ] `CHANGELOG.md` has an entry under `## [Unreleased]`
|
|
27
|
+
|
|
28
|
+
### New protocol or dispatch change — also:
|
|
29
|
+
|
|
30
|
+
<!-- Delete this block if it doesn't apply. See ARCHITECTURE.md's cookbook. -->
|
|
31
|
+
|
|
32
|
+
- [ ] Followed the add-a-protocol cookbook in [ARCHITECTURE.md](ARCHITECTURE.md) and the decode contract in `src/netprotocols/_base.py`
|
|
33
|
+
- [ ] New `EtherType` / `IPProtocol` numbers carry display names in lockstep (a completeness test enforces it)
|
|
34
|
+
- [ ] Registered the class in the fuzz suite (`tests/test_fuzz.py::ALL_PROTOCOLS`)
|
|
35
|
+
- [ ] Added a fixture and updated [tests/fixtures/MANIFEST.md](tests/fixtures/MANIFEST.md) — a real capture where possible (see CONTRIBUTING.md for the capture scripts)
|
|
36
|
+
- [ ] Updated the README coverage table and the ARCHITECTURE.md chain diagram
|
|
37
|
+
|
|
38
|
+
## Notes
|
|
39
|
+
|
|
40
|
+
<!-- Anything reviewers should know: trade-offs, follow-ups, companion PRs. Optional. -->
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
# Callable so the release workflow can run this exact ladder against a
|
|
9
|
+
# tagged commit before publishing (see release.yml). Keeping one
|
|
10
|
+
# definition means the release gate cannot drift from the PR gate.
|
|
11
|
+
workflow_call:
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
lint:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v5
|
|
18
|
+
- uses: astral-sh/setup-uv@v6
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
- run: uv run ruff check
|
|
22
|
+
- run: uv run ruff format --check
|
|
23
|
+
|
|
24
|
+
typecheck:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v5
|
|
28
|
+
- uses: astral-sh/setup-uv@v6
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.12"
|
|
31
|
+
- run: uv run mypy
|
|
32
|
+
|
|
33
|
+
test:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
strategy:
|
|
36
|
+
matrix:
|
|
37
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v5
|
|
40
|
+
- uses: astral-sh/setup-uv@v6
|
|
41
|
+
with:
|
|
42
|
+
python-version: ${{ matrix.python-version }}
|
|
43
|
+
- run: uv run --frozen pytest --cov=netprotocols --cov-report=term
|
|
44
|
+
|
|
45
|
+
build:
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v5
|
|
49
|
+
- uses: astral-sh/setup-uv@v6
|
|
50
|
+
- run: uv build
|
|
51
|
+
- run: uv run --isolated --no-project --with dist/*.whl python -c "import netprotocols; print(netprotocols.__version__)"
|
|
52
|
+
|
|
53
|
+
# Blocking, at 15% below the committed baseline. The threshold has
|
|
54
|
+
# runner data behind it: this job's first run measured 3.7% away from
|
|
55
|
+
# a baseline recorded on entirely different hardware, and run-to-run
|
|
56
|
+
# spread on one machine is ~2%. 15% is therefore roughly four times
|
|
57
|
+
# the noise it has to tolerate, which leaves real regressions (Tier 1
|
|
58
|
+
# moved this number by 30-90% a step) nowhere to hide.
|
|
59
|
+
#
|
|
60
|
+
# A failure re-measures once before failing, because a shared runner
|
|
61
|
+
# can lose a slice of CPU to a neighbour: a genuine regression fails
|
|
62
|
+
# both attempts, a noisy one usually does not.
|
|
63
|
+
#
|
|
64
|
+
# Pinned to the baseline's interpreter on purpose: normalization
|
|
65
|
+
# cancels how fast the machine is, not which CPython it runs (see the
|
|
66
|
+
# module docstring in scripts/benchmark.py). If a change is a
|
|
67
|
+
# deliberate trade, re-record with --update-baseline and say why.
|
|
68
|
+
benchmark:
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/checkout@v5
|
|
72
|
+
- uses: astral-sh/setup-uv@v6
|
|
73
|
+
with:
|
|
74
|
+
python-version: "3.12"
|
|
75
|
+
- name: Corpus decode throughput
|
|
76
|
+
shell: bash
|
|
77
|
+
run: |
|
|
78
|
+
# Keep the benchmark's own exit status: piping the whole thing
|
|
79
|
+
# into tee would report tee's, and wrapping it in a { } group
|
|
80
|
+
# would report the trailing echo's -- either way the gate
|
|
81
|
+
# would pass while the check failed.
|
|
82
|
+
status=0
|
|
83
|
+
uv run --frozen --group bench python scripts/benchmark.py \
|
|
84
|
+
--check --threshold 15 --compare >benchmark.out 2>&1 || status=$?
|
|
85
|
+
{
|
|
86
|
+
echo '```'
|
|
87
|
+
cat benchmark.out
|
|
88
|
+
echo '```'
|
|
89
|
+
} >>"$GITHUB_STEP_SUMMARY"
|
|
90
|
+
cat benchmark.out
|
|
91
|
+
exit "$status"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
# Run the full QA ladder against the tagged commit before anything is
|
|
12
|
+
# uploaded. This is not redundant with the CI workflow's own triggers:
|
|
13
|
+
# those fire on pushes and pull requests targeting master, and a tag
|
|
14
|
+
# push matches neither, so without this job a release ran no checks at
|
|
15
|
+
# all. A tag can also point at a commit that never landed on master.
|
|
16
|
+
# A published version can only be yanked, never replaced.
|
|
17
|
+
ci:
|
|
18
|
+
uses: ./.github/workflows/ci.yml
|
|
19
|
+
|
|
20
|
+
publish:
|
|
21
|
+
needs: ci
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
environment: pypi
|
|
24
|
+
permissions:
|
|
25
|
+
id-token: write
|
|
26
|
+
contents: read
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v5
|
|
29
|
+
- uses: astral-sh/setup-uv@v6
|
|
30
|
+
- run: uv build
|
|
31
|
+
# The QA ladder cannot catch a mistagged release: every check
|
|
32
|
+
# passes because the code is fine and only the tag is wrong. Read
|
|
33
|
+
# the version back out of the artifact that is about to be
|
|
34
|
+
# uploaded, rather than re-parsing pyproject.toml, so this
|
|
35
|
+
# compares against what PyPI will actually receive.
|
|
36
|
+
- name: Verify the tag matches the version being published
|
|
37
|
+
run: |
|
|
38
|
+
set -euo pipefail
|
|
39
|
+
# Only the "v*" trigger keeps a bare "1.3.0" tag from reaching
|
|
40
|
+
# this step, where it would compare equal and pass. Check the
|
|
41
|
+
# prefix here too, so the contract survives a wider trigger.
|
|
42
|
+
case "${GITHUB_REF_NAME}" in
|
|
43
|
+
v*) tag="${GITHUB_REF_NAME#v}" ;;
|
|
44
|
+
*)
|
|
45
|
+
echo "::error::Tag ${GITHUB_REF_NAME} does not start with 'v'."
|
|
46
|
+
echo "Release tags must be of the form v<version>." >&2
|
|
47
|
+
exit 1
|
|
48
|
+
;;
|
|
49
|
+
esac
|
|
50
|
+
built=$(uv run --isolated --no-project --with dist/*.whl \
|
|
51
|
+
python -c \
|
|
52
|
+
"from importlib.metadata import version; print(version('netprotocols'))")
|
|
53
|
+
if [ "$tag" != "$built" ]; then
|
|
54
|
+
echo "::error::Tag ${GITHUB_REF_NAME} would publish version ${built}."
|
|
55
|
+
echo "The tag and the packaged version must agree." >&2
|
|
56
|
+
echo "Either retag as v${built}, or set the version in" >&2
|
|
57
|
+
echo "pyproject.toml (and __version__) to ${tag} and retag." >&2
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
echo "Tag ${GITHUB_REF_NAME} matches packaged version ${built}."
|
|
61
|
+
- run: uv publish --trusted-publishing always
|