pyModeS 3.2.0__tar.gz → 3.4.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.
Files changed (62) hide show
  1. {pymodes-3.2.0 → pymodes-3.4.0}/CHANGELOG.md +25 -1
  2. {pymodes-3.2.0 → pymodes-3.4.0}/PKG-INFO +16 -16
  3. {pymodes-3.2.0 → pymodes-3.4.0}/README.md +18 -18
  4. {pymodes-3.2.0 → pymodes-3.4.0}/pyproject.toml +2 -2
  5. pymodes-3.4.0/scripts/README.md +58 -0
  6. pymodes-3.4.0/scripts/data/README.md +37 -0
  7. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/_pipe.py +573 -75
  8. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/data/airports.py +1 -1
  9. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/_infer.py +2 -5
  10. {pymodes-3.2.0 → pymodes-3.4.0}/.gitignore +0 -0
  11. {pymodes-3.2.0 → pymodes-3.4.0}/LICENSE +0 -0
  12. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/__init__.py +0 -0
  13. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/_aero.py +0 -0
  14. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/_altcode.py +0 -0
  15. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/_bits.py +0 -0
  16. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/_callsign.py +0 -0
  17. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/_idcode.py +0 -0
  18. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/_schema.py +0 -0
  19. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/_uncertainty.py +0 -0
  20. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/_v2_removed.py +0 -0
  21. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/cli/__init__.py +0 -0
  22. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/cli/__main__.py +0 -0
  23. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/cli/_args.py +0 -0
  24. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/cli/_sink.py +0 -0
  25. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/cli/_source.py +0 -0
  26. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/cli/_tui.py +0 -0
  27. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/cli/decode.py +0 -0
  28. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/cli/live.py +0 -0
  29. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/core.py +0 -0
  30. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/data/__init__.py +0 -0
  31. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/__init__.py +0 -0
  32. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/_base.py +0 -0
  33. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/acas.py +0 -0
  34. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/adsb.py +0 -0
  35. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/allcall.py +0 -0
  36. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/__init__.py +0 -0
  37. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/_helpers.py +0 -0
  38. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds05.py +0 -0
  39. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds06.py +0 -0
  40. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds08.py +0 -0
  41. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds09.py +0 -0
  42. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds10.py +0 -0
  43. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds17.py +0 -0
  44. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds20.py +0 -0
  45. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds30.py +0 -0
  46. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds40.py +0 -0
  47. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds44.py +0 -0
  48. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds45.py +0 -0
  49. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds50.py +0 -0
  50. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds60.py +0 -0
  51. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds61.py +0 -0
  52. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds62.py +0 -0
  53. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/bds/bds65.py +0 -0
  54. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/commb.py +0 -0
  55. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/decoder/surv.py +0 -0
  56. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/errors.py +0 -0
  57. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/message.py +0 -0
  58. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/position/__init__.py +0 -0
  59. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/position/_airports.py +0 -0
  60. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/position/_cpr.py +0 -0
  61. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/py.typed +0 -0
  62. {pymodes-3.2.0 → pymodes-3.4.0}/src/pyModeS/util.py +0 -0
@@ -5,6 +5,30 @@ All notable changes to pyModeS are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [3.4.0] — 2026-07-13
9
+
10
+ ### Fixed
11
+
12
+ - `PipeDecoder` no longer scans every per-ICAO cache on every timestamped
13
+ message. Full TTL eviction is throttled to a configurable one-second
14
+ interval by default, capped at the TTL itself. A bounded per-ICAO check
15
+ still expires state exactly before it is used, so sweep throttling cannot
16
+ change BDS inference at the TTL boundary. Set `eviction_interval=0` for
17
+ the former sweep-per-message memory-reclamation behavior.
18
+ - Timestamped messages that contain no fields used for stateful
19
+ disambiguation no longer create empty per-ICAO state entries. Existing
20
+ useful state is still refreshed when such a message arrives.
21
+ - State updates skip field extraction for downlink formats that cannot
22
+ contribute to stateful inference.
23
+
24
+ ### Added
25
+
26
+ - `scripts/stream_filtered.py`, a configurable live/replay Beast example
27
+ demonstrating high-volume DF/typecode prefiltering before `PipeDecoder`.
28
+ - Reproducible version-isolated benchmarks for mixed live traffic and
29
+ high-cardinality streaming, including checked-in reports and the public
30
+ Beast capture used by the mixed-traffic benchmark.
31
+
8
32
  ## [3.1.0] — 2026-04-14
9
33
 
10
34
  Streaming-robustness release. Adds defences against phantom positions
@@ -98,7 +122,7 @@ the migration guide.
98
122
  `bin2hex`, `crc`, `df`, `icao`, `typecode`, `altcode`, `idcode`,
99
123
  `cprNL`. Thin wrappers over `_bits.py`, `_altcode.py`, `_idcode.py`
100
124
  and `position/_cpr.py` with no logic duplication.
101
- - `scripts/smoke_test.sh` installs the freshly built wheel
125
+ - `scripts/smoke_test_wheel.sh` installs the freshly built wheel
102
126
  into a clean Python 3.12 venv and verifies the public API end to
103
127
  end (single-message decode, PipeDecoder baseline, batch CPR pair
104
128
  resolution, error-dict on malformed input).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyModeS
3
- Version: 3.2.0
3
+ Version: 3.4.0
4
4
  Summary: Python Mode-S and ADS-B Decoder
5
5
  Project-URL: homepage, https://mode-s.org
6
6
  Project-URL: repository, https://github.com/junzis/pyModeS
@@ -205,24 +205,24 @@ the full command reference.
205
205
 
206
206
  ## Performance
207
207
 
208
- Measured on jet1090's
209
- [`long_flight.csv`](https://github.com/xoolive/jet1090/blob/master/crates/rs1090/data/long_flight.csv)
210
- (172,432 Beast-format messages, 7 runs × 1 loop, mean timings,
211
- single-core only):
208
+ The committed benchmarks run each released/working-tree version in an
209
+ isolated environment and reject output mismatches before reporting timings.
210
+ On the captured 176,612-frame mixed Beast feed, the header-prefiltered v3 path
211
+ processes 85,963 incoming messages/s:
212
212
 
213
- | Decoder | Wall time | Throughput | vs pyModeS v3 |
214
- |---|---|---|---|
215
- | **pyModeS v3 (pure Python)** | **2.06s ± 0.01** | **83,549 msg/s** | **1.00×** |
216
- | pyModeS 2.21.1 (Python with compiled C) | 5.03s ± 0.01 | 34,303 msg/s | 0.41× |
217
- | rs1090 (Rust) | 5.60s ± 0.01 | 30,798 msg/s | 0.37× |
218
- | pyModeS 2.21.1 (Python) | 9.09s ± 0.02 | 18,959 msg/s | 0.23× |
213
+ | Decoder path | Throughput |
214
+ |---|---:|
215
+ | pyModeS 2.21.1 selective helpers | 52,998 msg/s |
216
+ | released v3.3.0 `PipeDecoder` | 3,897 msg/s |
217
+ | v3.4.0 `PipeDecoder` | 49,243 msg/s |
218
+ | v3.4.0 + header prefilter | **85,963 msg/s** |
219
219
 
220
- pyModeS v3 is **2.44× faster** than `pyModeS 2.21.1`'s compiled C
221
- extension, **4.41× faster** than `pyModeS 2.21.1` pure-Python, and
222
- **2.71× faster** than rs1090's single-core Rust — all while remaining
223
- pure Python with no C/Cython build.
220
+ On the 2,000-aircraft synthetic PipeDecoder workload, v3.4.0 processes
221
+ 38,838 msg/s versus v2's 39,604 msg/s, with identical normalized output.
224
222
 
225
- Reproduce with `scripts/benchmark_decode.py`.
223
+ See [`scripts/README.md`](./scripts/README.md) for the reproducible commands
224
+ and [`scripts/benchmark_results/`](./scripts/benchmark_results/) for the full
225
+ methodology, timings, and output digests.
226
226
 
227
227
  ## Supported messages
228
228
 
@@ -178,24 +178,24 @@ the full command reference.
178
178
 
179
179
  ## Performance
180
180
 
181
- Measured on jet1090's
182
- [`long_flight.csv`](https://github.com/xoolive/jet1090/blob/master/crates/rs1090/data/long_flight.csv)
183
- (172,432 Beast-format messages, 7 runs × 1 loop, mean timings,
184
- single-core only):
185
-
186
- | Decoder | Wall time | Throughput | vs pyModeS v3 |
187
- |---|---|---|---|
188
- | **pyModeS v3 (pure Python)** | **2.06s ± 0.01** | **83,549 msg/s** | **1.00×** |
189
- | pyModeS 2.21.1 (Python with compiled C) | 5.03s ± 0.01 | 34,303 msg/s | 0.41× |
190
- | rs1090 (Rust) | 5.60s ± 0.01 | 30,798 msg/s | 0.37× |
191
- | pyModeS 2.21.1 (Python) | 9.09s ± 0.02 | 18,959 msg/s | 0.23× |
192
-
193
- pyModeS v3 is **2.44× faster** than `pyModeS 2.21.1`'s compiled C
194
- extension, **4.41× faster** than `pyModeS 2.21.1` pure-Python, and
195
- **2.71× faster** than rs1090's single-core Rust — all while remaining
196
- pure Python with no C/Cython build.
197
-
198
- Reproduce with `scripts/benchmark_decode.py`.
181
+ The committed benchmarks run each released/working-tree version in an
182
+ isolated environment and reject output mismatches before reporting timings.
183
+ On the captured 176,612-frame mixed Beast feed, the header-prefiltered v3 path
184
+ processes 85,963 incoming messages/s:
185
+
186
+ | Decoder path | Throughput |
187
+ |---|---:|
188
+ | pyModeS 2.21.1 selective helpers | 52,998 msg/s |
189
+ | released v3.3.0 `PipeDecoder` | 3,897 msg/s |
190
+ | v3.4.0 `PipeDecoder` | 49,243 msg/s |
191
+ | v3.4.0 + header prefilter | **85,963 msg/s** |
192
+
193
+ On the 2,000-aircraft synthetic PipeDecoder workload, v3.4.0 processes
194
+ 38,838 msg/s versus v2's 39,604 msg/s, with identical normalized output.
195
+
196
+ See [`scripts/README.md`](./scripts/README.md) for the reproducible commands
197
+ and [`scripts/benchmark_results/`](./scripts/benchmark_results/) for the full
198
+ methodology, timings, and output digests.
199
199
 
200
200
  ## Supported messages
201
201
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyModeS"
3
- version = "3.2.0"
3
+ version = "3.4.0"
4
4
  description = "Python Mode-S and ADS-B Decoder"
5
5
  authors = [{ name = "Junzi Sun", email = "git@junzis.com" }]
6
6
  license = { text = "GNU GPL v3" }
@@ -44,6 +44,7 @@ issues = "https://github.com/junzis/pyModeS/issues"
44
44
  [dependency-groups]
45
45
  dev = [
46
46
  "mypy>=1.13",
47
+ "pre-commit>=4.5.1",
47
48
  "pytest>=7.2.0",
48
49
  "pytest-cov>=4.0.0",
49
50
  "ruff>=0.15.8",
@@ -56,7 +57,6 @@ dev = [
56
57
  [tool.ruff]
57
58
  target-version = "py311"
58
59
  line-length = 88
59
- extend-exclude = ["scripts/bench_pms_v2.py"]
60
60
 
61
61
  [tool.ruff.lint]
62
62
  select = [
@@ -0,0 +1,58 @@
1
+ # Maintainer scripts
2
+
3
+ Run these commands from the repository root. Scripts are grouped by purpose
4
+ through their names; generated benchmark reports live in
5
+ `scripts/benchmark_results/`, and committed input data lives in
6
+ `scripts/data/`.
7
+
8
+ ## Streaming example
9
+
10
+ - `stream_filtered.py` — configurable high-throughput example using a live
11
+ Mode-S Beast feed or the committed replay capture. Defaults to DF17/20/21
12
+ while excluding ADS-B typecodes 28/29/31.
13
+
14
+ ```bash
15
+ uv run python scripts/stream_filtered.py \
16
+ --network airsquitter.lr.tudelft.nl:10006 > decoded.jsonl
17
+ ```
18
+
19
+ ## Benchmarks
20
+
21
+ - `benchmark_mixed_traffic.py` — isolated v2, released-v3, updated-v3, and
22
+ prefiltered-v3 comparison on mixed captured traffic. Writes
23
+ `benchmark_results/mixed_traffic.md` by default.
24
+ - `benchmark_pipe.py` — synthetic high-cardinality PipeDecoder comparison.
25
+ Writes `benchmark_results/pipe.md` by default.
26
+ - `benchmark_cpr.py` — focused CPR primitive comparison against the v2 NumPy
27
+ implementation.
28
+
29
+ ```bash
30
+ uv run python scripts/benchmark_mixed_traffic.py
31
+ uv run python scripts/benchmark_pipe.py
32
+ uv run --with numpy python scripts/benchmark_cpr.py
33
+ ```
34
+
35
+ The version-comparison benchmarks build the working tree and resolve released
36
+ versions from PyPI in isolated temporary environments. They require network
37
+ access unless those packages are already cached.
38
+
39
+ ## Generated data
40
+
41
+ - `generate_airport_db.py` — refreshes `src/pyModeS/data/airports.py` from
42
+ OurAirports.
43
+ - `generate_v2_fixture.py` — regenerates the committed v2.21.1 golden oracle.
44
+
45
+ ```bash
46
+ uv run python scripts/generate_airport_db.py
47
+ uv run --no-project --with pyModeS==2.21.1 \
48
+ python scripts/generate_v2_fixture.py
49
+ ```
50
+
51
+ ## Release smoke tests
52
+
53
+ - `smoke_test_wheel.sh [wheel]` — installs a built wheel in a clean virtual
54
+ environment and checks the public API.
55
+ - `smoke_test_cli.sh` — checks CLI decoding and a short live Beast connection.
56
+
57
+ The CLI smoke test requires network access. The wheel smoke test uses the most
58
+ recent `dist/pymodes-*.whl` when no path is supplied.
@@ -0,0 +1,37 @@
1
+ # Benchmark capture data
2
+
3
+ ## `airsquitter_2026-07-13_120s.csv.gz`
4
+
5
+ Two-minute capture from the public TU Delft AirSquitter Mode-S Beast feed:
6
+
7
+ - Source: `airsquitter.lr.tudelft.nl:10006` (TCP, Beast binary)
8
+ - Capture date: 2026-07-13
9
+ - Frames: 176,612
10
+ - Uncompressed format: `timestamp,hex_message`, one frame per line
11
+ - Compressed size: approximately 2 MiB
12
+ - SHA-256: `fa433eb8dc2ec9cf6ebd40c1bf0a095ae753960d5e8c42ae6da15cc28eb3baea`
13
+
14
+ The timestamps are reconstructed by `pyModeS.cli._source.NetworkSource`
15
+ from the Beast MLAT counter, anchored to wall-clock time when the TCP data
16
+ is received. Mode A/C and Beast status frames are omitted because
17
+ `NetworkSource` yields only Mode-S short and long frames.
18
+
19
+ Capture and compact conversion:
20
+
21
+ ```bash
22
+ timeout --signal=INT 120 \
23
+ modes live \
24
+ --network airsquitter.lr.tudelft.nl:10006 \
25
+ --quiet \
26
+ --dump-to /tmp/pymodes-live-mixed.jsonl
27
+
28
+ jq -r '"\(.timestamp),\(.raw_msg)"' \
29
+ /tmp/pymodes-live-mixed.jsonl \
30
+ | gzip -9 > scripts/data/airsquitter_2026-07-13_120s.csv.gz
31
+ ```
32
+
33
+ Run the associated isolated three-version benchmark with:
34
+
35
+ ```bash
36
+ python scripts/benchmark_mixed_traffic.py
37
+ ```