polynode 0.10.4__tar.gz → 0.10.5__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 (47) hide show
  1. {polynode-0.10.4 → polynode-0.10.5}/PKG-INFO +27 -1
  2. {polynode-0.10.4 → polynode-0.10.5}/README.md +26 -0
  3. polynode-0.10.5/polynode/_version.py +1 -0
  4. {polynode-0.10.4 → polynode-0.10.5}/polynode/client.py +300 -0
  5. {polynode-0.10.4 → polynode-0.10.5}/polynode/engine.py +1 -0
  6. {polynode-0.10.4 → polynode-0.10.5}/polynode/orderbook_state.py +8 -2
  7. {polynode-0.10.4 → polynode-0.10.5}/pyproject.toml +1 -1
  8. polynode-0.10.5/tests/test_client.py +137 -0
  9. {polynode-0.10.4 → polynode-0.10.5}/tests/test_orderbook.py +15 -1
  10. polynode-0.10.4/polynode/_version.py +0 -1
  11. polynode-0.10.4/tests/test_client.py +0 -45
  12. {polynode-0.10.4 → polynode-0.10.5}/.gitignore +0 -0
  13. {polynode-0.10.4 → polynode-0.10.5}/polynode/__init__.py +0 -0
  14. {polynode-0.10.4 → polynode-0.10.5}/polynode/cache/__init__.py +0 -0
  15. {polynode-0.10.4 → polynode-0.10.5}/polynode/errors.py +0 -0
  16. {polynode-0.10.4 → polynode-0.10.5}/polynode/orderbook.py +0 -0
  17. {polynode-0.10.4 → polynode-0.10.5}/polynode/redemption_watcher.py +0 -0
  18. {polynode-0.10.4 → polynode-0.10.5}/polynode/short_form.py +0 -0
  19. {polynode-0.10.4 → polynode-0.10.5}/polynode/subscription.py +0 -0
  20. {polynode-0.10.4 → polynode-0.10.5}/polynode/testing.py +0 -0
  21. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/V2_ORDER_FLOW.md +0 -0
  22. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/__init__.py +0 -0
  23. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/clob_api.py +0 -0
  24. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/constants.py +0 -0
  25. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/cosigner.py +0 -0
  26. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/eip712.py +0 -0
  27. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/escrow.py +0 -0
  28. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/onboarding.py +0 -0
  29. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/position_management.py +0 -0
  30. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/privy.py +0 -0
  31. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/relayer.py +0 -0
  32. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/signer.py +0 -0
  33. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/sqlite_backend.py +0 -0
  34. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/trader.py +0 -0
  35. {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/types.py +0 -0
  36. {polynode-0.10.4 → polynode-0.10.5}/polynode/types/__init__.py +0 -0
  37. {polynode-0.10.4 → polynode-0.10.5}/polynode/types/enums.py +0 -0
  38. {polynode-0.10.4 → polynode-0.10.5}/polynode/types/events.py +0 -0
  39. {polynode-0.10.4 → polynode-0.10.5}/polynode/types/orderbook.py +0 -0
  40. {polynode-0.10.4 → polynode-0.10.5}/polynode/types/rest.py +0 -0
  41. {polynode-0.10.4 → polynode-0.10.5}/polynode/types/short_form.py +0 -0
  42. {polynode-0.10.4 → polynode-0.10.5}/polynode/types/ws.py +0 -0
  43. {polynode-0.10.4 → polynode-0.10.5}/polynode/ws.py +0 -0
  44. {polynode-0.10.4 → polynode-0.10.5}/tests/__init__.py +0 -0
  45. {polynode-0.10.4 → polynode-0.10.5}/tests/conftest.py +0 -0
  46. {polynode-0.10.4 → polynode-0.10.5}/tests/test_trading.py +0 -0
  47. {polynode-0.10.4 → polynode-0.10.5}/tests/test_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polynode
3
- Version: 0.10.4
3
+ Version: 0.10.5
4
4
  Summary: Python SDK for the PolyNode real-time prediction market data platform
5
5
  Project-URL: Homepage, https://polynode.dev
6
6
  Project-URL: Documentation, https://docs.polynode.dev
@@ -59,6 +59,32 @@ with PolyNode(api_key="pn_live_...") as pn:
59
59
  settlements = pn.recent_settlements(count=5)
60
60
  ```
61
61
 
62
+ ### Sports and Online Context
63
+
64
+ ```python
65
+ from polynode import PolyNode
66
+
67
+ with PolyNode(api_key="pn_live_...") as pn:
68
+ state = pn.sports_game_state(
69
+ "nba-cle-nyk-2026-05-31",
70
+ price_limit_tokens=20,
71
+ )
72
+
73
+ context = pn.sports_game_context(
74
+ "nba-cle-nyk-2026-05-31",
75
+ sources=["online"],
76
+ query_set="injuries",
77
+ max_queries=2,
78
+ max_per_query=5,
79
+ include_state=True,
80
+ )
81
+
82
+ web = pn.search_online(
83
+ "Cavaliers Knicks injury news",
84
+ max_results=5,
85
+ )
86
+ ```
87
+
62
88
  ### Async REST
63
89
 
64
90
  ```python
@@ -26,6 +26,32 @@ with PolyNode(api_key="pn_live_...") as pn:
26
26
  settlements = pn.recent_settlements(count=5)
27
27
  ```
28
28
 
29
+ ### Sports and Online Context
30
+
31
+ ```python
32
+ from polynode import PolyNode
33
+
34
+ with PolyNode(api_key="pn_live_...") as pn:
35
+ state = pn.sports_game_state(
36
+ "nba-cle-nyk-2026-05-31",
37
+ price_limit_tokens=20,
38
+ )
39
+
40
+ context = pn.sports_game_context(
41
+ "nba-cle-nyk-2026-05-31",
42
+ sources=["online"],
43
+ query_set="injuries",
44
+ max_queries=2,
45
+ max_per_query=5,
46
+ include_state=True,
47
+ )
48
+
49
+ web = pn.search_online(
50
+ "Cavaliers Knicks injury news",
51
+ max_results=5,
52
+ )
53
+ ```
54
+
29
55
  ### Async REST
30
56
 
31
57
  ```python
@@ -0,0 +1 @@
1
+ __version__ = "0.10.5"
@@ -32,6 +32,14 @@ from .types.rest import (
32
32
  from .types.enums import CandleResolution, MarketSortField
33
33
 
34
34
 
35
+ def _csv_param(value: Any) -> Any:
36
+ if value is None or isinstance(value, str):
37
+ return value
38
+ if isinstance(value, (list, tuple, set)):
39
+ return ",".join(str(item) for item in value)
40
+ return value
41
+
42
+
35
43
  class PolyNode:
36
44
  """Synchronous PolyNode REST client."""
37
45
 
@@ -187,6 +195,152 @@ class PolyNode:
187
195
  self._fetch("/v1/search", query={"q": query, "limit": limit, "include_inactive": include_inactive})
188
196
  )
189
197
 
198
+ def search_online(
199
+ self,
200
+ query: str,
201
+ *,
202
+ max_results: int | None = None,
203
+ engines: str | list[str] | tuple[str, ...] | set[str] | None = None,
204
+ language: str | None = None,
205
+ time_range: str | None = None,
206
+ safe_search: int | None = None,
207
+ cache_ttl_seconds: int | None = None,
208
+ ) -> dict:
209
+ return self._fetch(
210
+ "/v2/search/online",
211
+ query={
212
+ "q": query,
213
+ "max_results": max_results,
214
+ "engines": _csv_param(engines),
215
+ "language": language,
216
+ "time_range": time_range,
217
+ "safe_search": safe_search,
218
+ "cache_ttl_seconds": cache_ttl_seconds,
219
+ },
220
+ )
221
+
222
+ # ── Sports ──
223
+
224
+ def sports_leagues(self) -> dict:
225
+ return self._fetch("/v2/sports/leagues")
226
+
227
+ def sports_teams(self, league: str, *, limit: int | None = None, offset: int | None = None) -> dict:
228
+ return self._fetch(
229
+ f"/v2/sports/leagues/{quote(league, safe='')}/teams",
230
+ query={"limit": limit, "offset": offset},
231
+ )
232
+
233
+ def sports_games(
234
+ self,
235
+ league: str,
236
+ *,
237
+ status: str | None = None,
238
+ limit: int | None = None,
239
+ offset: int | None = None,
240
+ sort: str | None = None,
241
+ direction: str | None = None,
242
+ ) -> dict:
243
+ return self._fetch(
244
+ f"/v2/sports/leagues/{quote(league, safe='')}/games",
245
+ query={"status": status, "limit": limit, "offset": offset, "sort": sort, "direction": direction},
246
+ )
247
+
248
+ def sports_game(self, slug: str) -> dict:
249
+ return self._fetch(f"/v2/sports/games/{quote(slug, safe='')}")
250
+
251
+ def sports_game_state(
252
+ self,
253
+ slug: str,
254
+ *,
255
+ include_history: bool | None = None,
256
+ price_limit_tokens: int | None = None,
257
+ history_limit_tokens: int | None = None,
258
+ start_ts: int | str | None = None,
259
+ end_ts: int | str | None = None,
260
+ interval: str | None = None,
261
+ fidelity: int | None = None,
262
+ ) -> dict:
263
+ return self._fetch(
264
+ f"/v2/sports/games/{quote(slug, safe='')}/state",
265
+ query={
266
+ "include_history": include_history,
267
+ "price_limit_tokens": price_limit_tokens,
268
+ "history_limit_tokens": history_limit_tokens,
269
+ "start_ts": start_ts,
270
+ "end_ts": end_ts,
271
+ "interval": interval,
272
+ "fidelity": fidelity,
273
+ },
274
+ )
275
+
276
+ def sports_game_context(
277
+ self,
278
+ slug: str,
279
+ *,
280
+ sources: str | list[str] | tuple[str, ...] | set[str] | None = None,
281
+ query_set: str | None = None,
282
+ max_queries: int | None = None,
283
+ max_per_query: int | None = None,
284
+ include_state: bool | None = None,
285
+ price_limit_tokens: int | None = None,
286
+ ) -> dict:
287
+ return self._fetch(
288
+ f"/v2/sports/games/{quote(slug, safe='')}/context",
289
+ query={
290
+ "sources": _csv_param(sources),
291
+ "query_set": query_set,
292
+ "max_queries": max_queries,
293
+ "max_per_query": max_per_query,
294
+ "include_state": include_state,
295
+ "price_limit_tokens": price_limit_tokens,
296
+ },
297
+ )
298
+
299
+ def sports_game_prices(self, slug: str, *, limit_tokens: int | None = None) -> dict:
300
+ return self._fetch(
301
+ f"/v2/sports/games/{quote(slug, safe='')}/prices",
302
+ query={"limit_tokens": limit_tokens},
303
+ )
304
+
305
+ def sports_game_history(
306
+ self,
307
+ slug: str,
308
+ *,
309
+ start_ts: int | str | None = None,
310
+ end_ts: int | str | None = None,
311
+ interval: str | None = None,
312
+ fidelity: int | None = None,
313
+ limit_tokens: int | None = None,
314
+ ) -> dict:
315
+ return self._fetch(
316
+ f"/v2/sports/games/{quote(slug, safe='')}/history",
317
+ query={
318
+ "start_ts": start_ts,
319
+ "end_ts": end_ts,
320
+ "interval": interval,
321
+ "fidelity": fidelity,
322
+ "limit_tokens": limit_tokens,
323
+ },
324
+ )
325
+
326
+ def sports_market_types(self) -> dict:
327
+ return self._fetch("/v2/sports/market-types")
328
+
329
+ def sports_series(self, *, status: str | None = None, limit: int | None = None) -> dict:
330
+ return self._fetch("/v2/sports/series", query={"status": status, "limit": limit})
331
+
332
+ def sports_tournaments(self) -> dict:
333
+ return self._fetch("/v2/sports/tournaments")
334
+
335
+ def sports_live(self) -> dict:
336
+ return self._fetch("/v2/sports/live")
337
+
338
+ def sports_summary(self) -> dict:
339
+ return self._fetch("/v2/sports/summary")
340
+
341
+ def sports_search(self, query: str, *, limit: int | None = None) -> dict:
342
+ return self._fetch("/v2/sports/search", query={"q": query, "limit": limit})
343
+
190
344
  # ── Pricing ──
191
345
 
192
346
  def candles(
@@ -490,6 +644,152 @@ class AsyncPolyNode:
490
644
  await self._fetch("/v1/search", query={"q": query, "limit": limit, "include_inactive": include_inactive})
491
645
  )
492
646
 
647
+ async def search_online(
648
+ self,
649
+ query: str,
650
+ *,
651
+ max_results: int | None = None,
652
+ engines: str | list[str] | tuple[str, ...] | set[str] | None = None,
653
+ language: str | None = None,
654
+ time_range: str | None = None,
655
+ safe_search: int | None = None,
656
+ cache_ttl_seconds: int | None = None,
657
+ ) -> dict:
658
+ return await self._fetch(
659
+ "/v2/search/online",
660
+ query={
661
+ "q": query,
662
+ "max_results": max_results,
663
+ "engines": _csv_param(engines),
664
+ "language": language,
665
+ "time_range": time_range,
666
+ "safe_search": safe_search,
667
+ "cache_ttl_seconds": cache_ttl_seconds,
668
+ },
669
+ )
670
+
671
+ # ── Sports ──
672
+
673
+ async def sports_leagues(self) -> dict:
674
+ return await self._fetch("/v2/sports/leagues")
675
+
676
+ async def sports_teams(self, league: str, *, limit: int | None = None, offset: int | None = None) -> dict:
677
+ return await self._fetch(
678
+ f"/v2/sports/leagues/{quote(league, safe='')}/teams",
679
+ query={"limit": limit, "offset": offset},
680
+ )
681
+
682
+ async def sports_games(
683
+ self,
684
+ league: str,
685
+ *,
686
+ status: str | None = None,
687
+ limit: int | None = None,
688
+ offset: int | None = None,
689
+ sort: str | None = None,
690
+ direction: str | None = None,
691
+ ) -> dict:
692
+ return await self._fetch(
693
+ f"/v2/sports/leagues/{quote(league, safe='')}/games",
694
+ query={"status": status, "limit": limit, "offset": offset, "sort": sort, "direction": direction},
695
+ )
696
+
697
+ async def sports_game(self, slug: str) -> dict:
698
+ return await self._fetch(f"/v2/sports/games/{quote(slug, safe='')}")
699
+
700
+ async def sports_game_state(
701
+ self,
702
+ slug: str,
703
+ *,
704
+ include_history: bool | None = None,
705
+ price_limit_tokens: int | None = None,
706
+ history_limit_tokens: int | None = None,
707
+ start_ts: int | str | None = None,
708
+ end_ts: int | str | None = None,
709
+ interval: str | None = None,
710
+ fidelity: int | None = None,
711
+ ) -> dict:
712
+ return await self._fetch(
713
+ f"/v2/sports/games/{quote(slug, safe='')}/state",
714
+ query={
715
+ "include_history": include_history,
716
+ "price_limit_tokens": price_limit_tokens,
717
+ "history_limit_tokens": history_limit_tokens,
718
+ "start_ts": start_ts,
719
+ "end_ts": end_ts,
720
+ "interval": interval,
721
+ "fidelity": fidelity,
722
+ },
723
+ )
724
+
725
+ async def sports_game_context(
726
+ self,
727
+ slug: str,
728
+ *,
729
+ sources: str | list[str] | tuple[str, ...] | set[str] | None = None,
730
+ query_set: str | None = None,
731
+ max_queries: int | None = None,
732
+ max_per_query: int | None = None,
733
+ include_state: bool | None = None,
734
+ price_limit_tokens: int | None = None,
735
+ ) -> dict:
736
+ return await self._fetch(
737
+ f"/v2/sports/games/{quote(slug, safe='')}/context",
738
+ query={
739
+ "sources": _csv_param(sources),
740
+ "query_set": query_set,
741
+ "max_queries": max_queries,
742
+ "max_per_query": max_per_query,
743
+ "include_state": include_state,
744
+ "price_limit_tokens": price_limit_tokens,
745
+ },
746
+ )
747
+
748
+ async def sports_game_prices(self, slug: str, *, limit_tokens: int | None = None) -> dict:
749
+ return await self._fetch(
750
+ f"/v2/sports/games/{quote(slug, safe='')}/prices",
751
+ query={"limit_tokens": limit_tokens},
752
+ )
753
+
754
+ async def sports_game_history(
755
+ self,
756
+ slug: str,
757
+ *,
758
+ start_ts: int | str | None = None,
759
+ end_ts: int | str | None = None,
760
+ interval: str | None = None,
761
+ fidelity: int | None = None,
762
+ limit_tokens: int | None = None,
763
+ ) -> dict:
764
+ return await self._fetch(
765
+ f"/v2/sports/games/{quote(slug, safe='')}/history",
766
+ query={
767
+ "start_ts": start_ts,
768
+ "end_ts": end_ts,
769
+ "interval": interval,
770
+ "fidelity": fidelity,
771
+ "limit_tokens": limit_tokens,
772
+ },
773
+ )
774
+
775
+ async def sports_market_types(self) -> dict:
776
+ return await self._fetch("/v2/sports/market-types")
777
+
778
+ async def sports_series(self, *, status: str | None = None, limit: int | None = None) -> dict:
779
+ return await self._fetch("/v2/sports/series", query={"status": status, "limit": limit})
780
+
781
+ async def sports_tournaments(self) -> dict:
782
+ return await self._fetch("/v2/sports/tournaments")
783
+
784
+ async def sports_live(self) -> dict:
785
+ return await self._fetch("/v2/sports/live")
786
+
787
+ async def sports_summary(self) -> dict:
788
+ return await self._fetch("/v2/sports/summary")
789
+
790
+ async def sports_search(self, query: str, *, limit: int | None = None) -> dict:
791
+ return await self._fetch("/v2/sports/search", query={"q": query, "limit": limit})
792
+
493
793
  # ── Pricing ──
494
794
 
495
795
  async def candles(
@@ -120,6 +120,7 @@ class OrderbookEngine:
120
120
  self._route_update(update)
121
121
 
122
122
  def _on_price(self, change: PriceChange) -> None:
123
+ self._state.apply_price_change(change)
123
124
  for h in self._handlers["price"]:
124
125
  h(change)
125
126
  for view in self._views:
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from .types.orderbook import BookSnapshot, BookUpdate, OrderbookLevel, PriceChange
5
+ from .types.orderbook import BookSnapshot, BookUpdate, OrderbookLevel, PriceChange, PriceChangeAsset
6
6
 
7
7
 
8
8
  class LocalOrderbook:
@@ -10,6 +10,7 @@ class LocalOrderbook:
10
10
 
11
11
  def __init__(self) -> None:
12
12
  self._books: dict[str, tuple[list[OrderbookLevel], list[OrderbookLevel]]] = {}
13
+ self._last_prices: dict[str, PriceChangeAsset] = {}
13
14
 
14
15
  def apply_snapshot(self, snap: BookSnapshot) -> None:
15
16
  bids = sorted(list(snap.bids), key=lambda l: float(l.price), reverse=True)
@@ -25,11 +26,15 @@ class LocalOrderbook:
25
26
  self._books[update.asset_id] = (new_bids, new_asks)
26
27
 
27
28
  def apply_price_change(self, change: PriceChange) -> None:
28
- pass
29
+ for asset in change.assets:
30
+ self._last_prices[asset.asset_id] = asset
29
31
 
30
32
  def get_book(self, asset_id: str) -> tuple[list[OrderbookLevel], list[OrderbookLevel]] | None:
31
33
  return self._books.get(asset_id)
32
34
 
35
+ def get_last_price(self, asset_id: str) -> PriceChangeAsset | None:
36
+ return self._last_prices.get(asset_id)
37
+
33
38
  def get_best_bid(self, asset_id: str) -> OrderbookLevel | None:
34
39
  book = self._books.get(asset_id)
35
40
  if book and book[0]:
@@ -55,6 +60,7 @@ class LocalOrderbook:
55
60
 
56
61
  def clear(self) -> None:
57
62
  self._books.clear()
63
+ self._last_prices.clear()
58
64
 
59
65
 
60
66
  def _apply_deltas(
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "polynode"
7
- version = "0.10.4"
7
+ version = "0.10.5"
8
8
  description = "Python SDK for the PolyNode real-time prediction market data platform"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,137 @@
1
+ """Tests for the sync REST client."""
2
+
3
+ import pytest
4
+ from polynode import PolyNode
5
+ from polynode.types.rest import StatusResponse, MarketsResponse, SettlementsResponse
6
+
7
+ TEST_KEY = "pn_live_test_session_tracking_51eca107e9b347b589f5b0a04f98eb1d"
8
+
9
+
10
+ @pytest.fixture
11
+ def pn():
12
+ client = PolyNode(api_key=TEST_KEY)
13
+ yield client
14
+ client.close()
15
+
16
+
17
+ def test_healthz(pn):
18
+ result = pn.healthz()
19
+ assert result == "ok" or "ok" in str(result).lower()
20
+
21
+
22
+ def test_status(pn):
23
+ result = pn.status()
24
+ assert isinstance(result, StatusResponse)
25
+ assert result.uptime_seconds > 0
26
+ assert result.state.market_count > 0
27
+
28
+
29
+ def test_markets(pn):
30
+ result = pn.markets(count=3)
31
+ assert isinstance(result, MarketsResponse)
32
+ assert result.count > 0
33
+ assert len(result.markets) <= 3
34
+
35
+
36
+ def test_recent_settlements(pn):
37
+ result = pn.recent_settlements(count=1)
38
+ assert isinstance(result, SettlementsResponse)
39
+ assert result.count >= 0
40
+
41
+
42
+ def test_search(pn):
43
+ result = pn.search("election", limit=3)
44
+ assert result.query == "election"
45
+ assert result.count >= 0
46
+
47
+
48
+ def test_search_online_builds_expected_path():
49
+ client = PolyNode(api_key=TEST_KEY)
50
+ calls = []
51
+
52
+ def fake_fetch(path, **kwargs):
53
+ calls.append((path, kwargs))
54
+ return {"ok": True}
55
+
56
+ client._fetch = fake_fetch # type: ignore[method-assign]
57
+ try:
58
+ assert client.search_online(
59
+ "Cavaliers Knicks injury news",
60
+ max_results=5,
61
+ engines=["brave", "bing"],
62
+ language="en-US",
63
+ time_range="day",
64
+ safe_search=0,
65
+ cache_ttl_seconds=300,
66
+ )["ok"]
67
+ finally:
68
+ client.close()
69
+
70
+ assert calls[0][0] == "/v2/search/online"
71
+ assert calls[0][1]["query"] == {
72
+ "q": "Cavaliers Knicks injury news",
73
+ "max_results": 5,
74
+ "engines": "brave,bing",
75
+ "language": "en-US",
76
+ "time_range": "day",
77
+ "safe_search": 0,
78
+ "cache_ttl_seconds": 300,
79
+ }
80
+
81
+
82
+ def test_sports_methods_build_expected_paths():
83
+ client = PolyNode(api_key=TEST_KEY)
84
+ calls = []
85
+
86
+ def fake_fetch(path, **kwargs):
87
+ calls.append((path, kwargs))
88
+ return {"ok": True}
89
+
90
+ client._fetch = fake_fetch # type: ignore[method-assign]
91
+ try:
92
+ assert client.sports_leagues()["ok"]
93
+ assert client.sports_games("nba", status="active", limit=2)["ok"]
94
+ assert client.sports_game("nba-nyk-cle-2026-05-25")["ok"]
95
+ assert client.sports_game_state(
96
+ "nba-nyk-cle-2026-05-25",
97
+ include_history=True,
98
+ price_limit_tokens=4,
99
+ history_limit_tokens=2,
100
+ interval="1d",
101
+ )["ok"]
102
+ assert client.sports_game_context(
103
+ "nba-nyk-cle-2026-05-25",
104
+ sources=["x", "online"],
105
+ query_set="injuries",
106
+ max_queries=2,
107
+ max_per_query=3,
108
+ include_state=True,
109
+ price_limit_tokens=4,
110
+ )["ok"]
111
+ assert client.sports_game_prices("nba-nyk-cle-2026-05-25", limit_tokens=4)["ok"]
112
+ assert client.sports_game_history("nba-nyk-cle-2026-05-25", interval="1d", limit_tokens=2)["ok"]
113
+ finally:
114
+ client.close()
115
+
116
+ assert calls[0][0] == "/v2/sports/leagues"
117
+ assert calls[1][0] == "/v2/sports/leagues/nba/games"
118
+ assert calls[1][1]["query"] == {"status": "active", "limit": 2, "offset": None, "sort": None, "direction": None}
119
+ assert calls[2][0] == "/v2/sports/games/nba-nyk-cle-2026-05-25"
120
+ assert calls[3][0] == "/v2/sports/games/nba-nyk-cle-2026-05-25/state"
121
+ assert calls[3][1]["query"]["include_history"] is True
122
+ assert calls[3][1]["query"]["price_limit_tokens"] == 4
123
+ assert calls[3][1]["query"]["history_limit_tokens"] == 2
124
+ assert calls[4][0] == "/v2/sports/games/nba-nyk-cle-2026-05-25/context"
125
+ assert calls[4][1]["query"] == {
126
+ "sources": "x,online",
127
+ "query_set": "injuries",
128
+ "max_queries": 2,
129
+ "max_per_query": 3,
130
+ "include_state": True,
131
+ "price_limit_tokens": 4,
132
+ }
133
+ assert calls[5][0] == "/v2/sports/games/nba-nyk-cle-2026-05-25/prices"
134
+ assert calls[5][1]["query"] == {"limit_tokens": 4}
135
+ assert calls[6][0] == "/v2/sports/games/nba-nyk-cle-2026-05-25/history"
136
+ assert calls[6][1]["query"]["interval"] == "1d"
137
+ assert calls[6][1]["query"]["limit_tokens"] == 2
@@ -1,7 +1,7 @@
1
1
  """Tests for LocalOrderbook state management."""
2
2
 
3
3
  from polynode.orderbook_state import LocalOrderbook
4
- from polynode.types.orderbook import BookSnapshot, BookUpdate, OrderbookLevel
4
+ from polynode.types.orderbook import BookSnapshot, BookUpdate, OrderbookLevel, PriceChange, PriceChangeAsset
5
5
 
6
6
 
7
7
  def test_apply_snapshot():
@@ -57,3 +57,17 @@ def test_spread():
57
57
  spread = ob.get_spread("t1")
58
58
  assert spread is not None
59
59
  assert abs(spread - 0.10) < 0.001
60
+
61
+
62
+ def test_apply_price_change_tracks_last_price():
63
+ ob = LocalOrderbook()
64
+ change = PriceChange(
65
+ market="m1",
66
+ assets=[PriceChangeAsset(asset_id="t1", price="0.57", outcome="Yes")],
67
+ )
68
+ ob.apply_price_change(change)
69
+
70
+ last = ob.get_last_price("t1")
71
+ assert last is not None
72
+ assert last.price == "0.57"
73
+ assert last.outcome == "Yes"
@@ -1 +0,0 @@
1
- __version__ = "0.9.1"
@@ -1,45 +0,0 @@
1
- """Tests for the sync REST client."""
2
-
3
- import pytest
4
- from polynode import PolyNode
5
- from polynode.types.rest import StatusResponse, MarketsResponse, SettlementsResponse
6
-
7
- TEST_KEY = "pn_live_test_session_tracking_51eca107e9b347b589f5b0a04f98eb1d"
8
-
9
-
10
- @pytest.fixture
11
- def pn():
12
- client = PolyNode(api_key=TEST_KEY)
13
- yield client
14
- client.close()
15
-
16
-
17
- def test_healthz(pn):
18
- result = pn.healthz()
19
- assert result == "ok" or "ok" in str(result).lower()
20
-
21
-
22
- def test_status(pn):
23
- result = pn.status()
24
- assert isinstance(result, StatusResponse)
25
- assert result.uptime_seconds > 0
26
- assert result.state.market_count > 0
27
-
28
-
29
- def test_markets(pn):
30
- result = pn.markets(count=3)
31
- assert isinstance(result, MarketsResponse)
32
- assert result.count > 0
33
- assert len(result.markets) <= 3
34
-
35
-
36
- def test_recent_settlements(pn):
37
- result = pn.recent_settlements(count=1)
38
- assert isinstance(result, SettlementsResponse)
39
- assert result.count >= 0
40
-
41
-
42
- def test_search(pn):
43
- result = pn.search("election", limit=3)
44
- assert result.query == "election"
45
- assert result.count >= 0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes