From e3e5ec601f044b27a26dbe91a0e81faceb116066 Mon Sep 17 00:00:00 2001 From: harley-poly Date: Wed, 16 Sep 2026 21:42:40 -0400 Subject: [PATCH] fix: correct order snapshot subscription types --- .github/workflows/ci.yml | 2 +- README.md | 36 ++++++++++----- polymarket_us/websocket/types.py | 3 +- tests/test_order_snapshot_contract.py | 65 +++++++++++++++++++++++++++ tests/types/order_snapshot.py | 15 +++++++ 5 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 tests/test_order_snapshot_contract.py create mode 100644 tests/types/order_snapshot.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f36b35..fe50513 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: run: uv sync --all-extras - name: Run mypy - run: uv run mypy polymarket_us + run: uv run mypy polymarket_us tests/types/order_snapshot.py test: runs-on: ubuntu-latest diff --git a/README.md b/README.md index bfad525..b055cbe 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,16 @@ client = PolymarketUS( ) # Create an order -order = client.orders.create({ - "marketSlug": "btc-100k-2025", - "intent": "ORDER_INTENT_BUY_LONG", - "type": "ORDER_TYPE_LIMIT", - "price": {"value": "0.55", "currency": "USD"}, - "quantity": 100, - "tif": "TIME_IN_FORCE_GOOD_TILL_CANCEL", -}) +order = client.orders.create( + { + "marketSlug": "btc-100k-2025", + "intent": "ORDER_INTENT_BUY_LONG", + "type": "ORDER_TYPE_LIMIT", + "price": {"value": "0.55", "currency": "USD"}, + "quantity": 100, + "tif": "TIME_IN_FORCE_GOOD_TILL_CANCEL", + } +) # Get open orders open_orders = client.orders.list() @@ -92,6 +94,7 @@ import asyncio import os from polymarket_us import AsyncPolymarketUS + async def main(): async with AsyncPolymarketUS( key_id=os.environ["POLYMARKET_KEY_ID"], @@ -105,6 +108,7 @@ async def main(): print(f"Found {len(events['events'])} events") print(f"Found {len(markets['markets'])} markets") + asyncio.run(main()) ``` @@ -116,7 +120,7 @@ The SDK automatically signs requests with your credentials: ```python client = PolymarketUS( - key_id="your-api-key-id", # UUID + key_id="your-api-key-id", # UUID secret_key="your-secret-key", # Base64-encoded Ed25519 private key ) ``` @@ -156,8 +160,8 @@ except APIConnectionError as e: client = PolymarketUS( key_id="your-key-id", secret_key="your-secret-key", - timeout=30.0, # Request timeout in seconds (default: 30.0) - max_retries=2, # Automatic retries for idempotent requests (default: 2) + timeout=30.0, # Request timeout in seconds (default: 30.0) + max_retries=2, # Automatic retries for idempotent requests (default: 2) ) ``` @@ -186,11 +190,16 @@ except APIError as e: > **Note**: WebSocket connections are async-only due to their event-driven nature. > Use `asyncio.run()` when working with the sync client, or use `AsyncPolymarketUS` directly. +`SUBSCRIPTION_TYPE_ORDER` streams updates only. Request a one-shot order snapshot +separately with `SUBSCRIPTION_TYPE_ORDER_SNAPSHOT` and a distinct request ID. A +successful snapshot ends with an `eof: true` frame; failures use the `error` handler. + ```python import asyncio import os from polymarket_us import PolymarketUS + async def main(): client = PolymarketUS( key_id=os.environ["POLYMARKET_KEY_ID"], @@ -201,7 +210,8 @@ async def main(): private_ws = client.ws.private() def on_order_snapshot(data): - print(f"Open orders: {data['orderSubscriptionSnapshot']['orders']}") + snapshot = data["orderSubscriptionSnapshot"] + print(f"Order snapshot: {snapshot['orders']}, eof={snapshot['eof']}") def on_order_update(data): print(f"Order execution: {data['orderSubscriptionUpdate']['execution']}") @@ -212,6 +222,7 @@ async def main(): await private_ws.connect() await private_ws.subscribe("order-sub-1", "SUBSCRIPTION_TYPE_ORDER") + await private_ws.subscribe("order-snapshot-1", "SUBSCRIPTION_TYPE_ORDER_SNAPSHOT") await private_ws.subscribe("pos-sub-1", "SUBSCRIPTION_TYPE_POSITION") await private_ws.subscribe("balance-sub-1", "SUBSCRIPTION_TYPE_ACCOUNT_BALANCE") @@ -231,6 +242,7 @@ async def main(): await private_ws.close() await markets_ws.close() + asyncio.run(main()) ``` diff --git a/polymarket_us/websocket/types.py b/polymarket_us/websocket/types.py index bf8055c..9a2b4b8 100644 --- a/polymarket_us/websocket/types.py +++ b/polymarket_us/websocket/types.py @@ -6,6 +6,7 @@ PrivateSubscriptionType = Literal[ "SUBSCRIPTION_TYPE_ORDER", + "SUBSCRIPTION_TYPE_ORDER_SNAPSHOT", "SUBSCRIPTION_TYPE_POSITION", "SUBSCRIPTION_TYPE_ACCOUNT_BALANCE", ] @@ -51,7 +52,7 @@ class OrderSnapshot(TypedDict): """Order snapshot message.""" requestId: str - subscriptionType: Literal["SUBSCRIPTION_TYPE_ORDER"] + subscriptionType: Literal["SUBSCRIPTION_TYPE_ORDER_SNAPSHOT"] orderSubscriptionSnapshot: _OrderSubscriptionSnapshot diff --git a/tests/test_order_snapshot_contract.py b/tests/test_order_snapshot_contract.py new file mode 100644 index 0000000..1d14333 --- /dev/null +++ b/tests/test_order_snapshot_contract.py @@ -0,0 +1,65 @@ +"""Offline contracts for private order snapshot subscriptions.""" + +import json +from unittest.mock import AsyncMock, Mock + +import pytest + +from polymarket_us.errors import WebSocketError +from polymarket_us.websocket.private import PrivateWebSocket +from polymarket_us.websocket.types import OrderSnapshot + +TERMINAL: OrderSnapshot = { + "requestId": "snapshot-1", + "subscriptionType": "SUBSCRIPTION_TYPE_ORDER_SNAPSHOT", + "orderSubscriptionSnapshot": {"orders": [], "eof": True}, +} + + +@pytest.fixture +def ws() -> PrivateWebSocket: + # Synthetic key from the public SDK tests; no live connection is made. + return PrivateWebSocket( + key_id="offline", secret_key="nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A=" + ) + + +async def test_live_and_snapshot_subscriptions_use_distinct_types(ws: PrivateWebSocket) -> None: + send = AsyncMock() + ws.send = send + await ws.subscribe_orders("live-1") + await ws.subscribe("snapshot-1", "SUBSCRIPTION_TYPE_ORDER_SNAPSHOT") + assert [call.args[0] for call in send.call_args_list] == [ + {"subscribe": {"requestId": "live-1", "subscriptionType": "SUBSCRIPTION_TYPE_ORDER"}}, + { + "subscribe": { + "requestId": "snapshot-1", + "subscriptionType": "SUBSCRIPTION_TYPE_ORDER_SNAPSHOT", + } + }, + ] + + +@pytest.mark.parametrize("failed", [False, True]) +def test_terminal_snapshot_dispatch(ws: PrivateWebSocket, failed: bool) -> None: + snapshot = Mock() + error = Mock() + message = Mock() + ws.on("order_snapshot", snapshot) + ws.on("error", error) + ws.on("message", message) + frame = dict(TERMINAL) + if failed: + frame["error"] = "deadline exceeded" + ws._handle_message(json.dumps(frame)) + message.assert_called_once_with(frame) + if failed: + snapshot.assert_not_called() + error.assert_called_once() + failure = error.call_args.args[0] + assert isinstance(failure, WebSocketError) + assert failure.request_id == "snapshot-1" + assert str(failure) == "deadline exceeded" + else: + snapshot.assert_called_once_with(frame) + error.assert_not_called() diff --git a/tests/types/order_snapshot.py b/tests/types/order_snapshot.py new file mode 100644 index 0000000..b9b70ca --- /dev/null +++ b/tests/types/order_snapshot.py @@ -0,0 +1,15 @@ +"""Consumer typing checks included in the existing mypy CI job.""" + +from polymarket_us.websocket.private import PrivateWebSocket +from polymarket_us.websocket.types import OrderSnapshot + + +async def request_snapshot(ws: PrivateWebSocket) -> None: + await ws.subscribe("snapshot-1", "SUBSCRIPTION_TYPE_ORDER_SNAPSHOT") + + +terminal: OrderSnapshot = { + "requestId": "snapshot-1", + "subscriptionType": "SUBSCRIPTION_TYPE_ORDER_SNAPSHOT", + "orderSubscriptionSnapshot": {"orders": [], "eof": True}, +}