From e7ca2a73a821155667cb6027d4a929d2d10c354c Mon Sep 17 00:00:00 2001 From: krypton-byte Date: Sun, 23 Aug 2026 12:29:15 +0000 Subject: [PATCH 1/8] fix(examples): fix ruff lint errors (line length, f-string) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- examples/basic_bot.py | 6 ++++-- examples/group_bot.py | 15 ++++++++++----- examples/media_bot.py | 20 ++++++++++++++------ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/examples/basic_bot.py b/examples/basic_bot.py index 8cb7b4b..c2951c3 100644 --- a/examples/basic_bot.py +++ b/examples/basic_bot.py @@ -50,7 +50,9 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: sender_jid = source.sender text = (data.get_text() or "").strip() - print(f"[message] from={jid_to_text(sender_jid)} chat={jid_to_text(chat_jid)} text={text!r}") + sender = jid_to_text(sender_jid) + chat = jid_to_text(chat_jid) + print(f"[message] from={sender} chat={chat} text={text!r}") if not text: return @@ -83,7 +85,7 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: # ── /info ──────────────────────────────────────────────────────────── elif cmd == "info": info_lines = [ - f"*Your Info*", + "*Your Info*", f"• JID: {jid_to_text(sender_jid)}", f"• Chat: {jid_to_text(chat_jid)}", f"• Push name: {info.push_name or '(none)'}", diff --git a/examples/group_bot.py b/examples/group_bot.py index 5448162..f52371c 100644 --- a/examples/group_bot.py +++ b/examples/group_bot.py @@ -53,7 +53,9 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: sender_jid = source.sender text = (data.get_text() or "").strip() - print(f"[message] from={jid_to_text(sender_jid)} chat={jid_to_text(chat_jid)} text={text!r}") + sender = jid_to_text(sender_jid) + chat = jid_to_text(chat_jid) + print(f"[message] from={sender} chat={chat} text={text!r}") if not text or not source.is_group: return @@ -66,7 +68,7 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: try: metadata = await client.groups.get_metadata(chat_jid) lines = [ - f"*Group Info*", + "*Group Info*", f"• Name: {metadata.subject}", f"• Members: {metadata.size or len(metadata.participants)}", f"• Locked: {'Yes' if metadata.is_locked else 'No'}", @@ -114,15 +116,18 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: # ── /ephemeral N ───────────────────────────────────────────────────── elif cmd == "ephemeral": if len(cmd_parts) < 2: - await client.send_text(chat_jid, "Usage: /ephemeral \n0 = off", quoted=event) + msg = "Usage: /ephemeral \n0 = off" + await client.send_text(chat_jid, msg, quoted=event) return try: seconds = int(cmd_parts[1]) await client.groups.set_ephemeral(chat_jid, seconds) if seconds == 0: - await client.send_text(chat_jid, "Disabling disappearing messages", quoted=event) + msg = "Disabling disappearing messages" + await client.send_text(chat_jid, msg, quoted=event) else: - await client.send_text(chat_jid, f"Disappearing messages set to {seconds}s", quoted=event) + msg = f"Disappearing messages set to {seconds}s" + await client.send_text(chat_jid, msg, quoted=event) except ValueError: await client.send_text(chat_jid, "Invalid number", quoted=event) diff --git a/examples/media_bot.py b/examples/media_bot.py index 6c8dd13..4ddb833 100644 --- a/examples/media_bot.py +++ b/examples/media_bot.py @@ -84,7 +84,9 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: sender_jid = source.sender text = (data.get_text() or "").strip().lower() - print(f"[message] from={jid_to_text(sender_jid)} chat={jid_to_text(chat_jid)} text={text!r}") + sender = jid_to_text(sender_jid) + chat = jid_to_text(chat_jid) + print(f"[message] from={sender} chat={chat} text={text!r}") if not text: return @@ -99,7 +101,8 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: ) print(f"[photo] sent: {result.message_id}") except Exception as exc: - await client.send_text(chat_jid, f"Failed to send photo: {exc}", quoted=event) + msg = f"Failed to send photo: {exc}" + await client.send_text(chat_jid, msg, quoted=event) # ── /document ──────────────────────────────────────────────────────── elif text == "document": @@ -107,11 +110,14 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: await client.chatstate.send_composing(chat_jid) doc_data = await download_bytes(SAMPLE_MEDIA["document"]["url"]) result = await client.send_document( - chat_jid, doc_data, file_name="sample.pdf", caption="Sample PDF document" + chat_jid, doc_data, + file_name="sample.pdf", + caption="Sample PDF document", ) print(f"[document] sent: {result.message_id}") except Exception as exc: - await client.send_text(chat_jid, f"Failed to send document: {exc}", quoted=event) + msg = f"Failed to send document: {exc}" + await client.send_text(chat_jid, msg, quoted=event) # ── /audio ─────────────────────────────────────────────────────────── elif text == "audio": @@ -121,7 +127,8 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: result = await client.send_audio(chat_jid, audio_data) print(f"[audio] sent: {result.message_id}") except Exception as exc: - await client.send_text(chat_jid, f"Failed to send audio: {exc}", quoted=event) + msg = f"Failed to send audio: {exc}" + await client.send_text(chat_jid, msg, quoted=event) # ── /video ─────────────────────────────────────────────────────────── elif text == "video": @@ -133,7 +140,8 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: ) print(f"[video] sent: {result.message_id}") except Exception as exc: - await client.send_text(chat_jid, f"Failed to send video: {exc}", quoted=event) + msg = f"Failed to send video: {exc}" + await client.send_text(chat_jid, msg, quoted=event) # ── /help ──────────────────────────────────────────────────────────── elif text == "help": From a84241433105819b9943c8605d42c35647d1d346 Mon Sep 17 00:00:00 2001 From: krypton-byte Date: Sun, 23 Aug 2026 12:32:33 +0000 Subject: [PATCH 2/8] ci(pr): add PR check workflow for lint, tests, stubs, and docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runs on every pull request to catch issues before merge: - Ruff lint + format check - Pytest (build + test) - Pyright + mypy type stub validation - Zensical docs build validation 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- .github/workflows/pr-check.yml | 130 +++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 .github/workflows/pr-check.yml diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 0000000..0b57013 --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,130 @@ +name: PR Check + +on: + pull_request: + branches: [master, main, dev] + +permissions: + contents: read + +jobs: + quality: + name: Quality Checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - uses: astral-sh/setup-uv@v5 + + - uses: dtolnay/rust-toolchain@stable + + - name: Install dev dependencies + run: uv sync --group dev --no-install-project + + - name: Ruff lint + run: uv run --no-project ruff check . + + - name: Ruff format check + run: uv run --no-project ruff format --check . + + tests: + name: Python Tests + needs: quality + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - uses: astral-sh/setup-uv@v5 + + - uses: dtolnay/rust-toolchain@stable + + - name: Install dev dependencies + run: uv sync --group dev --no-install-project + + - name: Build native extension + run: uv run maturin develop + + - name: Run pytest + run: uv run pytest -q + + stubs: + name: Type Stubs Check + needs: quality + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - uses: astral-sh/setup-uv@v5 + + - uses: dtolnay/rust-toolchain@stable + + - name: Install dependencies + run: uv sync --group dev --group docs --no-install-project + + - name: Validate .pyi stubs with pyright + run: | + uv run --no-project pyright python/tryx/client.pyi \ + python/tryx/events.pyi \ + python/tryx/types.pyi \ + python/tryx/wacore.pyi \ + python/tryx/helpers.pyi \ + python/tryx/backend.pyi \ + python/tryx/exceptions.pyi \ + python/tryx/__init__.pyi \ + python/tryx/tryx.pyi \ + python/tryx/media.pyi + + - name: Validate .pyi stubs with mypy + run: | + uv run --no-project mypy python/tryx/client.pyi \ + python/tryx/events.pyi \ + python/tryx/types.pyi \ + python/tryx/wacore.pyi \ + python/tryx/helpers.pyi \ + python/tryx/backend.pyi \ + python/tryx/exceptions.pyi \ + python/tryx/__init__.pyi \ + python/tryx/tryx.pyi \ + python/tryx/media.pyi \ + --ignore-missing-imports + + docs: + name: Documentation Build + needs: quality + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - uses: astral-sh/setup-uv@v5 + + - uses: dtolnay/rust-toolchain@stable + + - name: Install dependencies + run: uv sync --group dev --group docs --no-install-project + + - name: Build docs (validation only) + run: uv run --no-project zensical build -f mkdocs.yml -s From cd4a4cc8f90482dae3ae44f21c011e6ebd65e380 Mon Sep 17 00:00:00 2001 From: krypton-byte Date: Sun, 23 Aug 2026 12:35:45 +0000 Subject: [PATCH 3/8] style: format all files with ruff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit only checks staged files, CI checks all files. Format the entire project to prevent CI failures on PRs. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- docs/api/backend.md | 1 + docs/core-concepts/architecture.md | 1 + docs/core-concepts/event-model.md | 8 +++-- docs/core-concepts/type-system.md | 50 ++++++++++++++++-------------- docs/getting-started/quickstart.md | 10 ++++-- docs/index.md | 2 ++ docs/operations/reliability.md | 2 +- docs/reference/error-handling.md | 4 ++- docs/tutorials/command-bot.md | 8 ++--- examples/basic_bot.py | 1 + examples/group_bot.py | 1 + examples/media_bot.py | 4 ++- 12 files changed, 58 insertions(+), 34 deletions(-) diff --git a/docs/api/backend.md b/docs/api/backend.md index 91a5645..3248988 100644 --- a/docs/api/backend.md +++ b/docs/api/backend.md @@ -47,6 +47,7 @@ exposing `lib_path` and `config_json` attributes satisfies this protocol. ```python import json + class PostgresStore: lib_path: str # path to compiled .so config_json: str diff --git a/docs/core-concepts/architecture.md b/docs/core-concepts/architecture.md index 8e262bc..4194aea 100644 --- a/docs/core-concepts/architecture.md +++ b/docs/core-concepts/architecture.md @@ -175,6 +175,7 @@ All WhatsApp interactions flow through typed events: ```python from tryx.events import EvMessage + @app.on(EvMessage) async def handle_message(client, event: EvMessage): # event.sender, event.text, event.media, etc. diff --git a/docs/core-concepts/event-model.md b/docs/core-concepts/event-model.md index 9b2ea53..f18709e 100644 --- a/docs/core-concepts/event-model.md +++ b/docs/core-concepts/event-model.md @@ -26,14 +26,17 @@ from tryx.events import EvMessage, EvConnected, EvDisconnected app = Tryx(store) + @app.on(EvConnected) async def on_connected(client): print("Connected to WhatsApp!") + @app.on(EvMessage) async def on_message(client, event): print(f"Message from {event.sender}: {event.text}") + @app.on(EvDisconnected) async def on_disconnected(client): print("Disconnected, will reconnect...") @@ -44,8 +47,7 @@ async def on_disconnected(client): Every handler receives two arguments: ```python -async def handler(client: TryxClient, event: EventType) -> None: - ... +async def handler(client: TryxClient, event: EventType) -> None: ... ``` - `client` — The `TryxClient` instance, ready to send messages and query state @@ -61,6 +63,7 @@ called for each event: async def log_message(client, event): logger.info(f"Received: {event.message_id}") + @app.on(EvMessage) async def process_message(client, event): if event.text: @@ -211,6 +214,7 @@ Build idempotent handlers using message identifiers: ```python processed = set() + @app.on(EvMessage) async def idempotent_handler(client, event): message_id = event.data.message_info.id diff --git a/docs/core-concepts/type-system.md b/docs/core-concepts/type-system.md index 5c8749b..cd37b2d 100644 --- a/docs/core-concepts/type-system.md +++ b/docs/core-concepts/type-system.md @@ -57,6 +57,7 @@ Contains identity, routing, and attribute information: ```python from tryx.events import EvMessage + @app.on(EvMessage) async def handle(client, event): info = event.data.message_info @@ -66,12 +67,12 @@ async def handle(client, event): timestamp = info.timestamp # Routing - sender = info.source.sender # JID - chat = info.source.chat # JID + sender = info.source.sender # JID + chat = info.source.chat # JID participant = info.source.participant # JID | None # Attributes - msg_type = info.message_type # "text", "image", etc. + msg_type = info.message_type # "text", "image", etc. is_from_me = info.is_from_me ``` @@ -82,9 +83,9 @@ Returned by all send methods: ```python result = await client.send_text(jid, "Hello") -result.message_id # str: unique message ID -result.timestamp # int: server timestamp -result.key # MessageKey: message key for tracking +result.message_id # str: unique message ID +result.timestamp # int: server timestamp +result.key # MessageKey: message key for tracking ``` ### MediaReuploadResult — Media Retry Output @@ -98,7 +99,7 @@ result = await client.request_media_reupload( media_key=b"...", ) -result.url # str: re-uploaded media URL +result.url # str: re-uploaded media URL result.direct_path # str: direct download path ``` @@ -109,9 +110,9 @@ Returned by media upload methods: ```python upload = await client.upload_photo(photo_bytes, jid) -upload.url # str: media URL +upload.url # str: media URL upload.direct_path # str: direct path -upload.media_key # bytes: encryption key +upload.media_key # bytes: encryption key upload.file_length # int: file size ``` @@ -120,10 +121,10 @@ upload.file_length # int: file size ```python picture = await client.contact.get_profile_picture(jid, preview=False) -picture.url # str: image URL -picture.direct_path # str: direct download path -picture.file_length # int: file size -picture.mimetype # str: image MIME type +picture.url # str: image URL +picture.direct_path # str: direct download path +picture.file_length # int: file size +picture.mimetype # str: image MIME type ``` --- @@ -134,15 +135,16 @@ Every event class has a defined payload contract: ```python from tryx.events import ( - EvMessage, # Incoming message - EvConnected, # WebSocket connected - EvDisconnected, # WebSocket lost - EvLoggedOut, # Session invalidated - EvPresence, # User typing/recording - EvGroupUpdate, # Group metadata changed - EvReceipt, # Delivery/read receipt + EvMessage, # Incoming message + EvConnected, # WebSocket connected + EvDisconnected, # WebSocket lost + EvLoggedOut, # Session invalidated + EvPresence, # User typing/recording + EvGroupUpdate, # Group metadata changed + EvReceipt, # Delivery/read receipt ) + @app.on(EvMessage) async def handle(client, event: EvMessage): # event.sender: JID @@ -195,9 +197,9 @@ StatusPrivacySetting.DenyList ```python from tryx.types import ChatStateType -ChatStateType.Composing # User is typing -ChatStateType.Recording # User is recording audio -ChatStateType.Paused # User stopped typing +ChatStateType.Composing # User is typing +ChatStateType.Recording # User is recording audio +ChatStateType.Paused # User stopped typing ``` ### Presence @@ -262,10 +264,12 @@ Keep type boundaries clean between layers: from tryx.events import EvMessage from tryx.types import JID + # Input boundary: accept typed events def extract_sender(event: EvMessage) -> JID: return event.data.message_info.source.sender + # Output boundary: return typed results async def forward_message(client, event: EvMessage, target: JID) -> SendResult: text = event.data.text or "" diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 38b64f9..374aa80 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -19,6 +19,7 @@ backend = SqliteStore("whatsapp.db") # Initialize the Tryx runtime app = Tryx(backend) + # Register an event handler @app.on(EvMessage) async def on_message(client, event): @@ -28,6 +29,7 @@ async def on_message(client, event): if text.lower() == "ping": await client.send_text(chat, "pong") + # Start the bot asyncio.run(app.run()) ``` @@ -113,8 +115,8 @@ with open("voice.ogg", "rb") as f: await client.send_audio( to=jid, audio_data=audio_data, - ptt=True, # Push-to-talk (voice note) - seconds=15, # Duration hint + ptt=True, # Push-to-talk (voice note) + seconds=15, # Duration hint ) ``` @@ -163,10 +165,12 @@ await client.send_sticker(to=jid, sticker_data=sticker_data) ```python from tryx.events import EvMessage, EvConnected, EvDisconnected + @app.on(EvConnected) async def on_connected(client): print("Connected to WhatsApp!") + @app.on(EvMessage) async def on_message(client, event): text = event.data.get_text() or "" @@ -182,6 +186,7 @@ async def on_message(client, event): names = [m.subject for m in groups.values()] await client.send_text(chat, f"Groups: {', '.join(names)}") + @app.on(EvDisconnected) async def on_disconnected(client): print("Disconnected, will reconnect...") @@ -229,6 +234,7 @@ import logging logger = logging.getLogger(__name__) + @app.on(EvMessage) async def safe_handler(client, event): try: diff --git a/docs/index.md b/docs/index.md index d634ed0..997a3ae 100644 --- a/docs/index.md +++ b/docs/index.md @@ -49,6 +49,7 @@ from tryx.backend import SqliteStore app = Tryx(SqliteStore("whatsapp.db")) client = app.get_client() + @app.on(EvMessage) async def on_message(client, event): text = event.data.get_text() @@ -56,6 +57,7 @@ async def on_message(client, event): if text: await client.send_text(chat, f"Echo: {text}") + app.run_blocking() ``` diff --git a/docs/operations/reliability.md b/docs/operations/reliability.md index 0b94904..45759b6 100644 --- a/docs/operations/reliability.md +++ b/docs/operations/reliability.md @@ -88,7 +88,7 @@ async def retry_with_backoff( last_exc = exc if attempt == max_attempts - 1: break - delay = min(base_delay * (2 ** attempt), max_delay) + delay = min(base_delay * (2**attempt), max_delay) if jitter: delay *= random.uniform(0.5, 1.0) await asyncio.sleep(delay) diff --git a/docs/reference/error-handling.md b/docs/reference/error-handling.md index db56b58..c57fafa 100644 --- a/docs/reference/error-handling.md +++ b/docs/reference/error-handling.md @@ -98,7 +98,9 @@ Persist context before recovery attempts: ```python # Store poll metadata first -poll_id, secret = await client.polls.create(to=chat_jid, name="Q", options=["A", "B"], selectable_count=1) +poll_id, secret = await client.polls.create( + to=chat_jid, name="Q", options=["A", "B"], selectable_count=1 +) # Now if vote fails, we have the poll_id and secret for recovery try: diff --git a/docs/tutorials/command-bot.md b/docs/tutorials/command-bot.md index f9b5df9..208ed90 100644 --- a/docs/tutorials/command-bot.md +++ b/docs/tutorials/command-bot.md @@ -234,12 +234,12 @@ async def cmd_ban(client, event, args): ```python COMMANDS = { "ping": cmd_ping, - "p": cmd_ping, # alias + "p": cmd_ping, # alias "echo": cmd_echo, - "e": cmd_echo, # alias + "e": cmd_echo, # alias "help": cmd_help, - "h": cmd_help, # alias - "?": cmd_help, # alias + "h": cmd_help, # alias + "?": cmd_help, # alias } ``` diff --git a/examples/basic_bot.py b/examples/basic_bot.py index c2951c3..d960623 100644 --- a/examples/basic_bot.py +++ b/examples/basic_bot.py @@ -95,6 +95,7 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: # ── Entry point ────────────────────────────────────────────────────────────── + async def main() -> None: print(f"Starting basic bot with DB: {DB_PATH}") await app.run() diff --git a/examples/group_bot.py b/examples/group_bot.py index f52371c..23c4a73 100644 --- a/examples/group_bot.py +++ b/examples/group_bot.py @@ -163,6 +163,7 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: # ── Entry point ────────────────────────────────────────────────────────────── + async def main() -> None: print(f"Starting group bot with DB: {DB_PATH}") await app.run() diff --git a/examples/media_bot.py b/examples/media_bot.py index 4ddb833..8759d79 100644 --- a/examples/media_bot.py +++ b/examples/media_bot.py @@ -110,7 +110,8 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: await client.chatstate.send_composing(chat_jid) doc_data = await download_bytes(SAMPLE_MEDIA["document"]["url"]) result = await client.send_document( - chat_jid, doc_data, + chat_jid, + doc_data, file_name="sample.pdf", caption="Sample PDF document", ) @@ -158,6 +159,7 @@ async def on_message(client: TryxClient, event: EvMessage) -> None: # ── Entry point ────────────────────────────────────────────────────────────── + async def main() -> None: print(f"Starting media bot with DB: {DB_PATH}") await app.run() From e1ca3b4e26834aae2b2e82cbca7a661d9d794314 Mon Sep 17 00:00:00 2001 From: krypton-byte Date: Sun, 23 Aug 2026 12:43:46 +0000 Subject: [PATCH 4/8] perf(ci): add Rust cache to tests job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swatinem/rust-cache caches Cargo build artifacts between runs. First build still ~10min, subsequent builds ~2-3min. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- .github/workflows/CI.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7428dad..0981b9e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -51,6 +51,10 @@ jobs: python-version: "3.12" - uses: astral-sh/setup-uv@v5 - uses: dtolnay/rust-toolchain@stable + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + workspaces: "libs/whatsapp-rust -> target" - name: Install dev dependencies run: uv sync --group dev --no-install-project - name: Build native extension From 28806efa32c8efa0cd51b9b70d2bacba98bca3e2 Mon Sep 17 00:00:00 2001 From: krypton-byte Date: Sun, 23 Aug 2026 12:44:12 +0000 Subject: [PATCH 5/8] perf(ci): add Rust cache to PR check tests job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- .github/workflows/pr-check.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 0b57013..75bfaf8 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -50,6 +50,11 @@ jobs: - uses: dtolnay/rust-toolchain@stable + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + workspaces: "libs/whatsapp-rust -> target" + - name: Install dev dependencies run: uv sync --group dev --no-install-project From 1a8c91efde185203d9f9f9a5bef5c3a0c6c6f1a8 Mon Sep 17 00:00:00 2001 From: krypton-byte Date: Sun, 23 Aug 2026 12:48:41 +0000 Subject: [PATCH 6/8] docs(security): add disclosure notice header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- SECURITY.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SECURITY.md b/SECURITY.md index 121947c..651e382 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,5 +1,7 @@ # Security Policy +> For security inquiries, please follow the disclosure process below. + ## Reporting a Vulnerability If you discover a security vulnerability in Tryx, please report it From 54ce0d9ecf6fc1e5202ccfc3ed8f019cce93fbc8 Mon Sep 17 00:00:00 2001 From: krypton-byte Date: Sun, 23 Aug 2026 13:22:12 +0000 Subject: [PATCH 7/8] fix(tests): import _PythonPrototypeAudioPlayer to fix test when Rust extension is built MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Rust native AudioPlayer has a different API (buffer_frames, file paths) than the Python prototype (queue_size, async iterators), causing test_audio_player_emits_frames_and_finishes_once to fail when the native extension is loaded. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- tests/test_media_contracts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_media_contracts.py b/tests/test_media_contracts.py index b5291f4..cc97b66 100644 --- a/tests/test_media_contracts.py +++ b/tests/test_media_contracts.py @@ -3,12 +3,12 @@ import pytest from tryx.media import ( - AudioPlayer, AudioSink, AudioSource, VideoFrame, validate_audio_frame, ) +from tryx.media import _PythonPrototypeAudioPlayer as AudioPlayer async def _frames(*frames: bytes): From f312a8a33f865f716569011374dc83c690204eb2 Mon Sep 17 00:00:00 2001 From: krypton-byte Date: Sun, 23 Aug 2026 13:41:35 +0000 Subject: [PATCH 8/8] perf(ci): cache root tryx target alongside whatsapp-rust workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous rust-cache only cached libs/whatsapp-rust/target, leaving the root ./target (where maturin builds the PyO3 extension) uncached. This caused full recompilation of the tryx crate on every CI run. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- .github/workflows/CI.yml | 5 ++++- .github/workflows/pr-check.yml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0981b9e..e9a5dda 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -51,7 +51,10 @@ jobs: python-version: "3.12" - uses: astral-sh/setup-uv@v5 - uses: dtolnay/rust-toolchain@stable - - name: Rust cache + - name: Rust cache (tryx root) + uses: Swatinem/rust-cache@v2 + + - name: Rust cache (whatsapp-rust workspace) uses: Swatinem/rust-cache@v2 with: workspaces: "libs/whatsapp-rust -> target" diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 75bfaf8..e4ee365 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -50,7 +50,10 @@ jobs: - uses: dtolnay/rust-toolchain@stable - - name: Rust cache + - name: Rust cache (tryx root) + uses: Swatinem/rust-cache@v2 + + - name: Rust cache (whatsapp-rust workspace) uses: Swatinem/rust-cache@v2 with: workspaces: "libs/whatsapp-rust -> target"