chartremotely 0.3.9__tar.gz → 0.3.10__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 (68) hide show
  1. {chartremotely-0.3.9 → chartremotely-0.3.10}/PKG-INFO +2 -2
  2. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/doctor.py +36 -9
  3. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely.egg-info/PKG-INFO +2 -2
  4. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely.egg-info/requires.txt +1 -1
  5. {chartremotely-0.3.9 → chartremotely-0.3.10}/pyproject.toml +2 -2
  6. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_doctor.py +41 -0
  7. {chartremotely-0.3.9 → chartremotely-0.3.10}/LICENSE +0 -0
  8. {chartremotely-0.3.9 → chartremotely-0.3.10}/README.md +0 -0
  9. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/__init__.py +0 -0
  10. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/answer.py +0 -0
  11. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/assets/shortcut.plist +0 -0
  12. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/ax.py +0 -0
  13. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/cli.py +0 -0
  14. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/config.py +0 -0
  15. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/displays.py +0 -0
  16. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/guilock.py +0 -0
  17. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/hearing.py +0 -0
  18. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/holder.py +0 -0
  19. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/keystore.py +0 -0
  20. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/layout.py +0 -0
  21. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/mcpclient.py +0 -0
  22. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/patron.py +0 -0
  23. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/popups.py +0 -0
  24. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/push.py +0 -0
  25. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/recent.py +0 -0
  26. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/registry.py +0 -0
  27. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/relay.py +0 -0
  28. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/requestlog.py +0 -0
  29. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/resolve.py +0 -0
  30. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/scales.py +0 -0
  31. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/screen.py +0 -0
  32. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/server.py +0 -0
  33. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/services.py +0 -0
  34. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/setup.py +0 -0
  35. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/shortcut.py +0 -0
  36. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/snapshot.py +0 -0
  37. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/studies.py +0 -0
  38. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/symbol.py +0 -0
  39. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/tailnet.py +0 -0
  40. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/terminal.py +0 -0
  41. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/timeframe.py +0 -0
  42. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/understand.py +0 -0
  43. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/vocab.py +0 -0
  44. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/voice.py +0 -0
  45. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely/window.py +0 -0
  46. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely.egg-info/SOURCES.txt +0 -0
  47. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely.egg-info/dependency_links.txt +0 -0
  48. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely.egg-info/entry_points.txt +0 -0
  49. {chartremotely-0.3.9 → chartremotely-0.3.10}/chartremotely.egg-info/top_level.txt +0 -0
  50. {chartremotely-0.3.9 → chartremotely-0.3.10}/setup.cfg +0 -0
  51. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_changelog.py +0 -0
  52. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_hearing.py +0 -0
  53. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_patron.py +0 -0
  54. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_popups.py +0 -0
  55. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_push.py +0 -0
  56. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_recent.py +0 -0
  57. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_registry.py +0 -0
  58. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_relay.py +0 -0
  59. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_requestlog.py +0 -0
  60. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_resolve.py +0 -0
  61. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_scales.py +0 -0
  62. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_screen.py +0 -0
  63. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_setup.py +0 -0
  64. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_snapshot.py +0 -0
  65. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_stamp_changelog.py +0 -0
  66. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_timeframe.py +0 -0
  67. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_understand.py +0 -0
  68. {chartremotely-0.3.9 → chartremotely-0.3.10}/tests/test_voice.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chartremotely
3
- Version: 0.3.9
3
+ Version: 0.3.10
4
4
  Summary: Remote control of desktop charting applications on macOS
5
5
  Author: Lonnie VanZandt
6
6
  License:
@@ -219,7 +219,7 @@ Requires-Dist: pyobjc-framework-Quartz>=10; extra == "macos"
219
219
  Requires-Dist: pyobjc-framework-Security>=10; extra == "macos"
220
220
  Requires-Dist: mlx-whisper>=0.4.3; (sys_platform == "darwin" and platform_machine == "arm64") and extra == "macos"
221
221
  Provides-Extra: setup
222
- Requires-Dist: tollbooth-dpyc[nostr]==0.106.0; extra == "setup"
222
+ Requires-Dist: tollbooth-dpyc[nostr]==0.109.3; extra == "setup"
223
223
  Provides-Extra: dev
224
224
  Requires-Dist: pytest>=8; extra == "dev"
225
225
  Requires-Dist: ruff>=0.6; extra == "dev"
@@ -106,20 +106,47 @@ def _check_registry() -> tuple[str, str]:
106
106
  return OK, f"{len(rows)} securities cached"
107
107
 
108
108
 
109
+ #: A free lookup the listener answers from a table: no operator trip, no
110
+ #: fare, and the chart stays where it is. "read" was all three.
111
+ LISTENER_PROBE = b"scale half"
112
+ LISTENER_SECONDS = 5
113
+
114
+
115
+ def _post_probe(port: int, token: str) -> str:
116
+ request = urllib.request.Request(
117
+ f"http://127.0.0.1:{port}/chart", data=LISTENER_PROBE,
118
+ headers={"X-Token": token}, method="POST")
119
+ with urllib.request.urlopen(request, timeout=LISTENER_SECONDS) as response:
120
+ return response.read().decode().strip()
121
+
122
+
123
+ def listener_verdict(port: int, token: str, post=_post_probe) -> tuple[str, str]:
124
+ """Ask the listener a free table lookup and judge the answer."""
125
+ try:
126
+ reply = post(port, token)
127
+ except urllib.error.HTTPError as exc:
128
+ if exc.code == 403:
129
+ return BAD, f"listener on :{port} refused the token in config.json - restart it: chartremotely setup"
130
+ return BAD, f"listener on :{port} answered HTTP {exc.code}"
131
+ except urllib.error.URLError as exc:
132
+ if isinstance(exc.reason, TimeoutError):
133
+ return BAD, (f"listener on :{port} took over {LISTENER_SECONDS} s to answer a table lookup - "
134
+ "restart it: chartremotely setup")
135
+ return WARN, f"nothing listening on :{port} - run: chartremotely serve"
136
+ except TimeoutError:
137
+ return BAD, (f"listener on :{port} took over {LISTENER_SECONDS} s to answer a table lookup - "
138
+ "restart it: chartremotely setup")
139
+ if reply != "half":
140
+ return BAD, f"listener on :{port} answered {reply[:60]!r} to a table lookup"
141
+ return OK, f"listener answering on :{port}"
142
+
143
+
109
144
  def _check_listener() -> tuple[str, str]:
110
145
  cfg = config.load()
111
- port = cfg["port"]
112
146
  token = cfg.get("token")
113
147
  if not token:
114
148
  return WARN, "no token yet - it is generated when the listener first starts"
115
- request = urllib.request.Request(
116
- f"http://127.0.0.1:{port}/chart", data=b"read",
117
- headers={"X-Token": token}, method="POST")
118
- try:
119
- with urllib.request.urlopen(request, timeout=5) as response:
120
- return OK, f"listener answering on :{port} - {response.read().decode().strip()}"
121
- except urllib.error.URLError:
122
- return WARN, f"nothing listening on :{port} - run: chartremotely serve"
149
+ return listener_verdict(cfg["port"], token)
123
150
 
124
151
 
125
152
  CHECKS = (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chartremotely
3
- Version: 0.3.9
3
+ Version: 0.3.10
4
4
  Summary: Remote control of desktop charting applications on macOS
5
5
  Author: Lonnie VanZandt
6
6
  License:
@@ -219,7 +219,7 @@ Requires-Dist: pyobjc-framework-Quartz>=10; extra == "macos"
219
219
  Requires-Dist: pyobjc-framework-Security>=10; extra == "macos"
220
220
  Requires-Dist: mlx-whisper>=0.4.3; (sys_platform == "darwin" and platform_machine == "arm64") and extra == "macos"
221
221
  Provides-Extra: setup
222
- Requires-Dist: tollbooth-dpyc[nostr]==0.106.0; extra == "setup"
222
+ Requires-Dist: tollbooth-dpyc[nostr]==0.109.3; extra == "setup"
223
223
  Provides-Extra: dev
224
224
  Requires-Dist: pytest>=8; extra == "dev"
225
225
  Requires-Dist: ruff>=0.6; extra == "dev"
@@ -13,4 +13,4 @@ pyobjc-framework-Security>=10
13
13
  mlx-whisper>=0.4.3
14
14
 
15
15
  [setup]
16
- tollbooth-dpyc[nostr]==0.106.0
16
+ tollbooth-dpyc[nostr]==0.109.3
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "chartremotely"
3
- version = "0.3.9"
3
+ version = "0.3.10"
4
4
  description = "Remote control of desktop charting applications on macOS"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -25,7 +25,7 @@ macos = [
25
25
  ]
26
26
  # `chartremotely setup` makes and proves Nostr identities. The key and proof
27
27
  # code is the tollbooth-dpyc SDK's own, never rewritten here.
28
- setup = ["tollbooth-dpyc[nostr]==0.106.0"]
28
+ setup = ["tollbooth-dpyc[nostr]==0.109.3"]
29
29
  dev = ["pytest>=8", "ruff>=0.6"]
30
30
 
31
31
  [project.urls]
@@ -40,3 +40,44 @@ def test_both_running_on_a_live_python_is_ok():
40
40
  def test_launchctl_print_yields_the_pid_only_while_running():
41
41
  assert services.pid_in("com.chartremotely.relay = {\n\tstate = running\n\tpid = 12804\n}") == 12804
42
42
  assert services.pid_in("com.chartremotely.relay = {\n\tstate = not running\n}") is None
43
+
44
+
45
+ # -- the listener: a free table lookup, never a paid chart read ----------------------------
46
+
47
+ import urllib.error
48
+
49
+
50
+ def test_the_listener_probe_is_a_free_lookup_never_read():
51
+ # server.LOOKUPS needs Quartz to import; the verb is pinned here instead.
52
+ assert doctor.LISTENER_PROBE.split()[0] == b"scale"
53
+
54
+
55
+ def test_a_listener_that_answers_the_lookup_is_ok():
56
+ assert doctor.listener_verdict(8899, "t", post=lambda port, token: "half")[0] == doctor.OK
57
+
58
+
59
+ def _raises(exc):
60
+ def post(port, token):
61
+ raise exc
62
+ return post
63
+
64
+
65
+ def test_a_slow_listener_fails_and_says_so():
66
+ for exc in (TimeoutError("timed out"), urllib.error.URLError(TimeoutError("timed out"))):
67
+ status, detail = doctor.listener_verdict(8899, "t", post=_raises(exc))
68
+ assert status == doctor.BAD and "took over" in detail
69
+
70
+
71
+ def test_nothing_listening_points_at_serve():
72
+ status, detail = doctor.listener_verdict(8899, "t", post=_raises(urllib.error.URLError(ConnectionRefusedError())))
73
+ assert status == doctor.WARN and "chartremotely serve" in detail
74
+
75
+
76
+ def test_a_refused_token_is_named():
77
+ refused = urllib.error.HTTPError("http://127.0.0.1:8899/chart", 403, "Forbidden", {}, None)
78
+ status, detail = doctor.listener_verdict(8899, "t", post=_raises(refused))
79
+ assert status == doctor.BAD and "token" in detail
80
+
81
+
82
+ def test_a_wrong_answer_fails():
83
+ assert doctor.listener_verdict(8899, "t", post=lambda port, token: "ERR nope")[0] == doctor.BAD
File without changes
File without changes
File without changes