mcli-framework 7.0.0__py3-none-any.whl
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.
Potentially problematic release.
This version of mcli-framework might be problematic. Click here for more details.
- mcli/app/chat_cmd.py +42 -0
- mcli/app/commands_cmd.py +226 -0
- mcli/app/completion_cmd.py +216 -0
- mcli/app/completion_helpers.py +288 -0
- mcli/app/cron_test_cmd.py +697 -0
- mcli/app/logs_cmd.py +419 -0
- mcli/app/main.py +492 -0
- mcli/app/model/model.py +1060 -0
- mcli/app/model_cmd.py +227 -0
- mcli/app/redis_cmd.py +269 -0
- mcli/app/video/video.py +1114 -0
- mcli/app/visual_cmd.py +303 -0
- mcli/chat/chat.py +2409 -0
- mcli/chat/command_rag.py +514 -0
- mcli/chat/enhanced_chat.py +652 -0
- mcli/chat/system_controller.py +1010 -0
- mcli/chat/system_integration.py +1016 -0
- mcli/cli.py +25 -0
- mcli/config.toml +20 -0
- mcli/lib/api/api.py +586 -0
- mcli/lib/api/daemon_client.py +203 -0
- mcli/lib/api/daemon_client_local.py +44 -0
- mcli/lib/api/daemon_decorator.py +217 -0
- mcli/lib/api/mcli_decorators.py +1032 -0
- mcli/lib/auth/auth.py +85 -0
- mcli/lib/auth/aws_manager.py +85 -0
- mcli/lib/auth/azure_manager.py +91 -0
- mcli/lib/auth/credential_manager.py +192 -0
- mcli/lib/auth/gcp_manager.py +93 -0
- mcli/lib/auth/key_manager.py +117 -0
- mcli/lib/auth/mcli_manager.py +93 -0
- mcli/lib/auth/token_manager.py +75 -0
- mcli/lib/auth/token_util.py +1011 -0
- mcli/lib/config/config.py +47 -0
- mcli/lib/discovery/__init__.py +1 -0
- mcli/lib/discovery/command_discovery.py +274 -0
- mcli/lib/erd/erd.py +1345 -0
- mcli/lib/erd/generate_graph.py +453 -0
- mcli/lib/files/files.py +76 -0
- mcli/lib/fs/fs.py +109 -0
- mcli/lib/lib.py +29 -0
- mcli/lib/logger/logger.py +611 -0
- mcli/lib/performance/optimizer.py +409 -0
- mcli/lib/performance/rust_bridge.py +502 -0
- mcli/lib/performance/uvloop_config.py +154 -0
- mcli/lib/pickles/pickles.py +50 -0
- mcli/lib/search/cached_vectorizer.py +479 -0
- mcli/lib/services/data_pipeline.py +460 -0
- mcli/lib/services/lsh_client.py +441 -0
- mcli/lib/services/redis_service.py +387 -0
- mcli/lib/shell/shell.py +137 -0
- mcli/lib/toml/toml.py +33 -0
- mcli/lib/ui/styling.py +47 -0
- mcli/lib/ui/visual_effects.py +634 -0
- mcli/lib/watcher/watcher.py +185 -0
- mcli/ml/api/app.py +215 -0
- mcli/ml/api/middleware.py +224 -0
- mcli/ml/api/routers/admin_router.py +12 -0
- mcli/ml/api/routers/auth_router.py +244 -0
- mcli/ml/api/routers/backtest_router.py +12 -0
- mcli/ml/api/routers/data_router.py +12 -0
- mcli/ml/api/routers/model_router.py +302 -0
- mcli/ml/api/routers/monitoring_router.py +12 -0
- mcli/ml/api/routers/portfolio_router.py +12 -0
- mcli/ml/api/routers/prediction_router.py +267 -0
- mcli/ml/api/routers/trade_router.py +12 -0
- mcli/ml/api/routers/websocket_router.py +76 -0
- mcli/ml/api/schemas.py +64 -0
- mcli/ml/auth/auth_manager.py +425 -0
- mcli/ml/auth/models.py +154 -0
- mcli/ml/auth/permissions.py +302 -0
- mcli/ml/backtesting/backtest_engine.py +502 -0
- mcli/ml/backtesting/performance_metrics.py +393 -0
- mcli/ml/cache.py +400 -0
- mcli/ml/cli/main.py +398 -0
- mcli/ml/config/settings.py +394 -0
- mcli/ml/configs/dvc_config.py +230 -0
- mcli/ml/configs/mlflow_config.py +131 -0
- mcli/ml/configs/mlops_manager.py +293 -0
- mcli/ml/dashboard/app.py +532 -0
- mcli/ml/dashboard/app_integrated.py +738 -0
- mcli/ml/dashboard/app_supabase.py +560 -0
- mcli/ml/dashboard/app_training.py +615 -0
- mcli/ml/dashboard/cli.py +51 -0
- mcli/ml/data_ingestion/api_connectors.py +501 -0
- mcli/ml/data_ingestion/data_pipeline.py +567 -0
- mcli/ml/data_ingestion/stream_processor.py +512 -0
- mcli/ml/database/migrations/env.py +94 -0
- mcli/ml/database/models.py +667 -0
- mcli/ml/database/session.py +200 -0
- mcli/ml/experimentation/ab_testing.py +845 -0
- mcli/ml/features/ensemble_features.py +607 -0
- mcli/ml/features/political_features.py +676 -0
- mcli/ml/features/recommendation_engine.py +809 -0
- mcli/ml/features/stock_features.py +573 -0
- mcli/ml/features/test_feature_engineering.py +346 -0
- mcli/ml/logging.py +85 -0
- mcli/ml/mlops/data_versioning.py +518 -0
- mcli/ml/mlops/experiment_tracker.py +377 -0
- mcli/ml/mlops/model_serving.py +481 -0
- mcli/ml/mlops/pipeline_orchestrator.py +614 -0
- mcli/ml/models/base_models.py +324 -0
- mcli/ml/models/ensemble_models.py +675 -0
- mcli/ml/models/recommendation_models.py +474 -0
- mcli/ml/models/test_models.py +487 -0
- mcli/ml/monitoring/drift_detection.py +676 -0
- mcli/ml/monitoring/metrics.py +45 -0
- mcli/ml/optimization/portfolio_optimizer.py +834 -0
- mcli/ml/preprocessing/data_cleaners.py +451 -0
- mcli/ml/preprocessing/feature_extractors.py +491 -0
- mcli/ml/preprocessing/ml_pipeline.py +382 -0
- mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
- mcli/ml/preprocessing/test_preprocessing.py +294 -0
- mcli/ml/scripts/populate_sample_data.py +200 -0
- mcli/ml/tasks.py +400 -0
- mcli/ml/tests/test_integration.py +429 -0
- mcli/ml/tests/test_training_dashboard.py +387 -0
- mcli/public/oi/oi.py +15 -0
- mcli/public/public.py +4 -0
- mcli/self/self_cmd.py +1246 -0
- mcli/workflow/daemon/api_daemon.py +800 -0
- mcli/workflow/daemon/async_command_database.py +681 -0
- mcli/workflow/daemon/async_process_manager.py +591 -0
- mcli/workflow/daemon/client.py +530 -0
- mcli/workflow/daemon/commands.py +1196 -0
- mcli/workflow/daemon/daemon.py +905 -0
- mcli/workflow/daemon/daemon_api.py +59 -0
- mcli/workflow/daemon/enhanced_daemon.py +571 -0
- mcli/workflow/daemon/process_cli.py +244 -0
- mcli/workflow/daemon/process_manager.py +439 -0
- mcli/workflow/daemon/test_daemon.py +275 -0
- mcli/workflow/dashboard/dashboard_cmd.py +113 -0
- mcli/workflow/docker/docker.py +0 -0
- mcli/workflow/file/file.py +100 -0
- mcli/workflow/gcloud/config.toml +21 -0
- mcli/workflow/gcloud/gcloud.py +58 -0
- mcli/workflow/git_commit/ai_service.py +328 -0
- mcli/workflow/git_commit/commands.py +430 -0
- mcli/workflow/lsh_integration.py +355 -0
- mcli/workflow/model_service/client.py +594 -0
- mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
- mcli/workflow/model_service/lightweight_embedder.py +397 -0
- mcli/workflow/model_service/lightweight_model_server.py +714 -0
- mcli/workflow/model_service/lightweight_test.py +241 -0
- mcli/workflow/model_service/model_service.py +1955 -0
- mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
- mcli/workflow/model_service/pdf_processor.py +386 -0
- mcli/workflow/model_service/test_efficient_runner.py +234 -0
- mcli/workflow/model_service/test_example.py +315 -0
- mcli/workflow/model_service/test_integration.py +131 -0
- mcli/workflow/model_service/test_new_features.py +149 -0
- mcli/workflow/openai/openai.py +99 -0
- mcli/workflow/politician_trading/commands.py +1790 -0
- mcli/workflow/politician_trading/config.py +134 -0
- mcli/workflow/politician_trading/connectivity.py +490 -0
- mcli/workflow/politician_trading/data_sources.py +395 -0
- mcli/workflow/politician_trading/database.py +410 -0
- mcli/workflow/politician_trading/demo.py +248 -0
- mcli/workflow/politician_trading/models.py +165 -0
- mcli/workflow/politician_trading/monitoring.py +413 -0
- mcli/workflow/politician_trading/scrapers.py +966 -0
- mcli/workflow/politician_trading/scrapers_california.py +412 -0
- mcli/workflow/politician_trading/scrapers_eu.py +377 -0
- mcli/workflow/politician_trading/scrapers_uk.py +350 -0
- mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
- mcli/workflow/politician_trading/supabase_functions.py +354 -0
- mcli/workflow/politician_trading/workflow.py +852 -0
- mcli/workflow/registry/registry.py +180 -0
- mcli/workflow/repo/repo.py +223 -0
- mcli/workflow/scheduler/commands.py +493 -0
- mcli/workflow/scheduler/cron_parser.py +238 -0
- mcli/workflow/scheduler/job.py +182 -0
- mcli/workflow/scheduler/monitor.py +139 -0
- mcli/workflow/scheduler/persistence.py +324 -0
- mcli/workflow/scheduler/scheduler.py +679 -0
- mcli/workflow/sync/sync_cmd.py +437 -0
- mcli/workflow/sync/test_cmd.py +314 -0
- mcli/workflow/videos/videos.py +242 -0
- mcli/workflow/wakatime/wakatime.py +11 -0
- mcli/workflow/workflow.py +37 -0
- mcli_framework-7.0.0.dist-info/METADATA +479 -0
- mcli_framework-7.0.0.dist-info/RECORD +186 -0
- mcli_framework-7.0.0.dist-info/WHEEL +5 -0
- mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
- mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
- mcli_framework-7.0.0.dist-info/top_level.txt +1 -0
mcli/ml/cache.py
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"""Redis caching layer for ML system"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import pickle
|
|
5
|
+
import hashlib
|
|
6
|
+
import asyncio
|
|
7
|
+
from typing import Optional, Any, Union, Callable
|
|
8
|
+
from functools import wraps
|
|
9
|
+
from datetime import timedelta
|
|
10
|
+
|
|
11
|
+
import redis
|
|
12
|
+
from redis import asyncio as aioredis
|
|
13
|
+
|
|
14
|
+
from mcli.ml.config import settings
|
|
15
|
+
from mcli.ml.logging import get_logger
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CacheManager:
|
|
21
|
+
"""Manage Redis cache connections and operations"""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self.redis_client: Optional[redis.Redis] = None
|
|
25
|
+
self.async_redis_client: Optional[aioredis.Redis] = None
|
|
26
|
+
self._initialized = False
|
|
27
|
+
|
|
28
|
+
def initialize(self):
|
|
29
|
+
"""Initialize Redis connections"""
|
|
30
|
+
if self._initialized:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
# Sync Redis client
|
|
35
|
+
self.redis_client = redis.Redis(
|
|
36
|
+
host=settings.redis.host,
|
|
37
|
+
port=settings.redis.port,
|
|
38
|
+
db=settings.redis.db,
|
|
39
|
+
password=settings.redis.password,
|
|
40
|
+
decode_responses=False,
|
|
41
|
+
socket_connect_timeout=5,
|
|
42
|
+
socket_timeout=5,
|
|
43
|
+
connection_pool=redis.ConnectionPool(
|
|
44
|
+
host=settings.redis.host,
|
|
45
|
+
port=settings.redis.port,
|
|
46
|
+
db=settings.redis.db,
|
|
47
|
+
password=settings.redis.password,
|
|
48
|
+
max_connections=settings.redis.max_connections
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Test connection
|
|
53
|
+
self.redis_client.ping()
|
|
54
|
+
self._initialized = True
|
|
55
|
+
logger.info("Cache manager initialized successfully")
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logger.error(f"Failed to initialize cache: {e}")
|
|
59
|
+
self.redis_client = None
|
|
60
|
+
|
|
61
|
+
async def initialize_async(self):
|
|
62
|
+
"""Initialize async Redis connection"""
|
|
63
|
+
if self.async_redis_client:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
self.async_redis_client = await aioredis.from_url(
|
|
68
|
+
settings.redis.url,
|
|
69
|
+
encoding="utf-8",
|
|
70
|
+
decode_responses=False,
|
|
71
|
+
max_connections=settings.redis.max_connections
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Test connection
|
|
75
|
+
await self.async_redis_client.ping()
|
|
76
|
+
logger.info("Async cache manager initialized successfully")
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.error(f"Failed to initialize async cache: {e}")
|
|
80
|
+
self.async_redis_client = None
|
|
81
|
+
|
|
82
|
+
def _make_key(self, key: str, prefix: str = "mcli:ml:") -> str:
|
|
83
|
+
"""Create cache key with prefix"""
|
|
84
|
+
return f"{prefix}{key}"
|
|
85
|
+
|
|
86
|
+
def _serialize(self, value: Any) -> bytes:
|
|
87
|
+
"""Serialize value for storage"""
|
|
88
|
+
try:
|
|
89
|
+
# Try JSON first (for simple types)
|
|
90
|
+
if isinstance(value, (dict, list, str, int, float, bool, type(None))):
|
|
91
|
+
return json.dumps(value).encode('utf-8')
|
|
92
|
+
except:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
# Fall back to pickle for complex objects
|
|
96
|
+
return pickle.dumps(value)
|
|
97
|
+
|
|
98
|
+
def _deserialize(self, value: bytes) -> Any:
|
|
99
|
+
"""Deserialize value from storage"""
|
|
100
|
+
if value is None:
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
# Try JSON first
|
|
104
|
+
try:
|
|
105
|
+
return json.loads(value.decode('utf-8'))
|
|
106
|
+
except:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
# Fall back to pickle
|
|
110
|
+
try:
|
|
111
|
+
return pickle.loads(value)
|
|
112
|
+
except:
|
|
113
|
+
logger.error("Failed to deserialize cache value")
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
def set(self, key: str, value: Any, expire: int = 3600) -> bool:
|
|
117
|
+
"""Set cache value"""
|
|
118
|
+
if not self.redis_client:
|
|
119
|
+
self.initialize()
|
|
120
|
+
|
|
121
|
+
if not self.redis_client:
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
cache_key = self._make_key(key)
|
|
126
|
+
serialized_value = self._serialize(value)
|
|
127
|
+
return self.redis_client.setex(cache_key, expire, serialized_value)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error(f"Cache set error: {e}")
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
def get(self, key: str) -> Any:
|
|
133
|
+
"""Get cache value"""
|
|
134
|
+
if not self.redis_client:
|
|
135
|
+
self.initialize()
|
|
136
|
+
|
|
137
|
+
if not self.redis_client:
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
cache_key = self._make_key(key)
|
|
142
|
+
value = self.redis_client.get(cache_key)
|
|
143
|
+
return self._deserialize(value)
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.error(f"Cache get error: {e}")
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
async def set_async(self, key: str, value: Any, expire: int = 3600) -> bool:
|
|
149
|
+
"""Set cache value asynchronously"""
|
|
150
|
+
if not self.async_redis_client:
|
|
151
|
+
await self.initialize_async()
|
|
152
|
+
|
|
153
|
+
if not self.async_redis_client:
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
cache_key = self._make_key(key)
|
|
158
|
+
serialized_value = self._serialize(value)
|
|
159
|
+
await self.async_redis_client.setex(cache_key, expire, serialized_value)
|
|
160
|
+
return True
|
|
161
|
+
except Exception as e:
|
|
162
|
+
logger.error(f"Async cache set error: {e}")
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
async def get_async(self, key: str) -> Any:
|
|
166
|
+
"""Get cache value asynchronously"""
|
|
167
|
+
if not self.async_redis_client:
|
|
168
|
+
await self.initialize_async()
|
|
169
|
+
|
|
170
|
+
if not self.async_redis_client:
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
cache_key = self._make_key(key)
|
|
175
|
+
value = await self.async_redis_client.get(cache_key)
|
|
176
|
+
return self._deserialize(value) if value else None
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logger.error(f"Async cache get error: {e}")
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
def delete(self, key: str) -> bool:
|
|
182
|
+
"""Delete cache entry"""
|
|
183
|
+
if not self.redis_client:
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
cache_key = self._make_key(key)
|
|
188
|
+
return bool(self.redis_client.delete(cache_key))
|
|
189
|
+
except Exception as e:
|
|
190
|
+
logger.error(f"Cache delete error: {e}")
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
async def delete_async(self, key: str) -> bool:
|
|
194
|
+
"""Delete cache entry asynchronously"""
|
|
195
|
+
if not self.async_redis_client:
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
cache_key = self._make_key(key)
|
|
200
|
+
result = await self.async_redis_client.delete(cache_key)
|
|
201
|
+
return bool(result)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(f"Async cache delete error: {e}")
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
def invalidate_pattern(self, pattern: str) -> int:
|
|
207
|
+
"""Invalidate all keys matching pattern"""
|
|
208
|
+
if not self.redis_client:
|
|
209
|
+
return 0
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
pattern_key = self._make_key(pattern)
|
|
213
|
+
keys = self.redis_client.keys(pattern_key)
|
|
214
|
+
if keys:
|
|
215
|
+
return self.redis_client.delete(*keys)
|
|
216
|
+
return 0
|
|
217
|
+
except Exception as e:
|
|
218
|
+
logger.error(f"Pattern invalidation error: {e}")
|
|
219
|
+
return 0
|
|
220
|
+
|
|
221
|
+
def get_or_set(self, key: str, func: Callable, expire: int = 3600) -> Any:
|
|
222
|
+
"""Get from cache or compute and set"""
|
|
223
|
+
value = self.get(key)
|
|
224
|
+
if value is not None:
|
|
225
|
+
return value
|
|
226
|
+
|
|
227
|
+
value = func()
|
|
228
|
+
self.set(key, value, expire)
|
|
229
|
+
return value
|
|
230
|
+
|
|
231
|
+
async def get_or_set_async(self, key: str, func: Callable, expire: int = 3600) -> Any:
|
|
232
|
+
"""Get from cache or compute and set asynchronously"""
|
|
233
|
+
value = await self.get_async(key)
|
|
234
|
+
if value is not None:
|
|
235
|
+
return value
|
|
236
|
+
|
|
237
|
+
if asyncio.iscoroutinefunction(func):
|
|
238
|
+
value = await func()
|
|
239
|
+
else:
|
|
240
|
+
value = func()
|
|
241
|
+
|
|
242
|
+
await self.set_async(key, value, expire)
|
|
243
|
+
return value
|
|
244
|
+
|
|
245
|
+
def close(self):
|
|
246
|
+
"""Close Redis connections"""
|
|
247
|
+
if self.redis_client:
|
|
248
|
+
self.redis_client.close()
|
|
249
|
+
self.redis_client = None
|
|
250
|
+
|
|
251
|
+
async def close_async(self):
|
|
252
|
+
"""Close async Redis connection"""
|
|
253
|
+
if self.async_redis_client:
|
|
254
|
+
await self.async_redis_client.close()
|
|
255
|
+
self.async_redis_client = None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# Global cache manager instance
|
|
259
|
+
cache_manager = CacheManager()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# Decorator for caching function results
|
|
263
|
+
def cached(expire: int = 3600, key_prefix: str = None):
|
|
264
|
+
"""Decorator to cache function results"""
|
|
265
|
+
|
|
266
|
+
def decorator(func: Callable):
|
|
267
|
+
@wraps(func)
|
|
268
|
+
async def async_wrapper(*args, **kwargs):
|
|
269
|
+
# Generate cache key
|
|
270
|
+
key_parts = [func.__module__, func.__name__]
|
|
271
|
+
if key_prefix:
|
|
272
|
+
key_parts.insert(0, key_prefix)
|
|
273
|
+
|
|
274
|
+
# Add function arguments to key
|
|
275
|
+
key_data = {
|
|
276
|
+
'args': args,
|
|
277
|
+
'kwargs': kwargs
|
|
278
|
+
}
|
|
279
|
+
key_hash = hashlib.md5(
|
|
280
|
+
json.dumps(key_data, sort_keys=True, default=str).encode()
|
|
281
|
+
).hexdigest()
|
|
282
|
+
cache_key = f"{':'.join(key_parts)}:{key_hash}"
|
|
283
|
+
|
|
284
|
+
# Try to get from cache
|
|
285
|
+
cached_value = await cache_manager.get_async(cache_key)
|
|
286
|
+
if cached_value is not None:
|
|
287
|
+
logger.debug(f"Cache hit for {cache_key}")
|
|
288
|
+
return cached_value
|
|
289
|
+
|
|
290
|
+
# Compute value
|
|
291
|
+
logger.debug(f"Cache miss for {cache_key}")
|
|
292
|
+
if asyncio.iscoroutinefunction(func):
|
|
293
|
+
value = await func(*args, **kwargs)
|
|
294
|
+
else:
|
|
295
|
+
value = func(*args, **kwargs)
|
|
296
|
+
|
|
297
|
+
# Store in cache
|
|
298
|
+
await cache_manager.set_async(cache_key, value, expire)
|
|
299
|
+
|
|
300
|
+
return value
|
|
301
|
+
|
|
302
|
+
@wraps(func)
|
|
303
|
+
def sync_wrapper(*args, **kwargs):
|
|
304
|
+
# Generate cache key
|
|
305
|
+
key_parts = [func.__module__, func.__name__]
|
|
306
|
+
if key_prefix:
|
|
307
|
+
key_parts.insert(0, key_prefix)
|
|
308
|
+
|
|
309
|
+
key_data = {
|
|
310
|
+
'args': args,
|
|
311
|
+
'kwargs': kwargs
|
|
312
|
+
}
|
|
313
|
+
key_hash = hashlib.md5(
|
|
314
|
+
json.dumps(key_data, sort_keys=True, default=str).encode()
|
|
315
|
+
).hexdigest()
|
|
316
|
+
cache_key = f"{':'.join(key_parts)}:{key_hash}"
|
|
317
|
+
|
|
318
|
+
# Try to get from cache
|
|
319
|
+
cached_value = cache_manager.get(cache_key)
|
|
320
|
+
if cached_value is not None:
|
|
321
|
+
logger.debug(f"Cache hit for {cache_key}")
|
|
322
|
+
return cached_value
|
|
323
|
+
|
|
324
|
+
# Compute value
|
|
325
|
+
logger.debug(f"Cache miss for {cache_key}")
|
|
326
|
+
value = func(*args, **kwargs)
|
|
327
|
+
|
|
328
|
+
# Store in cache
|
|
329
|
+
cache_manager.set(cache_key, value, expire)
|
|
330
|
+
|
|
331
|
+
return value
|
|
332
|
+
|
|
333
|
+
if asyncio.iscoroutinefunction(func):
|
|
334
|
+
return async_wrapper
|
|
335
|
+
else:
|
|
336
|
+
return sync_wrapper
|
|
337
|
+
|
|
338
|
+
return decorator
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# Cache invalidation helpers
|
|
342
|
+
def invalidate_user_cache(user_id: str):
|
|
343
|
+
"""Invalidate all cache entries for a user"""
|
|
344
|
+
pattern = f"user:{user_id}:*"
|
|
345
|
+
return cache_manager.invalidate_pattern(pattern)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def invalidate_model_cache(model_id: str):
|
|
349
|
+
"""Invalidate all cache entries for a model"""
|
|
350
|
+
pattern = f"model:{model_id}:*"
|
|
351
|
+
return cache_manager.invalidate_pattern(pattern)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def invalidate_prediction_cache(prediction_id: str = None):
|
|
355
|
+
"""Invalidate prediction cache"""
|
|
356
|
+
if prediction_id:
|
|
357
|
+
pattern = f"prediction:{prediction_id}:*"
|
|
358
|
+
else:
|
|
359
|
+
pattern = "prediction:*"
|
|
360
|
+
return cache_manager.invalidate_pattern(pattern)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
# Convenience functions
|
|
364
|
+
async def init_cache():
|
|
365
|
+
"""Initialize cache manager"""
|
|
366
|
+
await cache_manager.initialize_async()
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
async def close_cache():
|
|
370
|
+
"""Close cache connections"""
|
|
371
|
+
await cache_manager.close_async()
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
async def check_cache_health() -> bool:
|
|
375
|
+
"""Check if cache is healthy"""
|
|
376
|
+
try:
|
|
377
|
+
if not cache_manager.async_redis_client:
|
|
378
|
+
await cache_manager.initialize_async()
|
|
379
|
+
|
|
380
|
+
if cache_manager.async_redis_client:
|
|
381
|
+
await cache_manager.async_redis_client.ping()
|
|
382
|
+
return True
|
|
383
|
+
return False
|
|
384
|
+
except:
|
|
385
|
+
return False
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def cache_set(key: str, value: Any, expire: int = 3600):
|
|
389
|
+
"""Set cache value"""
|
|
390
|
+
return cache_manager.set(key, value, expire)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def cache_get(key: str):
|
|
394
|
+
"""Get cache value"""
|
|
395
|
+
return cache_manager.get(key)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def cache_delete(key: str):
|
|
399
|
+
"""Delete cache entry"""
|
|
400
|
+
return cache_manager.delete(key)
|