From cf42c30e9431a8e6552b2e413375bc8c272cc7bb Mon Sep 17 00:00:00 2001 From: Jordan Shaw Date: Fri, 12 Jun 2026 17:40:36 -0400 Subject: [PATCH 1/2] Prepare Python SDK release --- .gitignore | 1 + README.md | 3 ++- datanet/client.py | 20 ++++++++++++++++++++ examples/basic_subscribe.py | 2 +- examples/publish.py | 2 +- pyproject.toml | 11 +++++++++++ tests/test_client.py | 7 +++++++ 7 files changed, 43 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ba691a6..2cd6ec3 100644 --- a/.gitignore +++ b/.gitignore @@ -153,6 +153,7 @@ activemq-data/ !.env.example .envrc .venv +.venv-*/ env/ venv/ ENV/ diff --git a/README.md b/README.md index 9b68510..1549b5f 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ try: while True: time.sleep(1) except KeyboardInterrupt: - dn.disconnect() + dn.disconnect_sync() ``` ## API reference @@ -176,6 +176,7 @@ python examples/publish_p5.py | `await connect()` | Fetch JWT and open WebSocket | | `connect_sync(timeout=10)` | Same, but runs in a background thread | | `await disconnect()` | Close connection and stop run loop | +| `disconnect_sync(timeout=10)` | Close a sync/background-thread connection | | `subscribe(channel, handler)` | Register an async message handler | | `unsubscribe(channel, handler=None)` | Remove handler (or all) from channel | | `await publish(channel, data, content_type=None, metadata=None)` | Send JSON, or auto-detect bytes-like binary data | diff --git a/datanet/client.py b/datanet/client.py index 3ab0307..726f902 100644 --- a/datanet/client.py +++ b/datanet/client.py @@ -691,6 +691,26 @@ async def _run() -> None: ) self._connected_event.wait(timeout=0.05) + def disconnect_sync(self, timeout: float = 10.0) -> None: + """Disconnect from sync/background-thread code and wait for cleanup. + + This is the sync counterpart to :meth:`disconnect` for programs that + used :meth:`connect_sync`. Async applications should prefer + ``await disconnect()`` or the async context manager. + """ + if self._loop and self._loop.is_running(): + future = asyncio.run_coroutine_threadsafe(self.disconnect(), self._loop) + future.result(timeout=timeout) + else: + asyncio.run(self.disconnect()) + + if ( + self._thread + and self._thread.is_alive() + and threading.current_thread() is not self._thread + ): + self._thread.join(timeout=timeout) + # ── Async context manager ───────────────────────────────────────────────── async def __aenter__(self) -> "DataNet": diff --git a/examples/basic_subscribe.py b/examples/basic_subscribe.py index e0ed373..b758b19 100644 --- a/examples/basic_subscribe.py +++ b/examples/basic_subscribe.py @@ -140,7 +140,7 @@ async def on_message(data: object, meta: MessageMeta) -> None: time.sleep(0.5) except KeyboardInterrupt: print("\nShutting down…") - dn.disconnect() + dn.disconnect_sync() print("Goodbye.") diff --git a/examples/publish.py b/examples/publish.py index 47e039e..2c5ebf0 100644 --- a/examples/publish.py +++ b/examples/publish.py @@ -215,7 +215,7 @@ async def handle_error(exc: Exception) -> None: except KeyboardInterrupt: print("\nShutting down…") finally: - dn.disconnect() + dn.disconnect_sync() print(f"Published {published} readings. Goodbye.") diff --git a/pyproject.toml b/pyproject.toml index 35e1fae..0796cac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,17 @@ Documentation = "https://datanet.art/docs" [tool.hatch.build.targets.wheel] packages = ["datanet"] +[tool.hatch.build.targets.sdist] +include = [ + "/datanet", + "/examples", + "/tests", + "/LICENSE", + "/README.md", + "/PROTOCOL.md", + "/pyproject.toml", +] + [tool.pytest.ini_options] asyncio_mode = "auto" diff --git a/tests/test_client.py b/tests/test_client.py index 2e61d73..8333640 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -111,6 +111,13 @@ async def on_message(data, meta): self.assertEqual(seen, []) + def test_disconnect_sync_without_active_connection_is_safe(self): + client = DataNet("ak_test") + + client.disconnect_sync() + + self.assertFalse(client.connected) + async def test_binary_messages_dispatch_to_binary_and_any_handlers(self): client = DataNet("ak_test") binary_seen = [] From 79dfef3892eb70d5ccec830f899d8cce089702e1 Mon Sep 17 00:00:00 2001 From: Jordan Shaw Date: Fri, 12 Jun 2026 18:25:54 -0400 Subject: [PATCH 2/2] Polish Python SDK release docs --- .env.example | 5 +++++ CHANGELOG.md | 13 +++++++++++++ README.md | 22 ++++++++++++++++++++++ pyproject.toml | 10 +++++++--- 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.env.example b/.env.example index 72e4f22..176753c 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,11 @@ DATANET_API_KEY=ak_your_api_key_here + +# JSON examples use this channel. DATANET_CHANNEL=project.your_project_id.demo + +# Binary examples use this first; if unset, they fall back to DATANET_CHANNEL. DATANET_BINARY_CHANNEL=project.your_project_id.lighting.dmx + DATANET_DEVICE_ID=python-example DATANET_CLIENT_ID=datanet-python-example diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..539acff --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +## 0.1.0 - 2026-06-12 + +Initial public release. + +- Added async DataNet WebSocket client. +- Added sync/background-thread helpers. +- Added JSON publish and subscribe support. +- Added binary publish and subscribe support. +- Added DMX and Art-Net helper utilities. +- Added runnable examples for JSON, p5-style coordinates, and binary DMX. +- Added pytest coverage for client behavior, errors, and binary helpers. diff --git a/README.md b/README.md index 1549b5f..b3e2eee 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,28 @@ DATANET_WS_URL='ws://localhost:8080' \ python examples/publish.py ``` +### Binary examples + +The JSON examples use `DATANET_CHANNEL`. + +The binary DMX examples use `DATANET_BINARY_CHANNEL` first, then fall back to +`DATANET_CHANNEL` if no binary channel is set. Use the same binary channel for +both publisher and subscriber: + +```bash +DATANET_API_KEY='ak_local_key_here' \ +DATANET_BINARY_CHANNEL='project.abc.lighting.dmx' \ +python examples/binary_dmx_subscribe.py +``` + +In another terminal: + +```bash +DATANET_API_KEY='ak_local_key_here' \ +DATANET_BINARY_CHANNEL='project.abc.lighting.dmx' \ +python examples/binary_dmx_publish.py +``` + To drive the browser p5 visualizer demo directly with pixel coordinates: ```bash diff --git a/pyproject.toml b/pyproject.toml index 0796cac..8098e2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,12 +33,13 @@ dev = [ "pytest>=8.0", "pytest-asyncio>=0.23", "aioresponses>=0.7", + "ruff>=0.8", ] [project.urls] Homepage = "https://datanet.art" -Repository = "https://github.com/datanet-art/datanet-python" -Documentation = "https://datanet.art/docs" +Documentation = "https://datanet.art/docs/python" +Source = "https://github.com/datanet-art/datanet-python" [tool.hatch.build.targets.wheel] packages = ["datanet"] @@ -49,6 +50,8 @@ include = [ "/examples", "/tests", "/LICENSE", + "/CHANGELOG.md", + "/.env.example", "/README.md", "/PROTOCOL.md", "/pyproject.toml", @@ -62,4 +65,5 @@ target-version = "py311" line-length = 88 [tool.ruff.lint] -select = ["E", "F", "I", "UP"] +select = ["E", "F"] +ignore = ["E501"]