diff --git a/pykalshi/__init__.py b/pykalshi/__init__.py index a5bb8bc..59c2244 100644 --- a/pykalshi/__init__.py +++ b/pykalshi/__init__.py @@ -4,7 +4,7 @@ A clean, modular interface for the Kalshi trading API. """ -__version__ = "1.0.5" +__version__ = "1.0.6" import logging diff --git a/pykalshi/_async/api_keys.py b/pykalshi/_async/api_keys.py index 4766967..aa7abaa 100644 --- a/pykalshi/_async/api_keys.py +++ b/pykalshi/_async/api_keys.py @@ -17,7 +17,7 @@ def __init__(self, client: AsyncKalshiClient) -> None: async def list(self) -> list[APIKey]: """List all API keys for this account.""" data = await self._client.get("/api_keys") - return [APIKey.model_validate(k) for k in data.get("api_keys", [])] + return [APIKey.model_validate(k) for k in (data.get("api_keys") or [])] async def create(self, public_key: str, name: str | None = None) -> str: """Create an API key with a provided RSA public key. diff --git a/pykalshi/_async/client.py b/pykalshi/_async/client.py index 4d3f050..3f41bc9 100644 --- a/pykalshi/_async/client.py +++ b/pykalshi/_async/client.py @@ -156,7 +156,7 @@ async def paginated_get( filtered = {k: v for k, v in params.items() if v is not None} endpoint = f"{path}?{urlencode(filtered)}" if filtered else path response = await self.get(endpoint) - all_items.extend(response.get(response_key, [])) + all_items.extend(response.get(response_key) or []) cursor = response.get("cursor", "") if not fetch_all or not cursor: break @@ -409,5 +409,5 @@ async def get_candlesticks_batch( response = await self.get(f"/markets/candlesticks?{query}") return { item["market_ticker"]: CandlestickResponse.model_validate(item) - for item in response.get("markets", []) + for item in (response.get("markets") or []) } diff --git a/pykalshi/_async/exchange.py b/pykalshi/_async/exchange.py index 5e9954e..52e6614 100644 --- a/pykalshi/_async/exchange.py +++ b/pykalshi/_async/exchange.py @@ -39,7 +39,7 @@ async def get_schedule(self) -> dict[str, Any]: async def get_announcements(self) -> list[Announcement]: """Get exchange-wide announcements.""" data = await self._client.get("/exchange/announcements") - return [Announcement.model_validate(a) for a in data.get("announcements", [])] + return [Announcement.model_validate(a) for a in (data.get("announcements") or [])] async def get_user_data_timestamp(self) -> int: """Get timestamp of last user data validation (Unix ms).""" diff --git a/pykalshi/_async/history.py b/pykalshi/_async/history.py index 71b75ff..bd16865 100644 --- a/pykalshi/_async/history.py +++ b/pykalshi/_async/history.py @@ -92,7 +92,7 @@ async def get_candlesticks( ) return [ HistoricalCandlestick.model_validate(c) - for c in response.get("candlesticks", []) + for c in (response.get("candlesticks") or []) ] async def get_fills( diff --git a/pykalshi/_async/portfolio.py b/pykalshi/_async/portfolio.py index 0229b1f..f58bbc1 100644 --- a/pykalshi/_async/portfolio.py +++ b/pykalshi/_async/portfolio.py @@ -317,7 +317,7 @@ async def batch_place_orders(self, orders: list[dict]) -> DataFrameList[AsyncOrd prepared = self._build_batch_orders(orders) response = await self._client.post("/portfolio/orders/batched", {"orders": prepared}) result = [] - for item in response.get("orders", []): + for item in (response.get("orders") or []): order_data = item.get("order") if order_data is None: continue @@ -336,7 +336,7 @@ async def batch_cancel_orders(self, order_ids: list[str]) -> DataFrameList[Async orders = [{"order_id": oid} for oid in order_ids] response = await self._client.delete("/portfolio/orders/batched", {"orders": orders}) result = [] - for item in response.get("orders", []): + for item in (response.get("orders") or []): order_data = item.get("order") if order_data is None: continue @@ -373,7 +373,7 @@ async def get_queue_positions( response = await self._client.get(endpoint) return DataFrameList( QueuePositionModel.model_validate(qp) - for qp in response.get("queue_positions", []) + for qp in (response.get("queue_positions") or []) ) # --- Settlements --- @@ -438,7 +438,7 @@ async def get_order_groups(self) -> DataFrameList[OrderGroupModel]: response = await self._client.get("/portfolio/order_groups") return DataFrameList( OrderGroupModel.model_validate(og) - for og in response.get("order_groups", []) + for og in (response.get("order_groups") or []) ) async def reset_order_group(self, order_group_id: str) -> None: @@ -488,7 +488,7 @@ async def get_subaccount_balances(self) -> DataFrameList[SubaccountBalanceModel] response = await self._client.get("/portfolio/subaccounts/balances") return DataFrameList( SubaccountBalanceModel.model_validate(b) - for b in response.get("balances", []) + for b in (response.get("balances") or []) ) async def get_subaccount_transfers( diff --git a/pykalshi/_sync/api_keys.py b/pykalshi/_sync/api_keys.py index 9fbd506..34e4dc8 100644 --- a/pykalshi/_sync/api_keys.py +++ b/pykalshi/_sync/api_keys.py @@ -19,7 +19,7 @@ def __init__(self, client: KalshiClient) -> None: def list(self) -> list[APIKey]: """List all API keys for this account.""" data = self._client.get("/api_keys") - return [APIKey.model_validate(k) for k in data.get("api_keys", [])] + return [APIKey.model_validate(k) for k in (data.get("api_keys") or [])] def create(self, public_key: str, name: str | None = None) -> str: """Create an API key with a provided RSA public key. diff --git a/pykalshi/_sync/client.py b/pykalshi/_sync/client.py index 6554b8f..baec493 100644 --- a/pykalshi/_sync/client.py +++ b/pykalshi/_sync/client.py @@ -158,7 +158,7 @@ def paginated_get( filtered = {k: v for k, v in params.items() if v is not None} endpoint = f"{path}?{urlencode(filtered)}" if filtered else path response = self.get(endpoint) - all_items.extend(response.get(response_key, [])) + all_items.extend(response.get(response_key) or []) cursor = response.get("cursor", "") if not fetch_all or not cursor: break @@ -411,5 +411,5 @@ def get_candlesticks_batch( response = self.get(f"/markets/candlesticks?{query}") return { item["market_ticker"]: CandlestickResponse.model_validate(item) - for item in response.get("markets", []) + for item in (response.get("markets") or []) } diff --git a/pykalshi/_sync/exchange.py b/pykalshi/_sync/exchange.py index a7bb789..593816d 100644 --- a/pykalshi/_sync/exchange.py +++ b/pykalshi/_sync/exchange.py @@ -41,7 +41,7 @@ def get_schedule(self) -> dict[str, Any]: def get_announcements(self) -> list[Announcement]: """Get exchange-wide announcements.""" data = self._client.get("/exchange/announcements") - return [Announcement.model_validate(a) for a in data.get("announcements", [])] + return [Announcement.model_validate(a) for a in (data.get("announcements") or [])] def get_user_data_timestamp(self) -> int: """Get timestamp of last user data validation (Unix ms).""" diff --git a/pykalshi/_sync/history.py b/pykalshi/_sync/history.py index 99497fd..3ecc26d 100644 --- a/pykalshi/_sync/history.py +++ b/pykalshi/_sync/history.py @@ -94,7 +94,7 @@ def get_candlesticks( ) return [ HistoricalCandlestick.model_validate(c) - for c in response.get("candlesticks", []) + for c in (response.get("candlesticks") or []) ] def get_fills( diff --git a/pykalshi/_sync/portfolio.py b/pykalshi/_sync/portfolio.py index 74d4bf4..9825c08 100644 --- a/pykalshi/_sync/portfolio.py +++ b/pykalshi/_sync/portfolio.py @@ -319,7 +319,7 @@ def batch_place_orders(self, orders: list[dict]) -> DataFrameList[Order]: prepared = self._build_batch_orders(orders) response = self._client.post("/portfolio/orders/batched", {"orders": prepared}) result = [] - for item in response.get("orders", []): + for item in (response.get("orders") or []): order_data = item.get("order") if order_data is None: continue @@ -338,7 +338,7 @@ def batch_cancel_orders(self, order_ids: list[str]) -> DataFrameList[Order]: orders = [{"order_id": oid} for oid in order_ids] response = self._client.delete("/portfolio/orders/batched", {"orders": orders}) result = [] - for item in response.get("orders", []): + for item in (response.get("orders") or []): order_data = item.get("order") if order_data is None: continue @@ -375,7 +375,7 @@ def get_queue_positions( response = self._client.get(endpoint) return DataFrameList( QueuePositionModel.model_validate(qp) - for qp in response.get("queue_positions", []) + for qp in (response.get("queue_positions") or []) ) # --- Settlements --- @@ -440,7 +440,7 @@ def get_order_groups(self) -> DataFrameList[OrderGroupModel]: response = self._client.get("/portfolio/order_groups") return DataFrameList( OrderGroupModel.model_validate(og) - for og in response.get("order_groups", []) + for og in (response.get("order_groups") or []) ) def reset_order_group(self, order_group_id: str) -> None: @@ -490,7 +490,7 @@ def get_subaccount_balances(self) -> DataFrameList[SubaccountBalanceModel]: response = self._client.get("/portfolio/subaccounts/balances") return DataFrameList( SubaccountBalanceModel.model_validate(b) - for b in response.get("balances", []) + for b in (response.get("balances") or []) ) def get_subaccount_transfers( diff --git a/pyproject.toml b/pyproject.toml index f1153ff..b2aaef5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pykalshi" -version = "1.0.5" +version = "1.0.6" description = "A typed Python client for the Kalshi prediction markets API with WebSocket streaming, automatic retries, and ergonomic interfaces" readme = "README.md" license = "MIT" diff --git a/tests/integration/test_portfolio.py b/tests/integration/test_portfolio.py index 8a29b9c..3bf0ccc 100644 --- a/tests/integration/test_portfolio.py +++ b/tests/integration/test_portfolio.py @@ -531,8 +531,11 @@ def test_get_queue_position(self, client, market_for_orders): assert isinstance(queue_pos.queue_position_fp, str) assert float(queue_pos.queue_position_fp) >= 0 - # Cleanup - order.cancel() + # Cleanup (order may already be filled/gone) + try: + order.cancel() + except ResourceNotFoundError: + pass def test_get_queue_positions_multiple(self, client, market_for_orders): """Get queue positions for all resting orders (filtered by market).""" diff --git a/uv.lock b/uv.lock index 5bfd01c..899df89 100644 --- a/uv.lock +++ b/uv.lock @@ -1296,7 +1296,7 @@ wheels = [ [[package]] name = "pykalshi" -version = "0.3.7" +version = "1.0.6" source = { editable = "." } dependencies = [ { name = "cryptography" }, @@ -1341,7 +1341,7 @@ requires-dist = [ { name = "pydantic", specifier = ">=2.0.0" }, { name = "pykalshi", extras = ["dataframe"], marker = "extra == 'dev'" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0" }, - { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21.0" }, + { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=1.0.0" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.0" }, { name = "pytest-mock", marker = "extra == 'dev'", specifier = ">=3.0.0" }, { name = "python-dotenv", specifier = ">=1.0.0" },