oplnk-python-utils 1.0.0__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.0/oplnk_python_utils.egg-info → oplnk_python_utils-1.0.2}/PKG-INFO +163 -37
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/README.md +162 -35
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/oplnk_python_utils/__init__.py +3 -8
- {oplnk_python_utils-1.0.0 → 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.0 → oplnk_python_utils-1.0.2}/oplnk_python_utils/query/query_builder.py +295 -169
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2/oplnk_python_utils.egg-info}/PKG-INFO +163 -37
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/oplnk_python_utils.egg-info/requires.txt +0 -1
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/setup.py +2 -3
- oplnk_python_utils-1.0.0/oplnk_python_utils/cache/cache_utils.py +0 -88
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/LICENSE +0 -0
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/MANIFEST.in +0 -0
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/oplnk_python_utils/core/__init__.py +0 -0
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/oplnk_python_utils/core/data.py +0 -0
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/oplnk_python_utils/core/parsing.py +0 -0
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/oplnk_python_utils/core/serialization.py +0 -0
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/oplnk_python_utils/query/__init__.py +0 -0
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/oplnk_python_utils.egg-info/SOURCES.txt +0 -0
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/oplnk_python_utils.egg-info/dependency_links.txt +0 -0
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/oplnk_python_utils.egg-info/not-zip-safe +0 -0
- {oplnk_python_utils-1.0.0 → oplnk_python_utils-1.0.2}/oplnk_python_utils.egg-info/top_level.txt +0 -0
- {oplnk_python_utils-1.0.0 → 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
|
|
@@ -22,7 +22,6 @@ License-File: LICENSE
|
|
|
22
22
|
Requires-Dist: pymongo>=3.0.0
|
|
23
23
|
Requires-Dist: redis>=3.0.0
|
|
24
24
|
Requires-Dist: fastapi>=0.65.0
|
|
25
|
-
Requires-Dist: bson>=0.5.0
|
|
26
25
|
Provides-Extra: dev
|
|
27
26
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
28
27
|
Requires-Dist: pytest-cov>=2.0; extra == "dev"
|
|
@@ -190,7 +189,9 @@ serialized = datetime_serializer(data)
|
|
|
190
189
|
|
|
191
190
|
## Cache Module
|
|
192
191
|
|
|
193
|
-
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
|
|
194
195
|
|
|
195
196
|
```python
|
|
196
197
|
from oplnk_python_utils.cache import generate_cache_id, get_or_set_cache, invalidate_cache_keys
|
|
@@ -200,7 +201,7 @@ cache_key = generate_cache_id({
|
|
|
200
201
|
"filters": {"name": "John", "status": "active"},
|
|
201
202
|
"sort": ["created_at:desc"]
|
|
202
203
|
})
|
|
203
|
-
# Automatically normalizes data internally and creates
|
|
204
|
+
# Automatically normalizes data internally and creates SHA256 hash
|
|
204
205
|
|
|
205
206
|
# Get from cache or execute function and cache result
|
|
206
207
|
async def expensive_operation(query):
|
|
@@ -208,37 +209,145 @@ async def expensive_operation(query):
|
|
|
208
209
|
return await db.collection.find(query).to_list()
|
|
209
210
|
|
|
210
211
|
result = await get_or_set_cache(
|
|
212
|
+
redis_client=redis,
|
|
211
213
|
cache_key="user_query_abc123",
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
214
|
+
fetch_func=expensive_operation,
|
|
215
|
+
ttl=3600, # 1 hour
|
|
216
|
+
serializer=datetime_serializer
|
|
215
217
|
)
|
|
216
218
|
|
|
217
|
-
#
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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.
|
|
225
227
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
230
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
|
+
```
|
|
231
290
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
235
321
|
```
|
|
236
322
|
|
|
237
|
-
### 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
|
|
238
339
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
+
```
|
|
242
351
|
|
|
243
352
|
## Query Module
|
|
244
353
|
|
|
@@ -314,7 +423,7 @@ results = query_builder.read_query(
|
|
|
314
423
|
params=raw_params, # Will be parsed automatically using core.parsing
|
|
315
424
|
cache_client=redis_client,
|
|
316
425
|
cache_key_prefix="users",
|
|
317
|
-
use_cache=
|
|
426
|
+
use_cache=False # Note: Endpoint-level caching is now preferred over query-level caching
|
|
318
427
|
)
|
|
319
428
|
|
|
320
429
|
print(results)
|
|
@@ -442,30 +551,48 @@ class CRUD(QueryBuilder):
|
|
|
442
551
|
self.db = db[self.collection]
|
|
443
552
|
super().__init__(collection_prefix=PREFIX, relations=relations)
|
|
444
553
|
|
|
445
|
-
def read_query(self, params=None, query=None, pipeline=None, pipeline_count=None
|
|
446
|
-
# Just
|
|
554
|
+
def read_query(self, params=None, query=None, pipeline=None, pipeline_count=None):
|
|
555
|
+
# Just 4 lines! All complexity abstracted away
|
|
447
556
|
return super().read_query(
|
|
448
557
|
db_collection=self.db, params=params, query=query,
|
|
449
|
-
pipeline=pipeline, pipeline_count=pipeline_count
|
|
450
|
-
use_cache=use_cache, cache_client=redis,
|
|
451
|
-
cache_key_prefix=f"app:{self.collection}"
|
|
558
|
+
pipeline=pipeline, pipeline_count=pipeline_count
|
|
452
559
|
)
|
|
453
560
|
|
|
454
|
-
# Endpoint -
|
|
561
|
+
# Endpoint - With new endpoint-level caching!
|
|
562
|
+
from oplnk_python_utils.cache import cache_factory
|
|
455
563
|
from oplnk_python_utils.core.parsing import parse_params_to_dict
|
|
456
564
|
|
|
565
|
+
# Create reusable cache decorator for this collection
|
|
566
|
+
users_cache = cache_factory(redis_client, "myapp", "users")
|
|
567
|
+
|
|
457
568
|
@router.get("/query")
|
|
569
|
+
@users_cache(
|
|
570
|
+
endpoint_type="read",
|
|
571
|
+
tags=["users", "queries"],
|
|
572
|
+
ttl=3600 # Cache for 1 hour
|
|
573
|
+
)
|
|
458
574
|
async def get_data(request: Request):
|
|
459
575
|
# Option 1: Manual parsing (explicit control)
|
|
460
576
|
query = parse_params_to_dict(request.query_params)
|
|
461
|
-
return crud.read_query(query=query, pipeline=base_pipeline
|
|
577
|
+
return crud.read_query(query=query, pipeline=base_pipeline)
|
|
462
578
|
|
|
463
579
|
# Option 2: Automatic parsing (convenience)
|
|
464
580
|
return crud.read_query(
|
|
465
581
|
params=request.query_params, # Parsed automatically
|
|
466
|
-
pipeline=base_pipeline
|
|
467
|
-
use_cache=True
|
|
582
|
+
pipeline=base_pipeline
|
|
468
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
|
|
469
596
|
```
|
|
470
597
|
|
|
471
598
|
## Advanced Features
|
|
@@ -525,7 +652,6 @@ filters = {
|
|
|
525
652
|
- `pymongo`: MongoDB driver
|
|
526
653
|
- `redis`: Redis client
|
|
527
654
|
- `fastapi`: For HTTP exceptions
|
|
528
|
-
- `bson`: For ObjectId handling
|
|
529
655
|
|
|
530
656
|
## License
|
|
531
657
|
|
|
@@ -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
|
|
@@ -482,7 +610,6 @@ filters = {
|
|
|
482
610
|
- `pymongo`: MongoDB driver
|
|
483
611
|
- `redis`: Redis client
|
|
484
612
|
- `fastapi`: For HTTP exceptions
|
|
485
|
-
- `bson`: For ObjectId handling
|
|
486
613
|
|
|
487
614
|
## License
|
|
488
615
|
|
|
@@ -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
|
+
]
|