oplnk-python-utils 1.0.1__tar.gz → 1.0.2__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.
- {oplnk_python_utils-1.0.1/oplnk_python_utils.egg-info → oplnk_python_utils-1.0.2}/PKG-INFO +163 -35
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/README.md +162 -34
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/oplnk_python_utils/__init__.py +3 -8
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/oplnk_python_utils/cache/__init__.py +12 -4
- oplnk_python_utils-1.0.2/oplnk_python_utils/cache/cache_utils.py +226 -0
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/oplnk_python_utils/query/query_builder.py +295 -169
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2/oplnk_python_utils.egg-info}/PKG-INFO +163 -35
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/setup.py +2 -2
- oplnk_python_utils-1.0.1/oplnk_python_utils/cache/cache_utils.py +0 -88
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/LICENSE +0 -0
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/MANIFEST.in +0 -0
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/oplnk_python_utils/core/__init__.py +0 -0
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/oplnk_python_utils/core/data.py +0 -0
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/oplnk_python_utils/core/parsing.py +0 -0
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/oplnk_python_utils/core/serialization.py +0 -0
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/oplnk_python_utils/query/__init__.py +0 -0
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/oplnk_python_utils.egg-info/SOURCES.txt +0 -0
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/oplnk_python_utils.egg-info/dependency_links.txt +0 -0
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/oplnk_python_utils.egg-info/not-zip-safe +0 -0
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/oplnk_python_utils.egg-info/requires.txt +0 -0
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/oplnk_python_utils.egg-info/top_level.txt +0 -0
- {oplnk_python_utils-1.0.1 → oplnk_python_utils-1.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oplnk-python-utils
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Reusable utilities for caching, data manipulation, parsing, and MongoDB query building
|
|
5
5
|
Home-page: https://github.com/openlinkspa/oplnk-python-utils
|
|
6
6
|
Author: OpenLink SpA
|
|
@@ -189,7 +189,9 @@ serialized = datetime_serializer(data)
|
|
|
189
189
|
|
|
190
190
|
## Cache Module
|
|
191
191
|
|
|
192
|
-
Advanced caching utilities with Redis integration and intelligent cache key management.
|
|
192
|
+
Advanced caching utilities with Redis integration and intelligent cache key management. The module provides both low-level caching utilities and high-level endpoint decorators for comprehensive caching strategies.
|
|
193
|
+
|
|
194
|
+
### Low-Level Cache Utilities
|
|
193
195
|
|
|
194
196
|
```python
|
|
195
197
|
from oplnk_python_utils.cache import generate_cache_id, get_or_set_cache, invalidate_cache_keys
|
|
@@ -199,7 +201,7 @@ cache_key = generate_cache_id({
|
|
|
199
201
|
"filters": {"name": "John", "status": "active"},
|
|
200
202
|
"sort": ["created_at:desc"]
|
|
201
203
|
})
|
|
202
|
-
# Automatically normalizes data internally and creates
|
|
204
|
+
# Automatically normalizes data internally and creates SHA256 hash
|
|
203
205
|
|
|
204
206
|
# Get from cache or execute function and cache result
|
|
205
207
|
async def expensive_operation(query):
|
|
@@ -207,37 +209,145 @@ async def expensive_operation(query):
|
|
|
207
209
|
return await db.collection.find(query).to_list()
|
|
208
210
|
|
|
209
211
|
result = await get_or_set_cache(
|
|
212
|
+
redis_client=redis,
|
|
210
213
|
cache_key="user_query_abc123",
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
fetch_func=expensive_operation,
|
|
215
|
+
ttl=3600, # 1 hour
|
|
216
|
+
serializer=datetime_serializer
|
|
214
217
|
)
|
|
215
218
|
|
|
216
|
-
#
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
219
|
+
# Invalidate multiple cache keys by pattern
|
|
220
|
+
invalidated_count = invalidate_cache_keys(redis, "user_*")
|
|
221
|
+
print(f"Invalidated {invalidated_count} cache entries")
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Endpoint-Level Caching (NEW)
|
|
225
|
+
|
|
226
|
+
High-level decorators for caching entire endpoint responses with automatic cache key generation and tag-based invalidation.
|
|
224
227
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
228
|
+
```python
|
|
229
|
+
from oplnk_python_utils.cache import cache_response, cache_factory, build_cache_key, invalidate_tag
|
|
230
|
+
from fastapi import FastAPI, Request
|
|
231
|
+
import redis
|
|
232
|
+
|
|
233
|
+
app = FastAPI()
|
|
234
|
+
redis_client = redis.Redis(host='localhost', port=6379, db=0)
|
|
235
|
+
|
|
236
|
+
# Method 1: Direct decorator usage
|
|
237
|
+
@app.get("/users")
|
|
238
|
+
@cache_response(
|
|
239
|
+
redis_client=redis_client,
|
|
240
|
+
api_name="myapi",
|
|
241
|
+
collection="users",
|
|
242
|
+
endpoint_type="read",
|
|
243
|
+
tags=["users", "all_data"],
|
|
244
|
+
ttl=3600, # 1 hour
|
|
245
|
+
use_cache=True
|
|
229
246
|
)
|
|
247
|
+
async def get_users(request: Request):
|
|
248
|
+
# Your endpoint logic here
|
|
249
|
+
return {"users": [...]}
|
|
250
|
+
|
|
251
|
+
# Method 2: Factory pattern for reusable decorators
|
|
252
|
+
users_cache = cache_factory(redis_client, "myapi", "users")
|
|
253
|
+
|
|
254
|
+
@app.get("/users")
|
|
255
|
+
@users_cache(
|
|
256
|
+
endpoint_type="read",
|
|
257
|
+
tags=["users", "public"],
|
|
258
|
+
ttl=1800 # 30 minutes
|
|
259
|
+
)
|
|
260
|
+
async def get_users(request: Request):
|
|
261
|
+
return {"users": [...]}
|
|
262
|
+
|
|
263
|
+
@app.post("/users")
|
|
264
|
+
@users_cache(
|
|
265
|
+
endpoint_type="create",
|
|
266
|
+
tags=["users"],
|
|
267
|
+
ttl=0 # No caching for write operations
|
|
268
|
+
)
|
|
269
|
+
async def create_user(request: Request):
|
|
270
|
+
# Automatically invalidates 'users' tag after creation
|
|
271
|
+
return {"created": True}
|
|
272
|
+
|
|
273
|
+
# Manual cache key building
|
|
274
|
+
@app.get("/custom")
|
|
275
|
+
async def custom_endpoint(request: Request):
|
|
276
|
+
cache_key = build_cache_key(request, "myapi", "custom", "read")
|
|
277
|
+
# Use cache_key for custom caching logic
|
|
278
|
+
return {"data": "custom"}
|
|
279
|
+
|
|
280
|
+
# Tag-based cache invalidation
|
|
281
|
+
@app.delete("/users/{user_id}")
|
|
282
|
+
async def delete_user(user_id: str):
|
|
283
|
+
# Delete user logic here
|
|
284
|
+
# ...
|
|
285
|
+
|
|
286
|
+
# Invalidate all cached responses tagged with 'users'
|
|
287
|
+
invalidate_tag(redis_client, "users")
|
|
288
|
+
return {"deleted": True}
|
|
289
|
+
```
|
|
230
290
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
291
|
+
### Cache Key Structure
|
|
292
|
+
|
|
293
|
+
The caching system generates structured cache keys based on:
|
|
294
|
+
|
|
295
|
+
- **API Name**: Namespace for your application
|
|
296
|
+
- **Collection**: The data collection being cached
|
|
297
|
+
- **Endpoint Type**: Type of operation (read, create, update, delete)
|
|
298
|
+
- **Query Hash**: SHA256 hash of normalized query parameters
|
|
299
|
+
|
|
300
|
+
Example cache key: `myapi:users:read:a1b2c3d4e5f6...`
|
|
301
|
+
|
|
302
|
+
### Tag-Based Cache Management
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
# Cache responses with multiple tags
|
|
306
|
+
@cache_response(
|
|
307
|
+
redis_client=redis_client,
|
|
308
|
+
api_name="ecommerce",
|
|
309
|
+
collection="products",
|
|
310
|
+
endpoint_type="read",
|
|
311
|
+
tags=["products", "catalog", "public_data"],
|
|
312
|
+
ttl=7200 # 2 hours
|
|
313
|
+
)
|
|
314
|
+
async def get_products(request: Request):
|
|
315
|
+
return {"products": [...]}
|
|
316
|
+
|
|
317
|
+
# Invalidate specific tag groups
|
|
318
|
+
invalidate_tag(redis_client, "products") # Invalidates all product-related cache
|
|
319
|
+
invalidate_tag(redis_client, "catalog") # Invalidates all catalog cache
|
|
320
|
+
invalidate_tag(redis_client, "public_data") # Invalidates all public data cache
|
|
234
321
|
```
|
|
235
322
|
|
|
236
|
-
### Cache Functions
|
|
323
|
+
### Cache Functions Reference
|
|
324
|
+
|
|
325
|
+
#### Low-Level Functions
|
|
326
|
+
|
|
327
|
+
- **`generate_cache_id(params)`**: Generate SHA256 hash from normalized data for consistent cache keys
|
|
328
|
+
- **`get_or_set_cache(redis_client, cache_key, fetch_func, clear_cache=False, ttl=None, serializer=None)`**: Get cached result or execute function and cache with automatic serialization
|
|
329
|
+
- **`invalidate_cache_keys(redis_client, pattern)`**: Delete cache keys matching pattern (supports wildcards)
|
|
330
|
+
|
|
331
|
+
#### Endpoint Caching Functions
|
|
332
|
+
|
|
333
|
+
- **`build_cache_key(request, api_name, collection, endpoint_type)`**: Build structured cache key from request parameters
|
|
334
|
+
- **`cache_response(redis_client, api_name, collection, endpoint_type, tags, ttl=None, use_cache=True)`**: Decorator for caching endpoint responses
|
|
335
|
+
- **`cache_factory(redis_client, api_name, collection)`**: Factory function to create reusable cache decorators
|
|
336
|
+
- **`invalidate_tag(redis_client, tag)`**: Invalidate all cache keys associated with a specific tag
|
|
337
|
+
|
|
338
|
+
### Automatic Cache Invalidation
|
|
237
339
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
340
|
+
The caching system supports automatic cache clearing via query parameters:
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
# Clear cache before executing endpoint
|
|
344
|
+
GET /users?clear_cache=true
|
|
345
|
+
|
|
346
|
+
# This will:
|
|
347
|
+
# 1. Invalidate the collection tag (e.g., "users")
|
|
348
|
+
# 2. Execute the endpoint normally
|
|
349
|
+
# 3. Cache the fresh response
|
|
350
|
+
```
|
|
241
351
|
|
|
242
352
|
## Query Module
|
|
243
353
|
|
|
@@ -313,7 +423,7 @@ results = query_builder.read_query(
|
|
|
313
423
|
params=raw_params, # Will be parsed automatically using core.parsing
|
|
314
424
|
cache_client=redis_client,
|
|
315
425
|
cache_key_prefix="users",
|
|
316
|
-
use_cache=
|
|
426
|
+
use_cache=False # Note: Endpoint-level caching is now preferred over query-level caching
|
|
317
427
|
)
|
|
318
428
|
|
|
319
429
|
print(results)
|
|
@@ -441,30 +551,48 @@ class CRUD(QueryBuilder):
|
|
|
441
551
|
self.db = db[self.collection]
|
|
442
552
|
super().__init__(collection_prefix=PREFIX, relations=relations)
|
|
443
553
|
|
|
444
|
-
def read_query(self, params=None, query=None, pipeline=None, pipeline_count=None
|
|
445
|
-
# Just
|
|
554
|
+
def read_query(self, params=None, query=None, pipeline=None, pipeline_count=None):
|
|
555
|
+
# Just 4 lines! All complexity abstracted away
|
|
446
556
|
return super().read_query(
|
|
447
557
|
db_collection=self.db, params=params, query=query,
|
|
448
|
-
pipeline=pipeline, pipeline_count=pipeline_count
|
|
449
|
-
use_cache=use_cache, cache_client=redis,
|
|
450
|
-
cache_key_prefix=f"app:{self.collection}"
|
|
558
|
+
pipeline=pipeline, pipeline_count=pipeline_count
|
|
451
559
|
)
|
|
452
560
|
|
|
453
|
-
# Endpoint -
|
|
561
|
+
# Endpoint - With new endpoint-level caching!
|
|
562
|
+
from oplnk_python_utils.cache import cache_factory
|
|
454
563
|
from oplnk_python_utils.core.parsing import parse_params_to_dict
|
|
455
564
|
|
|
565
|
+
# Create reusable cache decorator for this collection
|
|
566
|
+
users_cache = cache_factory(redis_client, "myapp", "users")
|
|
567
|
+
|
|
456
568
|
@router.get("/query")
|
|
569
|
+
@users_cache(
|
|
570
|
+
endpoint_type="read",
|
|
571
|
+
tags=["users", "queries"],
|
|
572
|
+
ttl=3600 # Cache for 1 hour
|
|
573
|
+
)
|
|
457
574
|
async def get_data(request: Request):
|
|
458
575
|
# Option 1: Manual parsing (explicit control)
|
|
459
576
|
query = parse_params_to_dict(request.query_params)
|
|
460
|
-
return crud.read_query(query=query, pipeline=base_pipeline
|
|
577
|
+
return crud.read_query(query=query, pipeline=base_pipeline)
|
|
461
578
|
|
|
462
579
|
# Option 2: Automatic parsing (convenience)
|
|
463
580
|
return crud.read_query(
|
|
464
581
|
params=request.query_params, # Parsed automatically
|
|
465
|
-
pipeline=base_pipeline
|
|
466
|
-
use_cache=True
|
|
582
|
+
pipeline=base_pipeline
|
|
467
583
|
)
|
|
584
|
+
|
|
585
|
+
# Write operations with cache invalidation
|
|
586
|
+
@router.post("/users")
|
|
587
|
+
@users_cache(
|
|
588
|
+
endpoint_type="create",
|
|
589
|
+
tags=["users"],
|
|
590
|
+
ttl=0 # No caching for write operations
|
|
591
|
+
)
|
|
592
|
+
async def create_user(request: Request, user_data: dict):
|
|
593
|
+
result = crud.create(user_data)
|
|
594
|
+
# Cache is automatically invalidated due to 'users' tag
|
|
595
|
+
return result
|
|
468
596
|
```
|
|
469
597
|
|
|
470
598
|
## Advanced Features
|
|
@@ -147,7 +147,9 @@ serialized = datetime_serializer(data)
|
|
|
147
147
|
|
|
148
148
|
## Cache Module
|
|
149
149
|
|
|
150
|
-
Advanced caching utilities with Redis integration and intelligent cache key management.
|
|
150
|
+
Advanced caching utilities with Redis integration and intelligent cache key management. The module provides both low-level caching utilities and high-level endpoint decorators for comprehensive caching strategies.
|
|
151
|
+
|
|
152
|
+
### Low-Level Cache Utilities
|
|
151
153
|
|
|
152
154
|
```python
|
|
153
155
|
from oplnk_python_utils.cache import generate_cache_id, get_or_set_cache, invalidate_cache_keys
|
|
@@ -157,7 +159,7 @@ cache_key = generate_cache_id({
|
|
|
157
159
|
"filters": {"name": "John", "status": "active"},
|
|
158
160
|
"sort": ["created_at:desc"]
|
|
159
161
|
})
|
|
160
|
-
# Automatically normalizes data internally and creates
|
|
162
|
+
# Automatically normalizes data internally and creates SHA256 hash
|
|
161
163
|
|
|
162
164
|
# Get from cache or execute function and cache result
|
|
163
165
|
async def expensive_operation(query):
|
|
@@ -165,37 +167,145 @@ async def expensive_operation(query):
|
|
|
165
167
|
return await db.collection.find(query).to_list()
|
|
166
168
|
|
|
167
169
|
result = await get_or_set_cache(
|
|
170
|
+
redis_client=redis,
|
|
168
171
|
cache_key="user_query_abc123",
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
+
fetch_func=expensive_operation,
|
|
173
|
+
ttl=3600, # 1 hour
|
|
174
|
+
serializer=datetime_serializer
|
|
172
175
|
)
|
|
173
176
|
|
|
174
|
-
#
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
# Invalidate multiple cache keys by pattern
|
|
178
|
+
invalidated_count = invalidate_cache_keys(redis, "user_*")
|
|
179
|
+
print(f"Invalidated {invalidated_count} cache entries")
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Endpoint-Level Caching (NEW)
|
|
183
|
+
|
|
184
|
+
High-level decorators for caching entire endpoint responses with automatic cache key generation and tag-based invalidation.
|
|
182
185
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
```python
|
|
187
|
+
from oplnk_python_utils.cache import cache_response, cache_factory, build_cache_key, invalidate_tag
|
|
188
|
+
from fastapi import FastAPI, Request
|
|
189
|
+
import redis
|
|
190
|
+
|
|
191
|
+
app = FastAPI()
|
|
192
|
+
redis_client = redis.Redis(host='localhost', port=6379, db=0)
|
|
193
|
+
|
|
194
|
+
# Method 1: Direct decorator usage
|
|
195
|
+
@app.get("/users")
|
|
196
|
+
@cache_response(
|
|
197
|
+
redis_client=redis_client,
|
|
198
|
+
api_name="myapi",
|
|
199
|
+
collection="users",
|
|
200
|
+
endpoint_type="read",
|
|
201
|
+
tags=["users", "all_data"],
|
|
202
|
+
ttl=3600, # 1 hour
|
|
203
|
+
use_cache=True
|
|
187
204
|
)
|
|
205
|
+
async def get_users(request: Request):
|
|
206
|
+
# Your endpoint logic here
|
|
207
|
+
return {"users": [...]}
|
|
208
|
+
|
|
209
|
+
# Method 2: Factory pattern for reusable decorators
|
|
210
|
+
users_cache = cache_factory(redis_client, "myapi", "users")
|
|
211
|
+
|
|
212
|
+
@app.get("/users")
|
|
213
|
+
@users_cache(
|
|
214
|
+
endpoint_type="read",
|
|
215
|
+
tags=["users", "public"],
|
|
216
|
+
ttl=1800 # 30 minutes
|
|
217
|
+
)
|
|
218
|
+
async def get_users(request: Request):
|
|
219
|
+
return {"users": [...]}
|
|
220
|
+
|
|
221
|
+
@app.post("/users")
|
|
222
|
+
@users_cache(
|
|
223
|
+
endpoint_type="create",
|
|
224
|
+
tags=["users"],
|
|
225
|
+
ttl=0 # No caching for write operations
|
|
226
|
+
)
|
|
227
|
+
async def create_user(request: Request):
|
|
228
|
+
# Automatically invalidates 'users' tag after creation
|
|
229
|
+
return {"created": True}
|
|
230
|
+
|
|
231
|
+
# Manual cache key building
|
|
232
|
+
@app.get("/custom")
|
|
233
|
+
async def custom_endpoint(request: Request):
|
|
234
|
+
cache_key = build_cache_key(request, "myapi", "custom", "read")
|
|
235
|
+
# Use cache_key for custom caching logic
|
|
236
|
+
return {"data": "custom"}
|
|
237
|
+
|
|
238
|
+
# Tag-based cache invalidation
|
|
239
|
+
@app.delete("/users/{user_id}")
|
|
240
|
+
async def delete_user(user_id: str):
|
|
241
|
+
# Delete user logic here
|
|
242
|
+
# ...
|
|
243
|
+
|
|
244
|
+
# Invalidate all cached responses tagged with 'users'
|
|
245
|
+
invalidate_tag(redis_client, "users")
|
|
246
|
+
return {"deleted": True}
|
|
247
|
+
```
|
|
188
248
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
249
|
+
### Cache Key Structure
|
|
250
|
+
|
|
251
|
+
The caching system generates structured cache keys based on:
|
|
252
|
+
|
|
253
|
+
- **API Name**: Namespace for your application
|
|
254
|
+
- **Collection**: The data collection being cached
|
|
255
|
+
- **Endpoint Type**: Type of operation (read, create, update, delete)
|
|
256
|
+
- **Query Hash**: SHA256 hash of normalized query parameters
|
|
257
|
+
|
|
258
|
+
Example cache key: `myapi:users:read:a1b2c3d4e5f6...`
|
|
259
|
+
|
|
260
|
+
### Tag-Based Cache Management
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
# Cache responses with multiple tags
|
|
264
|
+
@cache_response(
|
|
265
|
+
redis_client=redis_client,
|
|
266
|
+
api_name="ecommerce",
|
|
267
|
+
collection="products",
|
|
268
|
+
endpoint_type="read",
|
|
269
|
+
tags=["products", "catalog", "public_data"],
|
|
270
|
+
ttl=7200 # 2 hours
|
|
271
|
+
)
|
|
272
|
+
async def get_products(request: Request):
|
|
273
|
+
return {"products": [...]}
|
|
274
|
+
|
|
275
|
+
# Invalidate specific tag groups
|
|
276
|
+
invalidate_tag(redis_client, "products") # Invalidates all product-related cache
|
|
277
|
+
invalidate_tag(redis_client, "catalog") # Invalidates all catalog cache
|
|
278
|
+
invalidate_tag(redis_client, "public_data") # Invalidates all public data cache
|
|
192
279
|
```
|
|
193
280
|
|
|
194
|
-
### Cache Functions
|
|
281
|
+
### Cache Functions Reference
|
|
282
|
+
|
|
283
|
+
#### Low-Level Functions
|
|
284
|
+
|
|
285
|
+
- **`generate_cache_id(params)`**: Generate SHA256 hash from normalized data for consistent cache keys
|
|
286
|
+
- **`get_or_set_cache(redis_client, cache_key, fetch_func, clear_cache=False, ttl=None, serializer=None)`**: Get cached result or execute function and cache with automatic serialization
|
|
287
|
+
- **`invalidate_cache_keys(redis_client, pattern)`**: Delete cache keys matching pattern (supports wildcards)
|
|
288
|
+
|
|
289
|
+
#### Endpoint Caching Functions
|
|
290
|
+
|
|
291
|
+
- **`build_cache_key(request, api_name, collection, endpoint_type)`**: Build structured cache key from request parameters
|
|
292
|
+
- **`cache_response(redis_client, api_name, collection, endpoint_type, tags, ttl=None, use_cache=True)`**: Decorator for caching endpoint responses
|
|
293
|
+
- **`cache_factory(redis_client, api_name, collection)`**: Factory function to create reusable cache decorators
|
|
294
|
+
- **`invalidate_tag(redis_client, tag)`**: Invalidate all cache keys associated with a specific tag
|
|
295
|
+
|
|
296
|
+
### Automatic Cache Invalidation
|
|
195
297
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
298
|
+
The caching system supports automatic cache clearing via query parameters:
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
# Clear cache before executing endpoint
|
|
302
|
+
GET /users?clear_cache=true
|
|
303
|
+
|
|
304
|
+
# This will:
|
|
305
|
+
# 1. Invalidate the collection tag (e.g., "users")
|
|
306
|
+
# 2. Execute the endpoint normally
|
|
307
|
+
# 3. Cache the fresh response
|
|
308
|
+
```
|
|
199
309
|
|
|
200
310
|
## Query Module
|
|
201
311
|
|
|
@@ -271,7 +381,7 @@ results = query_builder.read_query(
|
|
|
271
381
|
params=raw_params, # Will be parsed automatically using core.parsing
|
|
272
382
|
cache_client=redis_client,
|
|
273
383
|
cache_key_prefix="users",
|
|
274
|
-
use_cache=
|
|
384
|
+
use_cache=False # Note: Endpoint-level caching is now preferred over query-level caching
|
|
275
385
|
)
|
|
276
386
|
|
|
277
387
|
print(results)
|
|
@@ -399,30 +509,48 @@ class CRUD(QueryBuilder):
|
|
|
399
509
|
self.db = db[self.collection]
|
|
400
510
|
super().__init__(collection_prefix=PREFIX, relations=relations)
|
|
401
511
|
|
|
402
|
-
def read_query(self, params=None, query=None, pipeline=None, pipeline_count=None
|
|
403
|
-
# Just
|
|
512
|
+
def read_query(self, params=None, query=None, pipeline=None, pipeline_count=None):
|
|
513
|
+
# Just 4 lines! All complexity abstracted away
|
|
404
514
|
return super().read_query(
|
|
405
515
|
db_collection=self.db, params=params, query=query,
|
|
406
|
-
pipeline=pipeline, pipeline_count=pipeline_count
|
|
407
|
-
use_cache=use_cache, cache_client=redis,
|
|
408
|
-
cache_key_prefix=f"app:{self.collection}"
|
|
516
|
+
pipeline=pipeline, pipeline_count=pipeline_count
|
|
409
517
|
)
|
|
410
518
|
|
|
411
|
-
# Endpoint -
|
|
519
|
+
# Endpoint - With new endpoint-level caching!
|
|
520
|
+
from oplnk_python_utils.cache import cache_factory
|
|
412
521
|
from oplnk_python_utils.core.parsing import parse_params_to_dict
|
|
413
522
|
|
|
523
|
+
# Create reusable cache decorator for this collection
|
|
524
|
+
users_cache = cache_factory(redis_client, "myapp", "users")
|
|
525
|
+
|
|
414
526
|
@router.get("/query")
|
|
527
|
+
@users_cache(
|
|
528
|
+
endpoint_type="read",
|
|
529
|
+
tags=["users", "queries"],
|
|
530
|
+
ttl=3600 # Cache for 1 hour
|
|
531
|
+
)
|
|
415
532
|
async def get_data(request: Request):
|
|
416
533
|
# Option 1: Manual parsing (explicit control)
|
|
417
534
|
query = parse_params_to_dict(request.query_params)
|
|
418
|
-
return crud.read_query(query=query, pipeline=base_pipeline
|
|
535
|
+
return crud.read_query(query=query, pipeline=base_pipeline)
|
|
419
536
|
|
|
420
537
|
# Option 2: Automatic parsing (convenience)
|
|
421
538
|
return crud.read_query(
|
|
422
539
|
params=request.query_params, # Parsed automatically
|
|
423
|
-
pipeline=base_pipeline
|
|
424
|
-
use_cache=True
|
|
540
|
+
pipeline=base_pipeline
|
|
425
541
|
)
|
|
542
|
+
|
|
543
|
+
# Write operations with cache invalidation
|
|
544
|
+
@router.post("/users")
|
|
545
|
+
@users_cache(
|
|
546
|
+
endpoint_type="create",
|
|
547
|
+
tags=["users"],
|
|
548
|
+
ttl=0 # No caching for write operations
|
|
549
|
+
)
|
|
550
|
+
async def create_user(request: Request, user_data: dict):
|
|
551
|
+
result = crud.create(user_data)
|
|
552
|
+
# Cache is automatically invalidated due to 'users' tag
|
|
553
|
+
return result
|
|
426
554
|
```
|
|
427
555
|
|
|
428
556
|
## Advanced Features
|
|
@@ -2,17 +2,12 @@
|
|
|
2
2
|
OpenLink Python Utils - Reusable utilities for caching, data manipulation, parsing, and MongoDB query building
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
# Import modules only - no function re-exports
|
|
5
|
+
# Import modules only - no function re-exports
|
|
6
6
|
from . import core
|
|
7
7
|
from . import cache
|
|
8
8
|
from . import query
|
|
9
9
|
|
|
10
|
-
__version__ = "1.0.
|
|
10
|
+
__version__ = "1.0.2"
|
|
11
11
|
__author__ = "OpenLink SpA"
|
|
12
12
|
|
|
13
|
-
__all__ = [
|
|
14
|
-
"core",
|
|
15
|
-
"cache",
|
|
16
|
-
"query",
|
|
17
|
-
"__version__"
|
|
18
|
-
]
|
|
13
|
+
__all__ = ["core", "cache", "query", "__version__"]
|
|
@@ -5,7 +5,11 @@ Cache utilities module - Reusable caching functionality
|
|
|
5
5
|
from .cache_utils import (
|
|
6
6
|
generate_cache_id,
|
|
7
7
|
get_or_set_cache,
|
|
8
|
-
invalidate_cache_keys
|
|
8
|
+
invalidate_cache_keys,
|
|
9
|
+
build_cache_key,
|
|
10
|
+
cache_response,
|
|
11
|
+
invalidate_tag,
|
|
12
|
+
cache_factory,
|
|
9
13
|
)
|
|
10
14
|
|
|
11
15
|
# Re-export core utilities for convenience in this domain
|
|
@@ -14,8 +18,12 @@ from ..core.serialization import datetime_serializer
|
|
|
14
18
|
|
|
15
19
|
__all__ = [
|
|
16
20
|
"generate_cache_id",
|
|
17
|
-
"datetime_serializer",
|
|
21
|
+
"datetime_serializer",
|
|
18
22
|
"get_or_set_cache",
|
|
19
23
|
"invalidate_cache_keys",
|
|
20
|
-
"normalize"
|
|
21
|
-
|
|
24
|
+
"normalize",
|
|
25
|
+
"build_cache_key",
|
|
26
|
+
"cache_response",
|
|
27
|
+
"invalidate_tag",
|
|
28
|
+
"cache_factory",
|
|
29
|
+
]
|