plexus-python 0.7.0__tar.gz → 0.7.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 (52) hide show
  1. {plexus_python-0.7.0 → plexus_python-0.7.1}/CHANGELOG.md +8 -0
  2. {plexus_python-0.7.0 → plexus_python-0.7.1}/PKG-INFO +1 -1
  3. {plexus_python-0.7.0 → plexus_python-0.7.1}/plexus/__init__.py +1 -1
  4. {plexus_python-0.7.0 → plexus_python-0.7.1}/plexus/client.py +0 -14
  5. {plexus_python-0.7.0 → plexus_python-0.7.1}/plexus/config.py +0 -48
  6. {plexus_python-0.7.0 → plexus_python-0.7.1}/plexus/ws.py +1 -24
  7. {plexus_python-0.7.0 → plexus_python-0.7.1}/pyproject.toml +1 -1
  8. {plexus_python-0.7.0 → plexus_python-0.7.1}/tests/test_ws.py +1 -74
  9. {plexus_python-0.7.0 → plexus_python-0.7.1}/uv.lock +1 -1
  10. {plexus_python-0.7.0 → plexus_python-0.7.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  11. {plexus_python-0.7.0 → plexus_python-0.7.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  12. {plexus_python-0.7.0 → plexus_python-0.7.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  13. {plexus_python-0.7.0 → plexus_python-0.7.1}/.github/workflows/ci.yml +0 -0
  14. {plexus_python-0.7.0 → plexus_python-0.7.1}/.github/workflows/publish.yml +0 -0
  15. {plexus_python-0.7.0 → plexus_python-0.7.1}/.gitignore +0 -0
  16. {plexus_python-0.7.0 → plexus_python-0.7.1}/AGENTS.md +0 -0
  17. {plexus_python-0.7.0 → plexus_python-0.7.1}/API.md +0 -0
  18. {plexus_python-0.7.0 → plexus_python-0.7.1}/CODE_OF_CONDUCT.md +0 -0
  19. {plexus_python-0.7.0 → plexus_python-0.7.1}/CONTRIBUTING.md +0 -0
  20. {plexus_python-0.7.0 → plexus_python-0.7.1}/LICENSE +0 -0
  21. {plexus_python-0.7.0 → plexus_python-0.7.1}/README.md +0 -0
  22. {plexus_python-0.7.0 → plexus_python-0.7.1}/SECURITY.md +0 -0
  23. {plexus_python-0.7.0 → plexus_python-0.7.1}/TODO.md +0 -0
  24. {plexus_python-0.7.0 → plexus_python-0.7.1}/examples/.python-version +0 -0
  25. {plexus_python-0.7.0 → plexus_python-0.7.1}/examples/README.md +0 -0
  26. {plexus_python-0.7.0 → plexus_python-0.7.1}/examples/basic.py +0 -0
  27. {plexus_python-0.7.0 → plexus_python-0.7.1}/examples/can.py +0 -0
  28. {plexus_python-0.7.0 → plexus_python-0.7.1}/examples/i2c_bme280.py +0 -0
  29. {plexus_python-0.7.0 → plexus_python-0.7.1}/examples/mac_metrics.py +0 -0
  30. {plexus_python-0.7.0 → plexus_python-0.7.1}/examples/mavlink.py +0 -0
  31. {plexus_python-0.7.0 → plexus_python-0.7.1}/examples/mqtt.py +0 -0
  32. {plexus_python-0.7.0 → plexus_python-0.7.1}/examples/pyproject.toml +0 -0
  33. {plexus_python-0.7.0 → plexus_python-0.7.1}/examples/thermal_camera.py +0 -0
  34. {plexus_python-0.7.0 → plexus_python-0.7.1}/examples/uv.lock +0 -0
  35. {plexus_python-0.7.0 → plexus_python-0.7.1}/plexus/_log.py +0 -0
  36. {plexus_python-0.7.0 → plexus_python-0.7.1}/plexus/buffer.py +0 -0
  37. {plexus_python-0.7.0 → plexus_python-0.7.1}/plexus/cameras/__init__.py +0 -0
  38. {plexus_python-0.7.0 → plexus_python-0.7.1}/plexus/cameras/thermal.py +0 -0
  39. {plexus_python-0.7.0 → plexus_python-0.7.1}/plexus/cli.py +0 -0
  40. {plexus_python-0.7.0 → plexus_python-0.7.1}/scripts/plexus.service +0 -0
  41. {plexus_python-0.7.0 → plexus_python-0.7.1}/scripts/release.sh +0 -0
  42. {plexus_python-0.7.0 → plexus_python-0.7.1}/scripts/scan_buses.py +0 -0
  43. {plexus_python-0.7.0 → plexus_python-0.7.1}/scripts/setup.sh +0 -0
  44. {plexus_python-0.7.0 → plexus_python-0.7.1}/skills/plexus/SKILL.md +0 -0
  45. {plexus_python-0.7.0 → plexus_python-0.7.1}/skills/plexus/references/api.md +0 -0
  46. {plexus_python-0.7.0 → plexus_python-0.7.1}/skills/plexus/references/sdk.md +0 -0
  47. {plexus_python-0.7.0 → plexus_python-0.7.1}/tests/test_basic.py +0 -0
  48. {plexus_python-0.7.0 → plexus_python-0.7.1}/tests/test_buffer.py +0 -0
  49. {plexus_python-0.7.0 → plexus_python-0.7.1}/tests/test_config.py +0 -0
  50. {plexus_python-0.7.0 → plexus_python-0.7.1}/tests/test_retry.py +0 -0
  51. {plexus_python-0.7.0 → plexus_python-0.7.1}/tests/test_thermal.py +0 -0
  52. {plexus_python-0.7.0 → plexus_python-0.7.1}/tests/test_video.py +0 -0
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.1] - 2026-06-02 - Remove install_id / source_id auto-suffix
4
+
5
+ ### Changed
6
+
7
+ - Removed `install_id` from the device auth frame and `WebSocketTransport`.
8
+ - Removed server-side source_id auto-suffix handling (`on_source_id_assigned` callback, `set_source_id` persistence).
9
+ - Removed `get_install_id` and `set_source_id` from `plexus.config`.
10
+
3
11
  ## [0.7.0] - 2026-05-29 - SDK hardening
4
12
 
5
13
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: Thin Python SDK for Plexus — send telemetry in one line
5
5
  Project-URL: Homepage, https://plexus.dev
6
6
  Project-URL: Documentation, https://docs.plexus.dev
@@ -10,5 +10,5 @@ Plexus — thin Python SDK for sending telemetry to the Plexus gateway.
10
10
  from plexus.client import Plexus, PlexusError, AuthenticationError, read_mjpeg_frames
11
11
  from plexus.config import RetryConfig
12
12
 
13
- __version__ = "0.7.0"
13
+ __version__ = "0.7.1"
14
14
  __all__ = ["Plexus", "PlexusError", "AuthenticationError", "RetryConfig", "read_mjpeg_frames"]
@@ -55,9 +55,7 @@ from plexus.config import (
55
55
  get_endpoint,
56
56
  get_gateway_url,
57
57
  get_gateway_ws_url,
58
- get_install_id,
59
58
  get_source_id,
60
- set_source_id,
61
59
  )
62
60
 
63
61
  logger = logging.getLogger(__name__)
@@ -414,9 +412,7 @@ class Plexus:
414
412
  api_key=self.api_key,
415
413
  source_id=self.source_id,
416
414
  ws_url=self._ws_url,
417
- install_id=get_install_id(),
418
415
  agent_version=__version__,
419
- on_source_id_assigned=self._on_source_id_assigned,
420
416
  on_clock_synced=self._on_clock_synced,
421
417
  )
422
418
  self._ws.start()
@@ -428,16 +424,6 @@ class Plexus:
428
424
  def _on_clock_synced(self, offset_ms: int) -> None:
429
425
  self._clock_offset_ms = offset_ms
430
426
 
431
- def _on_source_id_assigned(self, assigned: str) -> None:
432
- """Callback from WebSocketTransport when the gateway returns an
433
- auto-suffixed source_id. Persists it so subsequent runs (and the HTTP
434
- fallback path in this process) use the assigned name directly."""
435
- self.source_id = assigned
436
- try:
437
- set_source_id(assigned)
438
- except Exception as e: # pragma: no cover - persistence failure is non-fatal
439
- logger.debug("failed to persist assigned source_id: %s", e)
440
-
441
427
  def _encode_frame(self, frame, quality: int) -> Tuple[bytes, int, int]:
442
428
  """Normalize any supported frame type to (jpeg_bytes, width, height).
443
429
 
@@ -145,54 +145,6 @@ def get_source_id() -> Optional[str]:
145
145
  return source_id
146
146
 
147
147
 
148
- def get_install_id() -> str:
149
- """Get the device install ID, generating one if not set.
150
-
151
- The install_id is a stable per-installation UUID. It is generated lazily
152
- on first run (NOT at image-build time) so that cloned SD-card images
153
- naturally get distinct install_ids on their first boot. The gateway uses
154
- it to tell "same device reconnecting" from "different device claiming the
155
- same name" when resolving source_id collisions.
156
-
157
- Resolution order:
158
- 1. ``PLEXUS_INSTALL_ID`` env var — lets ephemeral containers (Fly
159
- machines, CI runners, Kubernetes pods) pin a stable identity
160
- across restarts when the config filesystem is ephemeral. Without
161
- this, every redeploy generates a new install_id and the gateway
162
- auto-suffixes the source_id to avoid a collision with the prior
163
- install ("gw-001" → "gw-001_2" → "gw-001_3"…).
164
- 2. ``install_id`` in the on-disk config.
165
- 3. Newly-generated UUID, persisted to config.
166
- """
167
- env_id = os.environ.get("PLEXUS_INSTALL_ID", "").strip()
168
- if env_id:
169
- return env_id
170
-
171
- config = load_config()
172
- install_id = config.get("install_id")
173
-
174
- if not install_id:
175
- import uuid
176
- install_id = uuid.uuid4().hex
177
- config["install_id"] = install_id
178
- save_config(config)
179
-
180
- return install_id
181
-
182
-
183
- def set_source_id(source_id: str) -> None:
184
- """Persist an updated source_id to the config file.
185
-
186
- Called by the SDK when the gateway returns an auto-suffixed name so the
187
- assigned name is stable across reconnects.
188
- """
189
- config = load_config()
190
- if config.get("source_id") == source_id:
191
- return
192
- config["source_id"] = source_id
193
- save_config(config)
194
-
195
-
196
148
  def get_persistent_buffer() -> bool:
197
149
  """Get persistent buffer setting. Default True (store-and-forward enabled)."""
198
150
  config = load_config()
@@ -89,11 +89,9 @@ class WebSocketTransport:
89
89
  source_id: str,
90
90
  ws_url: str,
91
91
  *,
92
- install_id: str = "",
93
92
  agent_version: str = "0.0.0",
94
93
  platform: str = "python-sdk",
95
94
  auto_reconnect: bool = True,
96
- on_source_id_assigned: Optional[Callable[[str], None]] = None,
97
95
  on_clock_synced: Optional[Callable[[int], None]] = None,
98
96
  ):
99
97
  if not api_key:
@@ -103,12 +101,10 @@ class WebSocketTransport:
103
101
 
104
102
  self.api_key = api_key
105
103
  self.source_id = source_id
106
- self.install_id = install_id
107
104
  self.ws_url = _ensure_device_path(ws_url)
108
105
  self.agent_version = agent_version
109
106
  self.platform = platform
110
107
  self.auto_reconnect = auto_reconnect
111
- self._on_source_id_assigned = on_source_id_assigned
112
108
  self._on_clock_synced = on_clock_synced
113
109
 
114
110
  self._commands: Dict[str, _RegisteredCommand] = {}
@@ -278,16 +274,13 @@ class WebSocketTransport:
278
274
  self._ws = ws
279
275
 
280
276
  # 1. Send device_auth
281
- desired_source_id = self.source_id
282
277
  auth = {
283
278
  "type": "device_auth",
284
279
  "api_key": self.api_key,
285
- "source_id": desired_source_id,
280
+ "source_id": self.source_id,
286
281
  "platform": self.platform,
287
282
  "agent_version": self.agent_version,
288
283
  }
289
- if self.install_id:
290
- auth["install_id"] = self.install_id
291
284
  if self._commands:
292
285
  auth["commands"] = [c.to_manifest() for c in self._commands.values()]
293
286
  ws.send(json.dumps(auth))
@@ -312,22 +305,6 @@ class WebSocketTransport:
312
305
  except Exception as e:
313
306
  logger.debug("on_clock_synced callback raised: %s", e)
314
307
 
315
- # The gateway may return a different source_id if the desired name
316
- # was already claimed by another install — adopt the assigned value
317
- # so all subsequent frames (heartbeats, future reconnects) use it.
318
- assigned = msg.get("source_id")
319
- if isinstance(assigned, str) and assigned and assigned != self.source_id:
320
- logger.info(
321
- "plexus ws source_id auto-suffixed: requested=%s assigned=%s",
322
- desired_source_id, assigned,
323
- )
324
- self.source_id = assigned
325
- if self._on_source_id_assigned is not None:
326
- try:
327
- self._on_source_id_assigned(assigned)
328
- except Exception as e: # pragma: no cover - callback errors must not break auth
329
- logger.debug("on_source_id_assigned callback raised: %s", e)
330
-
331
308
  was_reconnect = self._backoff_attempt > 0
332
309
  self._authenticated.set()
333
310
  self._backoff_attempt = 0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "plexus-python"
7
- version = "0.7.0"
7
+ version = "0.7.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"
@@ -126,7 +126,6 @@ def test_auth_handshake_and_telemetry(gateway):
126
126
  api_key="plx_test_abc",
127
127
  source_id="drone-001",
128
128
  ws_url=_url(gateway.port),
129
- install_id="install-A",
130
129
  agent_version="9.9.9",
131
130
  )
132
131
  t.start()
@@ -137,7 +136,7 @@ def test_auth_handshake_and_telemetry(gateway):
137
136
  assert gateway.auth_frame["type"] == "device_auth"
138
137
  assert gateway.auth_frame["api_key"] == "plx_test_abc"
139
138
  assert gateway.auth_frame["source_id"] == "drone-001"
140
- assert gateway.auth_frame["install_id"] == "install-A"
139
+ assert "install_id" not in gateway.auth_frame
141
140
  assert gateway.auth_frame["platform"] == "python-sdk"
142
141
  assert gateway.auth_frame["agent_version"] == "9.9.9"
143
142
  # commands is omitted when none registered
@@ -256,78 +255,6 @@ def test_handler_exception_returns_error(gateway):
256
255
  t.stop()
257
256
 
258
257
 
259
- def test_install_id_omitted_when_empty():
260
- # Default install_id="" should not leak an empty install_id field into
261
- # the auth frame — that keeps the wire shape identical for legacy SDK
262
- # builds that don't set one.
263
- g = _StubGateway()
264
- g.start()
265
- try:
266
- t = WebSocketTransport(
267
- api_key="plx_test_abc",
268
- source_id="drone-001",
269
- ws_url=_url(g.port),
270
- )
271
- t.start()
272
- try:
273
- assert t.wait_authenticated(timeout=3)
274
- assert "install_id" not in g.auth_frame
275
- finally:
276
- t.stop()
277
- finally:
278
- g.stop()
279
-
280
-
281
- def test_server_assigned_source_id_is_adopted():
282
- # Simulate the auto-suffix path: SDK asks for "drone-01", the gateway
283
- # returns "drone-01_2" in the authenticated frame. The transport must
284
- # adopt the assigned name and fire the on_source_id_assigned callback.
285
- g = _StubGateway(assigned_source_id="drone-01_2")
286
- g.start()
287
- try:
288
- seen: List[str] = []
289
- t = WebSocketTransport(
290
- api_key="plx_test_abc",
291
- source_id="drone-01",
292
- ws_url=_url(g.port),
293
- install_id="install-B",
294
- on_source_id_assigned=lambda s: seen.append(s),
295
- )
296
- t.start()
297
- try:
298
- assert t.wait_authenticated(timeout=3)
299
- assert t.source_id == "drone-01_2"
300
- assert seen == ["drone-01_2"]
301
- finally:
302
- t.stop()
303
- finally:
304
- g.stop()
305
-
306
-
307
- def test_same_assigned_source_id_does_not_fire_callback():
308
- # Happy path — gateway returns the same name. No callback, source_id unchanged.
309
- g = _StubGateway() # echoes whatever was sent
310
- g.start()
311
- try:
312
- seen: List[str] = []
313
- t = WebSocketTransport(
314
- api_key="plx_test_abc",
315
- source_id="drone-01",
316
- ws_url=_url(g.port),
317
- install_id="install-A",
318
- on_source_id_assigned=lambda s: seen.append(s),
319
- )
320
- t.start()
321
- try:
322
- assert t.wait_authenticated(timeout=3)
323
- assert t.source_id == "drone-01"
324
- assert seen == []
325
- finally:
326
- t.stop()
327
- finally:
328
- g.stop()
329
-
330
-
331
258
  def test_ensure_device_path():
332
259
  from plexus.ws import _ensure_device_path
333
260
  assert _ensure_device_path("wss://foo") == "wss://foo/ws/device"
@@ -429,7 +429,7 @@ wheels = [
429
429
 
430
430
  [[package]]
431
431
  name = "plexus-python"
432
- version = "0.6.2"
432
+ version = "0.7.1"
433
433
  source = { editable = "." }
434
434
  dependencies = [
435
435
  { name = "websocket-client" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes