reflex-sdk 0.3.2__tar.gz → 0.3.3__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 (55) hide show
  1. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/PKG-INFO +1 -1
  2. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/pyproject.toml +1 -1
  3. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/_webrtc_client.py +71 -24
  4. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/webrtc.py +6 -1
  5. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex_sdk.egg-info/PKG-INFO +1 -1
  6. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/README.md +0 -0
  7. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/__init__.py +0 -0
  8. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/__main__.py +0 -0
  9. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/_convex.py +0 -0
  10. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/_region_probe.py +0 -0
  11. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/_transport.py +0 -0
  12. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/_version.py +0 -0
  13. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/actions.py +0 -0
  14. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/auth_runner.py +0 -0
  15. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/cameras/__init__.py +0 -0
  16. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/cameras/base.py +0 -0
  17. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/cameras/realsense.py +0 -0
  18. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/cameras/shm.py +0 -0
  19. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/cameras/v4l2.py +0 -0
  20. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/cli.py +0 -0
  21. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/client.py +0 -0
  22. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/connect_runner.py +0 -0
  23. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/connectors/__init__.py +0 -0
  24. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/connectors/base.py +0 -0
  25. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/connectors/shell.py +0 -0
  26. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/connectors/yam_bimanual.py +0 -0
  27. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/datasets.py +0 -0
  28. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/deployments.py +0 -0
  29. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/instances.py +0 -0
  30. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/keys.py +0 -0
  31. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/models.py +0 -0
  32. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/product.py +0 -0
  33. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/receipts.py +0 -0
  34. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/robot_runtime.py +0 -0
  35. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/robots.py +0 -0
  36. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/sessions.py +0 -0
  37. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/so101.py +0 -0
  38. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/training.py +0 -0
  39. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/__init__.py +0 -0
  40. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/_webrtc_streaming_client.py +0 -0
  41. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/base.py +0 -0
  42. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/edge_http.py +0 -0
  43. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex/transports/platform.py +0 -0
  44. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex_sdk.egg-info/SOURCES.txt +0 -0
  45. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex_sdk.egg-info/dependency_links.txt +0 -0
  46. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex_sdk.egg-info/entry_points.txt +0 -0
  47. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex_sdk.egg-info/requires.txt +0 -0
  48. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/reflex_sdk.egg-info/top_level.txt +0 -0
  49. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/setup.cfg +0 -0
  50. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/tests/test_connect_runner.py +0 -0
  51. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/tests/test_prod1_smoke.py +0 -0
  52. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/tests/test_product_cli.py +0 -0
  53. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/tests/test_public_sdk.py +0 -0
  54. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/tests/test_region_probe.py +0 -0
  55. {reflex_sdk-0.3.2 → reflex_sdk-0.3.3}/tests/test_so101_actions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reflex-sdk
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Python SDK for Reflex hosted robot inference and training
5
5
  Author: Reflex
6
6
  Project-URL: Homepage, https://tryreflex.ai
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "reflex-sdk"
3
- version = "0.3.2"
3
+ version = "0.3.3"
4
4
  description = "Python SDK for Reflex hosted robot inference and training"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -415,33 +415,70 @@ class WebRTCClient:
415
415
  method="POST",
416
416
  headers=signaling_headers,
417
417
  )
418
- # Auto-wake cold Modal worker retry up to 60s on 503/connect error.
419
- # Modal scales workers to zero after idle; first request takes 30-90s to
420
- # cold-start. The cli would otherwise crash with HTTPError 503.
418
+ # Auto-wake cold Modal worker. Modal scales workers to zero after idle;
419
+ # first request takes 30-90s to cold-start. Both the signaling POST and
420
+ # the ICE wait below share a single deadline = `timeout` seconds from
421
+ # start, so callers tune one number to govern the whole connect.
422
+ deadline = time.monotonic() + float(timeout)
423
+ loop = asyncio.get_event_loop()
424
+
425
+ def _do_post(per_attempt_timeout: float) -> dict:
426
+ with urllib.request.urlopen(req, timeout=per_attempt_timeout) as r:
427
+ return json.loads(r.read())
428
+
421
429
  t0 = time.perf_counter()
422
- last_err = None
423
- for _attempt in range(7):
430
+ attempt = 0
431
+ last_err: BaseException | None = None
432
+ warned = False
433
+ resp = None
434
+ while True:
435
+ remaining = deadline - time.monotonic()
436
+ if remaining <= 0:
437
+ msg = (
438
+ f"WebRTC signaling timeout after {time.perf_counter()-t0:.1f}s "
439
+ f"(budget {timeout:.0f}s)"
440
+ )
441
+ if last_err is not None:
442
+ raise RuntimeError(f"{msg} — last error: {last_err!r}") from last_err
443
+ raise RuntimeError(f"{msg} — no response from {self.signaling_url}")
444
+ attempt += 1
445
+ # Cap each POST at 30s so a stalled connect can't burn the whole
446
+ # budget on one attempt — we'd rather retry sooner with feedback.
447
+ per_attempt = min(remaining, 30.0)
424
448
  try:
425
- with urllib.request.urlopen(req, timeout=60) as r:
426
- resp = json.loads(r.read())
449
+ resp = await loop.run_in_executor(None, _do_post, per_attempt)
427
450
  last_err = None
428
451
  break
429
452
  except urllib.error.HTTPError as exc:
430
453
  last_err = exc
431
- if exc.code in (502, 503, 504):
432
- if _attempt == 0:
433
- print(f"[webrtc-client] worker cold (HTTP {exc.code}) — waking...", flush=True)
434
- time.sleep(10)
435
- continue
436
- raise
454
+ if exc.code not in (502, 503, 504):
455
+ raise
437
456
  except (urllib.error.URLError, TimeoutError) as exc:
438
457
  last_err = exc
439
- if _attempt == 0:
440
- print(f"[webrtc-client] worker unreachable ({exc!r}) — retrying...", flush=True)
441
- time.sleep(10)
442
- continue
443
- if last_err is not None:
444
- raise last_err
458
+
459
+ if not warned:
460
+ warned = True
461
+ if isinstance(last_err, urllib.error.HTTPError):
462
+ print(
463
+ f"[webrtc-client] worker cold (HTTP {last_err.code}) — "
464
+ f"waking, budget {timeout:.0f}s...",
465
+ flush=True,
466
+ )
467
+ else:
468
+ print(
469
+ f"[webrtc-client] worker unreachable ({last_err!r}) — "
470
+ f"retrying, budget {timeout:.0f}s...",
471
+ flush=True,
472
+ )
473
+ elapsed = time.perf_counter() - t0
474
+ print(
475
+ f"[webrtc-client] cold-wake attempt {attempt} failed at "
476
+ f"{elapsed:.0f}s of {timeout:.0f}s — sleeping before retry",
477
+ flush=True,
478
+ )
479
+ sleep_for = min(10.0, max(0.0, deadline - time.monotonic()))
480
+ if sleep_for > 0:
481
+ await asyncio.sleep(sleep_for)
445
482
  print(f"[webrtc-client] signaling roundtrip: {(time.perf_counter()-t0)*1000:.0f}ms", flush=True)
446
483
 
447
484
  await self.pc.setRemoteDescription(RTCSessionDescription(
@@ -449,13 +486,23 @@ class WebRTCClient:
449
486
  ))
450
487
  self.session_id = resp.get("session_id")
451
488
 
452
- # Wait for ICE + DTLS + DataChannel to come up
453
- t0 = time.perf_counter()
489
+ # Wait for ICE + DTLS + DataChannel using whatever time is left in the
490
+ # shared deadline. Floor at 1s so a near-exhausted budget still gives
491
+ # ICE a meaningful chance instead of insta-timing-out.
492
+ ice_remaining = max(1.0, deadline - time.monotonic())
493
+ ice_t0 = time.perf_counter()
454
494
  try:
455
- await asyncio.wait_for(self._connected.wait(), timeout=timeout)
456
- print(f"[webrtc-client] connected in {(time.perf_counter()-t0)*1000:.0f}ms session={self.session_id}", flush=True)
495
+ await asyncio.wait_for(self._connected.wait(), timeout=ice_remaining)
496
+ print(
497
+ f"[webrtc-client] connected in {(time.perf_counter()-ice_t0)*1000:.0f}ms "
498
+ f"session={self.session_id}",
499
+ flush=True,
500
+ )
457
501
  except asyncio.TimeoutError:
458
- raise RuntimeError("WebRTC connection timeout — likely NAT/firewall blocked direct UDP")
502
+ raise RuntimeError(
503
+ f"WebRTC ICE/DataChannel timeout after {ice_remaining:.1f}s — "
504
+ "likely NAT/firewall blocked direct UDP"
505
+ )
459
506
 
460
507
  # H.264 keyframes can take ~500ms-1s to arrive at the server after
461
508
  # negotiation. Sleep briefly so the first measured infer() finds a
@@ -148,7 +148,12 @@ class WebRTCTransport(Transport):
148
148
  fut = asyncio.run_coroutine_threadsafe(
149
149
  self._client.connect(timeout=self._connect_timeout), self._loop
150
150
  )
151
- fut.result(timeout=self._connect_timeout + 5.0)
151
+ # The client owns the deadline (signaling cold-wake + ICE share one
152
+ # budget = connect_timeout). +30s slack here lets an in-flight urllib
153
+ # POST in an executor thread unwind its TCP timeout naturally after
154
+ # the inner coroutine cancels — avoids a bare TimeoutError leaking up
155
+ # while the inner is in the middle of raising its own diagnostic one.
156
+ fut.result(timeout=self._connect_timeout + 30.0)
152
157
 
153
158
  def stop(self) -> None:
154
159
  if self._client is not None and self._loop is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reflex-sdk
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Python SDK for Reflex hosted robot inference and training
5
5
  Author: Reflex
6
6
  Project-URL: Homepage, https://tryreflex.ai
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes