better-rtplot 0.4.7__tar.gz → 0.4.8__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.
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/PKG-INFO +1 -1
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/pyproject.toml +1 -1
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/client.py +6 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/server_browser.py +92 -42
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/LICENSE +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/README.md +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/interactive_test.py +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/saved_plots/.gitignore +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/server.py +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/server_browser_gui.py +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/index.html +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/auto-render.min.js +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/katex.min.css +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/katex.min.js +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/uPlot.iife.min.js +0 -0
- {better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/uPlot.min.css +0 -0
|
@@ -538,6 +538,12 @@ def poll_controls():
|
|
|
538
538
|
if plot_desc_dict is not None:
|
|
539
539
|
socket.send_string(SENDING_PLOT_UPDATE)
|
|
540
540
|
socket.send_json(plot_desc_dict)
|
|
541
|
+
# Surface a one-line confirmation so the user can see in
|
|
542
|
+
# their script log that the recovery handshake worked.
|
|
543
|
+
# If they never see this after clicking Reconnect, the
|
|
544
|
+
# PUSH/PULL channel back from the server isn't wired —
|
|
545
|
+
# check that poll_controls() is actually being called.
|
|
546
|
+
print("rtplot.client: resent cached initialize_plots() config")
|
|
541
547
|
|
|
542
548
|
buttons = _control_button_events
|
|
543
549
|
_control_button_events = []
|
|
@@ -215,7 +215,13 @@ RECEIVED_DISPLAY = 4
|
|
|
215
215
|
BIND_ME_ID = "bind_me"
|
|
216
216
|
BIND_ME_NAME = "Shared - Bind to me"
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
# Path to the persisted tabs JSON. Honors RTPLOT_TABS_FILE so tests
|
|
219
|
+
# (and operators with multi-user setups) can override without symlinking
|
|
220
|
+
# their HOME directory or losing access to ~/.local site-packages.
|
|
221
|
+
TABS_FILE = os.environ.get(
|
|
222
|
+
"RTPLOT_TABS_FILE",
|
|
223
|
+
os.path.join(os.path.expanduser("~"), ".rtplot", "tabs.json"),
|
|
224
|
+
)
|
|
219
225
|
|
|
220
226
|
zmq_ctx = zmq.asyncio.Context()
|
|
221
227
|
|
|
@@ -602,43 +608,67 @@ def _remove_ws(ws):
|
|
|
602
608
|
def _open_tab_sockets(tab: Tab):
|
|
603
609
|
"""Open ``tab``'s data + control sockets according to its mode.
|
|
604
610
|
|
|
605
|
-
|
|
606
|
-
|
|
611
|
+
For bind tabs, the just-closed sockets may leave the OS-level port
|
|
612
|
+
in a transient state for a few hundred ms (especially on Windows /
|
|
613
|
+
WSL). We retry the bind with a small backoff so the user-visible
|
|
614
|
+
Reconnect button doesn't fail spuriously the very first time. On
|
|
615
|
+
sustained failure we set tab.status="error" + tab.error=<reason>;
|
|
616
|
+
the tab stays in the list but won't stream until re-opened.
|
|
607
617
|
"""
|
|
608
618
|
# Close any prior sockets first (e.g. on retry).
|
|
609
619
|
_close_tab_sockets(tab)
|
|
610
620
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
ctrl.
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
621
|
+
last_exc: Exception | None = None
|
|
622
|
+
attempts = 4 if tab.mode == "bind" else 1
|
|
623
|
+
for attempt in range(attempts):
|
|
624
|
+
try:
|
|
625
|
+
data = zmq_ctx.socket(zmq.SUB)
|
|
626
|
+
data.setsockopt_string(zmq.SUBSCRIBE, "")
|
|
627
|
+
data.setsockopt(zmq.LINGER, 0)
|
|
628
|
+
ctrl = zmq_ctx.socket(zmq.PUSH)
|
|
629
|
+
ctrl.setsockopt(zmq.SNDHWM, 1000)
|
|
630
|
+
ctrl.setsockopt(zmq.LINGER, 0)
|
|
631
|
+
|
|
632
|
+
if tab.mode == "bind":
|
|
633
|
+
data.bind(f"tcp://*:{ZMQ_DEFAULT_PORT}")
|
|
634
|
+
ctrl.bind(f"tcp://*:{ZMQ_CONTROL_PORT}")
|
|
635
|
+
tab.endpoint = f"*:{ZMQ_DEFAULT_PORT}"
|
|
636
|
+
print(f"[{tab.id}] ZMQ: bound on tcp://*:{ZMQ_DEFAULT_PORT}")
|
|
637
|
+
else:
|
|
638
|
+
data_ep, label = _normalize_connect_target(tab.endpoint)
|
|
639
|
+
data.connect(data_ep)
|
|
640
|
+
ctrl_ep, _ = _normalize_connect_target(
|
|
641
|
+
_control_target_from_data(tab.endpoint), port=ZMQ_CONTROL_PORT
|
|
642
|
+
)
|
|
643
|
+
ctrl.connect(ctrl_ep)
|
|
644
|
+
tab.endpoint = label
|
|
645
|
+
print(f"[{tab.id}] ZMQ: connected to {data_ep} (ctrl {ctrl_ep})")
|
|
646
|
+
|
|
647
|
+
tab.data_sock = data
|
|
648
|
+
tab.ctrl_sock = ctrl
|
|
649
|
+
tab.status = "idle"
|
|
650
|
+
tab.error = None
|
|
651
|
+
return
|
|
652
|
+
except Exception as exc: # noqa: BLE001
|
|
653
|
+
last_exc = exc
|
|
654
|
+
# Drop the half-built sockets before retrying so we don't
|
|
655
|
+
# leak descriptors and so the next attempt re-takes the port.
|
|
656
|
+
try: data.close(0)
|
|
657
|
+
except Exception: pass
|
|
658
|
+
try: ctrl.close(0)
|
|
659
|
+
except Exception: pass
|
|
660
|
+
if attempt < attempts - 1:
|
|
661
|
+
wait = 0.15 * (attempt + 1)
|
|
662
|
+
print(
|
|
663
|
+
f"[{tab.id}] ZMQ open attempt {attempt + 1}/{attempts}"
|
|
664
|
+
f" failed ({exc}); retrying in {wait:.2f}s"
|
|
665
|
+
)
|
|
666
|
+
time.sleep(wait)
|
|
632
667
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
except Exception as exc: # noqa: BLE001
|
|
638
|
-
tab.status = "error"
|
|
639
|
-
tab.error = str(exc)
|
|
640
|
-
print(f"[{tab.id}] ZMQ open failed: {exc}")
|
|
641
|
-
_close_tab_sockets(tab)
|
|
668
|
+
tab.status = "error"
|
|
669
|
+
tab.error = str(last_exc) if last_exc is not None else "unknown error"
|
|
670
|
+
print(f"[{tab.id}] ZMQ open failed after {attempts} attempts: {last_exc}")
|
|
671
|
+
_close_tab_sockets(tab)
|
|
642
672
|
|
|
643
673
|
|
|
644
674
|
def _close_tab_sockets(tab: Tab):
|
|
@@ -1001,24 +1031,44 @@ async def _request_config_resend(tab: "Tab"):
|
|
|
1001
1031
|
the cached config so the plot picks back up without the user having
|
|
1002
1032
|
to restart their script.
|
|
1003
1033
|
|
|
1004
|
-
Retries
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1034
|
+
Retries over a ~10 s window because:
|
|
1035
|
+
* the client's PULL socket may need a moment to auto-reconnect to
|
|
1036
|
+
the freshly bound PUSH socket — especially over a WAN/SSH link;
|
|
1037
|
+
* a slow user loop may only call poll_controls() once per second,
|
|
1038
|
+
so the message can sit unread in the PULL queue for a while;
|
|
1039
|
+
* the client may not be running at all (no peer), in which case
|
|
1040
|
+
the queued message is just dropped when the next reconnect happens.
|
|
1041
|
+
Stops early once tab.initialized flips True (the client answered).
|
|
1009
1042
|
"""
|
|
1010
|
-
|
|
1043
|
+
schedule = (0.3, 0.7, 1.5, 2.5, 5.0) # cumulative ~10 s
|
|
1044
|
+
started_initialized = tab.initialized
|
|
1045
|
+
for i, delay in enumerate(schedule):
|
|
1011
1046
|
await asyncio.sleep(delay)
|
|
1012
1047
|
if tab.ctrl_sock is None:
|
|
1013
1048
|
return
|
|
1014
|
-
if i > 0 and tab.initialized:
|
|
1049
|
+
if i > 0 and tab.initialized and not started_initialized:
|
|
1015
1050
|
return
|
|
1016
1051
|
try:
|
|
1017
1052
|
await tab.ctrl_sock.send_json(
|
|
1018
1053
|
{"type": "resend_config"}, flags=zmq.DONTWAIT
|
|
1019
1054
|
)
|
|
1020
|
-
|
|
1021
|
-
|
|
1055
|
+
if DEBUG_TEXT_ENABLED:
|
|
1056
|
+
print(f"[{tab.id}] resend_config sent (attempt {i + 1}/{len(schedule)})")
|
|
1057
|
+
except (zmq.Again, zmq.ZMQError) as exc:
|
|
1058
|
+
if DEBUG_TEXT_ENABLED:
|
|
1059
|
+
print(
|
|
1060
|
+
f"[{tab.id}] resend_config attempt {i + 1}/{len(schedule)}"
|
|
1061
|
+
f" failed: {exc} (likely no peer connected yet)"
|
|
1062
|
+
)
|
|
1063
|
+
# If we asked the client to re-init but nothing came back, surface
|
|
1064
|
+
# a hint so the user knows where to look. Common causes: the client
|
|
1065
|
+
# is on rtplot < 0.4.6 (no resend_config handler), or its loop never
|
|
1066
|
+
# calls poll_controls(), or there's no client running at all.
|
|
1067
|
+
if not started_initialized and not tab.initialized:
|
|
1068
|
+
print(
|
|
1069
|
+
f"[{tab.id}] no config received after resend_config nudges."
|
|
1070
|
+
" Is the client running rtplot >= 0.4.6 and calling poll_controls()?"
|
|
1071
|
+
)
|
|
1022
1072
|
|
|
1023
1073
|
|
|
1024
1074
|
async def reconnect_tab(tab_id: str):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_AMS-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Caligraphic-Bold.woff2
RENAMED
|
File without changes
|
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Fraktur-Bold.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Fraktur-Regular.woff2
RENAMED
|
File without changes
|
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Main-BoldItalic.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Main-Italic.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Main-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Math-BoldItalic.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Math-Italic.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_SansSerif-Bold.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_SansSerif-Italic.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_SansSerif-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Script-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Size1-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Size2-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Size3-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Size4-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.7 → better_rtplot-0.4.8}/rtplot/static/katex/fonts/KaTeX_Typewriter-Regular.woff2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|