plexus-python 0.11.0__tar.gz → 0.11.1__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. {plexus_python-0.11.0 → plexus_python-0.11.1}/CHANGELOG.md +24 -0
  2. {plexus_python-0.11.0 → plexus_python-0.11.1}/PKG-INFO +1 -1
  3. {plexus_python-0.11.0 → plexus_python-0.11.1}/plexus/__init__.py +1 -1
  4. {plexus_python-0.11.0 → plexus_python-0.11.1}/pyproject.toml +1 -1
  5. {plexus_python-0.11.0 → plexus_python-0.11.1}/skills/plexus/SKILL.md +48 -3
  6. {plexus_python-0.11.0 → plexus_python-0.11.1}/skills/plexus-firmware/SKILL.md +39 -2
  7. {plexus_python-0.11.0 → plexus_python-0.11.1}/uv.lock +1 -1
  8. {plexus_python-0.11.0 → plexus_python-0.11.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  9. {plexus_python-0.11.0 → plexus_python-0.11.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  10. {plexus_python-0.11.0 → plexus_python-0.11.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  11. {plexus_python-0.11.0 → plexus_python-0.11.1}/.github/workflows/ci.yml +0 -0
  12. {plexus_python-0.11.0 → plexus_python-0.11.1}/.github/workflows/publish.yml +0 -0
  13. {plexus_python-0.11.0 → plexus_python-0.11.1}/.gitignore +0 -0
  14. {plexus_python-0.11.0 → plexus_python-0.11.1}/AGENTS.md +0 -0
  15. {plexus_python-0.11.0 → plexus_python-0.11.1}/API.md +0 -0
  16. {plexus_python-0.11.0 → plexus_python-0.11.1}/CODE_OF_CONDUCT.md +0 -0
  17. {plexus_python-0.11.0 → plexus_python-0.11.1}/CONTRIBUTING.md +0 -0
  18. {plexus_python-0.11.0 → plexus_python-0.11.1}/LICENSE +0 -0
  19. {plexus_python-0.11.0 → plexus_python-0.11.1}/README.md +0 -0
  20. {plexus_python-0.11.0 → plexus_python-0.11.1}/SECURITY.md +0 -0
  21. {plexus_python-0.11.0 → plexus_python-0.11.1}/TODO.md +0 -0
  22. {plexus_python-0.11.0 → plexus_python-0.11.1}/examples/.python-version +0 -0
  23. {plexus_python-0.11.0 → plexus_python-0.11.1}/examples/README.md +0 -0
  24. {plexus_python-0.11.0 → plexus_python-0.11.1}/examples/basic.py +0 -0
  25. {plexus_python-0.11.0 → plexus_python-0.11.1}/examples/can.py +0 -0
  26. {plexus_python-0.11.0 → plexus_python-0.11.1}/examples/i2c_bme280.py +0 -0
  27. {plexus_python-0.11.0 → plexus_python-0.11.1}/examples/mac_metrics.py +0 -0
  28. {plexus_python-0.11.0 → plexus_python-0.11.1}/examples/mavlink.py +0 -0
  29. {plexus_python-0.11.0 → plexus_python-0.11.1}/examples/mqtt.py +0 -0
  30. {plexus_python-0.11.0 → plexus_python-0.11.1}/examples/pyproject.toml +0 -0
  31. {plexus_python-0.11.0 → plexus_python-0.11.1}/examples/thermal_camera.py +0 -0
  32. {plexus_python-0.11.0 → plexus_python-0.11.1}/examples/uv.lock +0 -0
  33. {plexus_python-0.11.0 → plexus_python-0.11.1}/plexus/_log.py +0 -0
  34. {plexus_python-0.11.0 → plexus_python-0.11.1}/plexus/batching.py +0 -0
  35. {plexus_python-0.11.0 → plexus_python-0.11.1}/plexus/buffer.py +0 -0
  36. {plexus_python-0.11.0 → plexus_python-0.11.1}/plexus/cameras/__init__.py +0 -0
  37. {plexus_python-0.11.0 → plexus_python-0.11.1}/plexus/cameras/thermal.py +0 -0
  38. {plexus_python-0.11.0 → plexus_python-0.11.1}/plexus/cli.py +0 -0
  39. {plexus_python-0.11.0 → plexus_python-0.11.1}/plexus/client.py +0 -0
  40. {plexus_python-0.11.0 → plexus_python-0.11.1}/plexus/config.py +0 -0
  41. {plexus_python-0.11.0 → plexus_python-0.11.1}/plexus/ws.py +0 -0
  42. {plexus_python-0.11.0 → plexus_python-0.11.1}/scripts/plexus.service +0 -0
  43. {plexus_python-0.11.0 → plexus_python-0.11.1}/scripts/release.sh +0 -0
  44. {plexus_python-0.11.0 → plexus_python-0.11.1}/scripts/scan_buses.py +0 -0
  45. {plexus_python-0.11.0 → plexus_python-0.11.1}/scripts/setup.sh +0 -0
  46. {plexus_python-0.11.0 → plexus_python-0.11.1}/scripts/verify_skills.py +0 -0
  47. {plexus_python-0.11.0 → plexus_python-0.11.1}/skills/README.md +0 -0
  48. {plexus_python-0.11.0 → plexus_python-0.11.1}/skills/plexus-dashboard/SKILL.md +0 -0
  49. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/conftest.py +0 -0
  50. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/test_basic.py +0 -0
  51. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/test_batching.py +0 -0
  52. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/test_batching_wire.py +0 -0
  53. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/test_buffer.py +0 -0
  54. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/test_config.py +0 -0
  55. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/test_isolation.py +0 -0
  56. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/test_rate_limit.py +0 -0
  57. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/test_retry.py +0 -0
  58. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/test_runs.py +0 -0
  59. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/test_skills.py +0 -0
  60. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/test_thermal.py +0 -0
  61. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/test_video.py +0 -0
  62. {plexus_python-0.11.0 → plexus_python-0.11.1}/tests/test_ws.py +0 -0
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.11.1] - 2026-09-02 - Correct what the agent skills teach
6
+
7
+ ### Fixed
8
+
9
+ - **`plexus-firmware` claimed the SDK batches. It does not.** Of CPython
10
+ targets — naming Jetson and embedded Linux specifically — the skill said
11
+ "stop and use the SDK instead ... It already does batching". `send()` is one
12
+ message per call, the gateway's ceiling counts messages, and the overflow is
13
+ discarded asynchronously after the send returned True. The skills are how a
14
+ coding agent learns this API instead of inventing it, so that line aimed
15
+ agents at silent data loss on exactly the hardware most likely to hit bench
16
+ rates. Both skills now carry the real ceilings and point at `px.batch()`.
17
+
18
+ - **Runs were documented in no skill**, so an agent had no way to know a test
19
+ bench should name its window. The `plexus` skill now carries the contract:
20
+ the app host rather than the gateway, offset-bearing timestamps, the
21
+ operators, and the two semantics that surprise people — every sample must
22
+ satisfy a criterion, and a metric with no data in the window fails rather
23
+ than passes.
24
+
25
+ - **The documented slug rule was wrong** (`^[a-z0-9][a-z0-9_-]{1,62}$`). The
26
+ gateway accepts dots, single characters and 256 bytes, so an agent applying
27
+ the documented rule would reject slugs the platform takes.
28
+
5
29
  ## [0.11.0] - 2026-09-01 - Batching, runs, and an end to silent drops
6
30
 
7
31
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: plexus-python
3
- Version: 0.11.0
3
+ Version: 0.11.1
4
4
  Summary: Thin Python SDK for Plexus — send telemetry in one line
5
5
  Project-URL: Homepage, https://plexus.company
6
6
  Project-URL: Documentation, https://docs.plexus.company
@@ -23,7 +23,7 @@ from plexus.client import (
23
23
  )
24
24
  from plexus.config import RetryConfig
25
25
 
26
- __version__ = "0.11.0"
26
+ __version__ = "0.11.1"
27
27
  __all__ = [
28
28
  "AuthenticationError",
29
29
  "BatchSender",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "plexus-python"
7
- version = "0.11.0"
7
+ version = "0.11.1"
8
8
  description = "Thin Python SDK for Plexus — send telemetry in one line"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -156,12 +156,54 @@ When asked to "set up Plexus" in a project, do this:
156
156
  5. **Add a single usage example** in the project — one obvious place, not five — so they can verify it works.
157
157
  6. **Update README** with a short section: "Set `PLEXUS_API_KEY`, then run X."
158
158
 
159
+ ## Runs
160
+
161
+ A **run** is a named time window on a source — a hot-fire, a bench sequence, a
162
+ flight. Runs are recalled on `/runs`, compared against each other aligned at
163
+ T+0, and evaluated against declared pass criteria on close. Test-bench software
164
+ should open one when a test starts and close it when the test ends, rather than
165
+ expecting somebody to drag a time picker afterwards.
166
+
167
+ These live on the **app** host (`https://app.plexus.company`), not the gateway,
168
+ and take the same `x-api-key`.
169
+
170
+ ```
171
+ POST /api/runs {name, source_id, started_at, pass_criteria?, tags?}
172
+ PATCH /api/runs/{id} {status: "completed"|"aborted", ended_at}
173
+ GET /api/runs ?source_id=&status=
174
+ ```
175
+
176
+ `started_at` / `ended_at` are ISO-8601 **with an offset** (`...Z` is fine); a
177
+ naive local timestamp is a 400.
178
+
179
+ In plexus-python (>= 0.11.0) this is `px.start_run()` / `px.end_run()`, or:
180
+
181
+ ```python
182
+ with px.run("hotfire-03", pass_criteria=[
183
+ {"metric": "motor.temp_c", "operator": "<", "value": 85},
184
+ ]):
185
+ bench.execute()
186
+ ```
187
+
188
+ An exception leaving the block closes the run `aborted`; a clean exit closes it
189
+ `completed`.
190
+
191
+ **Pass criteria** are `{metric, operator, value, label?}` with operator one of
192
+ `> >= < <= = !=`. On close, every sample of that metric inside the window is
193
+ checked. A criterion passes only if *all* of them satisfy it — one excursion
194
+ fails it — and a criterion whose metric has no data in the window **fails**
195
+ rather than passing. The verdict lands on the run's `test_result`.
196
+
197
+ Criteria are evaluated against stored telemetry, which trails ingest by a few
198
+ seconds. A run closed the instant its last point is sent may read "no data";
199
+ let the pipeline settle first.
200
+
159
201
  ## Idioms
160
202
 
161
203
  - **Polling cadences for dashboards**: latest values 5s, charts 10s, fleet health 10s, source list 30s. Use SWR or TanStack Query with `refreshInterval`.
162
204
  - **Time ranges**: prefer `last=1h` (relative) over `start`/`end` (absolute) — easier to reason about and less timezone footgun. `start`/`end` are ISO date-times, not epoch ms.
163
- - **Batching ingest**: buffer up to 64 points or 5 seconds, whichever first. On 429 / 5xx, exponential backoff with max 3 attempts.
164
- - **Source IDs are slugs**: `drone-001`, `sat-alpha-3`. Must match `^[a-z0-9][a-z0-9_-]{1,62}$`. Stable, lowercase, hyphenated. Don't use UUIDs in user-facing surfaces.
205
+ - **Batching ingest**: the gateway meters **messages, not points** — 500/s per WebSocket connection, 2000/s per source, up to 10,000 points in one message. Buffer up to 64 points or 5 seconds, whichever first. One send per reading at bench rates exceeds the limit and the overflow is *discarded*, reported asynchronously as `RATE_LIMITED` after the send returned. In plexus-python use `px.batch(interval_ms=50)`; `px.send()` alone is one message per call. On 429 / 5xx, exponential backoff with max 3 attempts.
206
+ - **Source IDs are slugs**: `drone-001`, `sat-alpha-3`, `bench.rig-2`. Must match `^[a-z0-9][a-z0-9._-]*$`, up to 256 characters — dots are legal and a single character is legal. A uuid-shaped slug is rejected: every resolver reads uuid-shaped refs as internal ids, so such a source would be unreachable. Stable, lowercase.
165
207
  - **Source IDs are not deduplicated.** The gateway writes whatever `source_id` you declare. Two devices declaring the same name merge into one source.
166
208
  - **Metric names are opaque and may be long.** Anything that round-trips a name must use the identical string on both sides or the series and its metadata will not join.
167
209
 
@@ -180,4 +222,7 @@ When asked to "set up Plexus" in a project, do this:
180
222
 
181
223
  Fetch `https://plexus-data-api.fly.dev/openapi.json` for the authoritative HTTP schema (it will not show WebSocket routes), and check a real response before writing parsing code. `scripts/verify_skills.py` in this repo checks these docs against the live spec — run it if something here looks stale.
182
224
 
183
- This cheat sheet has drifted before. Corrected 2026-08-27 against Data API 0.1.0 (ingest array name, `sources`/`devices` paths, removed per-source health, columnar query) and again 2026-08-28 against gateway + API source (the live-stream host/path/auth/frame shape, the required `class` field, numeric-only timestamps, the redirect chain, and the now-fixed 1970 timestamp behavior).
225
+ This cheat sheet has drifted before. Corrected 2026-09-01 against the shipped
226
+ gateway and app (runs API — which an earlier revision of API.md wrongly said did
227
+ not exist; message-not-point metering and `px.batch()`; the real slug rule,
228
+ which permits dots, single characters and 256 bytes). Corrected 2026-08-27 against Data API 0.1.0 (ingest array name, `sources`/`devices` paths, removed per-source health, columnar query) and again 2026-08-28 against gateway + API source (the live-stream host/path/auth/frame shape, the required `class` field, numeric-only timestamps, the redirect chain, and the now-fixed 1970 timestamp behavior).
@@ -14,11 +14,29 @@ Adds a small, well-behaved Plexus ingest client to device-side code. Optimized f
14
14
  - Targets include: ESP32 / Arduino / RP2040, Raspberry Pi, NVIDIA Jetson, embedded Linux gateways, drone autopilot companion computers, satellite OBC software
15
15
  - Languages: C / C++, MicroPython, CPython, Rust, Go (for edge gateways)
16
16
 
17
- **If the target runs CPython (Pi, Jetson, any embedded Linux with room), stop and use the SDK instead** — `pip install plexus-python`, then `Plexus(api_key=..., source_id=...).send("temp", 72.5)`. It already does batching, backoff, and buffering. Hand-rolling is for targets the SDK can't reach.
17
+ **If the target runs CPython (Pi, Jetson, any embedded Linux with room), stop and use the SDK instead** — `pip install plexus-python`, then `Plexus(api_key=..., source_id=...)`. It handles backoff, store-and-forward buffering and the WebSocket transport. Hand-rolling is for targets the SDK can't reach.
18
+
19
+ **`send()` does NOT batch.** Every call is its own message on the wire, and the ceiling below counts messages. Above a few readings a second, use `px.batch()`:
20
+
21
+ ```python
22
+ with px.batch(interval_ms=50) as b: # one message per interval
23
+ while running:
24
+ b.send("imu.accel_x", imu.x) # same signature as px.send()
25
+ b.send("imu.accel_y", imu.y)
26
+ ```
27
+
28
+ Leaving the block flushes what is queued, and readings keep the timestamp they were taken at. Requires plexus-python >= 0.11.0.
18
29
 
19
30
  For **server-side ingestion** (a backend collecting metrics and forwarding), the generic `plexus` skill is fine.
20
31
  For **dashboards / UI**, use `plexus-dashboard`.
21
32
 
33
+ **On a test bench, open a run.** Software driving a test should name its window
34
+ rather than leave someone to find it on a chart later: `POST /api/runs` when the
35
+ test starts, `PATCH /api/runs/{id}` with `status` and `ended_at` when it ends
36
+ (app host, same `x-api-key`), or `with px.run(...)` from plexus-python. Declared
37
+ `pass_criteria` are evaluated against every sample in the window on close. See
38
+ the `plexus` skill for the contract.
39
+
22
40
  ## The ingest contract
23
41
 
24
42
  ```
@@ -53,7 +71,26 @@ Every firmware integration must do these. Skip any of them and you'll lose data
53
71
 
54
72
  ### 1. Batch
55
73
 
56
- Never POST one point at a time. Buffer until **64 points OR 5 seconds**, whichever first, then flush. Constants worth exposing:
74
+ **The gateway meters messages, not points.** One request carrying 200 points costs
75
+ exactly what one carrying 1 costs, so the shape of your sends — not their volume —
76
+ decides whether you hit a limit:
77
+
78
+ | Limit | Value |
79
+ | --- | --- |
80
+ | Telemetry messages per WebSocket connection | 500/s sustained, 2000 burst |
81
+ | Hard ceiling per source (WS and HTTP) | 2000 messages/s |
82
+ | Points per message | 10,000 |
83
+ | Body size | 1 MB (WS) / 5 MB (HTTP) |
84
+
85
+ Eight channels at 100 Hz sent one at a time is 800 messages/s — over the limit,
86
+ and **the overflow is discarded**. Batched every 50 ms it is 20 messages/s.
87
+
88
+ Over the limit the gateway drops the whole message and answers `RATE_LIMITED`,
89
+ which arrives *after* the send returned. Those points cannot be recovered, so
90
+ this is a batching problem, not a retry one.
91
+
92
+ Never POST one point at a time. Buffer until **64 points OR 5 seconds**, whichever
93
+ first, then flush. Constants worth exposing:
57
94
 
58
95
  ```c
59
96
  #define PLEXUS_BATCH_SIZE 64
@@ -425,7 +425,7 @@ wheels = [
425
425
 
426
426
  [[package]]
427
427
  name = "plexus-python"
428
- version = "0.11.0"
428
+ version = "0.11.1"
429
429
  source = { editable = "." }
430
430
  dependencies = [
431
431
  { name = "websocket-client" },
File without changes
File without changes
File without changes
File without changes
File without changes