From a77c32de64ce7423effff3b88f049b70752a9b64 Mon Sep 17 00:00:00 2001 From: Jeff West Date: Tue, 29 Sep 2026 06:16:14 -0500 Subject: [PATCH] chore(deps): bump cachetools to 7.2.0 and fix TTLCache typing Lockfile now resolves cachetools 7.2.0 (requires >=7.1.7). cachetools 7 ships stubs that type TTLCache.get() as V | None, so the instrument cache no longer needs a cast. Recreating order-tracking caches must use TTLCache[str, dict[str, Any]] and TTLCache[str, int] so restore loops do not mix value types. Refs #119 --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- src/project_x_py/client/cache.py | 11 ++++------- src/project_x_py/order_manager/tracking.py | 14 ++++++++------ tests/order_manager/test_order_tracking.py | 11 ++++++++++- uv.lock | 8 ++++---- 6 files changed, 33 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f093e306..8ef9f73d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Dependencies + +- Bump `cachetools` from 6.1.0 to 7.2.0 (requires `>=7.1.7`). Runtime + use remains `TTLCache` only; typing updated for cachetools 7 stubs + (#119). + ## [4.3.2] - 2026-09-26 ### Fixed diff --git a/pyproject.toml b/pyproject.toml index 972b2885..71d4099c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ dependencies = [ "pyyaml>=6.0.2", "orjson>=3.11.6", "lz4>=4.4.4", - "cachetools>=6.1.0", + "cachetools>=7.1.7", "deprecated>=1.2.18", ] diff --git a/src/project_x_py/client/cache.py b/src/project_x_py/client/cache.py index b51946fa..cf517ca5 100644 --- a/src/project_x_py/client/cache.py +++ b/src/project_x_py/client/cache.py @@ -19,7 +19,7 @@ import gc import io import logging -from typing import TYPE_CHECKING, Any, cast +from typing import Any import lz4.frame import polars as pl @@ -27,9 +27,6 @@ from project_x_py.models import Instrument -if TYPE_CHECKING: - pass - logger = logging.getLogger(__name__) @@ -84,8 +81,8 @@ def cache_ttl(self, value: float) -> None: """ self._cache_ttl = value # Recreate caches with new TTL - self._opt_instrument_cache = TTLCache(maxsize=1000, ttl=value) - self._opt_market_data_cache = TTLCache(maxsize=10000, ttl=value) + self._opt_instrument_cache = TTLCache[str, Instrument](maxsize=1000, ttl=value) + self._opt_market_data_cache = TTLCache[str, bytes](maxsize=10000, ttl=value) def _serialize_dataframe(self, df: pl.DataFrame) -> bytes: """ @@ -174,7 +171,7 @@ def get_cached_instrument(self, symbol: str) -> Instrument | None: Cached instrument or None if not found or expired. """ cache_key = symbol.upper() - instrument = cast(Instrument | None, self._opt_instrument_cache.get(cache_key)) + instrument = self._opt_instrument_cache.get(cache_key) if instrument: self.cache_hit_count += 1 return instrument diff --git a/src/project_x_py/order_manager/tracking.py b/src/project_x_py/order_manager/tracking.py index f79b249f..12dda394 100644 --- a/src/project_x_py/order_manager/tracking.py +++ b/src/project_x_py/order_manager/tracking.py @@ -1364,18 +1364,20 @@ async def configure_memory_limits( old_tracked = dict(self.tracked_orders) old_status = dict(self.order_status_cache) - self.tracked_orders = TTLCache( + self.tracked_orders = TTLCache[str, dict[str, Any]]( maxsize=max_tracked_orders, ttl=self._order_ttl_seconds ) - self.order_status_cache = TTLCache( + self.order_status_cache = TTLCache[str, int]( maxsize=max_tracked_orders, ttl=self._order_ttl_seconds ) # Restore data up to new limit - for key, value in list(old_tracked.items())[-max_tracked_orders:]: - self.tracked_orders[key] = value - for key, value in list(old_status.items())[-max_tracked_orders:]: - self.order_status_cache[key] = value + for order_id, order_data in list(old_tracked.items())[ + -max_tracked_orders: + ]: + self.tracked_orders[order_id] = order_data + for order_id, status in list(old_status.items())[-max_tracked_orders:]: + self.order_status_cache[order_id] = status if order_ttl_seconds is not None: self._order_ttl_seconds = order_ttl_seconds diff --git a/tests/order_manager/test_order_tracking.py b/tests/order_manager/test_order_tracking.py index c0fe5ef0..fbd503d3 100644 --- a/tests/order_manager/test_order_tracking.py +++ b/tests/order_manager/test_order_tracking.py @@ -717,8 +717,17 @@ async def test_configure_memory_limits(self, mock_order_manager): assert om._order_ttl_seconds == 1800 assert om._cleanup_interval == 150 - # Should have kept most recent 3 orders + # Should have kept most recent 3 orders with typed cache values intact + assert isinstance(om.tracked_orders, TTLCache) + assert isinstance(om.order_status_cache, TTLCache) + assert om.tracked_orders.maxsize == 3 + assert om.order_status_cache.maxsize == 3 assert len(om.tracked_orders) <= 3 + assert len(om.order_status_cache) <= 3 + assert all( + isinstance(order_data, dict) for order_data in om.tracked_orders.values() + ) + assert all(isinstance(status, int) for status in om.order_status_cache.values()) def test_clear_order_tracking(self, mock_order_manager): """Test clearing all tracking data.""" diff --git a/uv.lock b/uv.lock index 753ea5ce..bda7d79b 100644 --- a/uv.lock +++ b/uv.lock @@ -435,11 +435,11 @@ filecache = [ [[package]] name = "cachetools" -version = "6.1.0" +version = "7.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/89/817ad5d0411f136c484d535952aef74af9b25e0d99e90cdffbe121e6d628/cachetools-6.1.0.tar.gz", hash = "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587", size = 30714, upload-time = "2025-06-16T18:51:03.07Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/2c/3f18755527b03ca9ff6be724bd5370cb777c76a87f17301377cf04a4729b/cachetools-7.2.0.tar.gz", hash = "sha256:bcac1a1b8da6909994a2957238a57b8140dab7c5c5c69a43669654fe87a33c1d", size = 41129, upload-time = "2026-09-16T20:48:27.209Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/f0/2ef431fe4141f5e334759d73e81120492b23b2824336883a91ac04ba710b/cachetools-6.1.0-py3-none-any.whl", hash = "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e", size = 11189, upload-time = "2025-06-16T18:51:01.514Z" }, + { url = "https://files.pythonhosted.org/packages/cc/bb/1c6e7a89b11da19f137e4ef5a5c5fe47a5bb686449ba343944ccf8fb34b2/cachetools-7.2.0-py3-none-any.whl", hash = "sha256:3045213f186b89fdd95d94441354c4bd87c570b7a38d8c3fd1d9dd37f6dc90d8", size = 16918, upload-time = "2026-09-16T20:48:25.575Z" }, ] [[package]] @@ -2854,7 +2854,7 @@ requires-dist = [ { name = "aioresponses", marker = "extra == 'test'", specifier = ">=0.7.6" }, { name = "black", marker = "extra == 'all'", specifier = ">=26.3.1" }, { name = "black", marker = "extra == 'dev'", specifier = ">=26.3.1" }, - { name = "cachetools", specifier = ">=6.1.0" }, + { name = "cachetools", specifier = ">=7.1.7" }, { name = "deprecated", specifier = ">=1.2.18" }, { name = "httpx", extras = ["http2"], specifier = ">=0.27.0" }, { name = "isort", marker = "extra == 'all'", specifier = ">=5.12.0" },