oplnk-python-utils 1.0.1__py3-none-any.whl → 1.0.2__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.
- oplnk_python_utils/__init__.py +3 -8
- oplnk_python_utils/cache/__init__.py +12 -4
- oplnk_python_utils/cache/cache_utils.py +147 -9
- oplnk_python_utils/query/query_builder.py +295 -169
- {oplnk_python_utils-1.0.1.dist-info → oplnk_python_utils-1.0.2.dist-info}/METADATA +163 -35
- oplnk_python_utils-1.0.2.dist-info/RECORD +14 -0
- oplnk_python_utils-1.0.1.dist-info/RECORD +0 -14
- {oplnk_python_utils-1.0.1.dist-info → oplnk_python_utils-1.0.2.dist-info}/WHEEL +0 -0
- {oplnk_python_utils-1.0.1.dist-info → oplnk_python_utils-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {oplnk_python_utils-1.0.1.dist-info → oplnk_python_utils-1.0.2.dist-info}/top_level.txt +0 -0
oplnk_python_utils/__init__.py
CHANGED
|
@@ -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
|
+
]
|
|
@@ -7,6 +7,8 @@ import json
|
|
|
7
7
|
from typing import Callable, Any, Optional, Union
|
|
8
8
|
from fastapi.encoders import jsonable_encoder
|
|
9
9
|
from bson.objectid import ObjectId
|
|
10
|
+
from fastapi import Request
|
|
11
|
+
import functools
|
|
10
12
|
|
|
11
13
|
# Import core utilities
|
|
12
14
|
from ..core.data import normalize
|
|
@@ -17,6 +19,12 @@ def generate_cache_id(params: dict) -> str:
|
|
|
17
19
|
"""
|
|
18
20
|
Utility to generate a cache key hash from a dictionary.
|
|
19
21
|
Creates a consistent hash based on normalized dictionary content.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
params: Dictionary of parameters to hash
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Cache key string
|
|
20
28
|
"""
|
|
21
29
|
normalized = normalize(params)
|
|
22
30
|
encoded_data = jsonable_encoder(normalized, custom_encoder={ObjectId: str})
|
|
@@ -35,7 +43,7 @@ def get_or_set_cache(
|
|
|
35
43
|
"""
|
|
36
44
|
Retrieve data from cache if available, otherwise fetch, cache, and return it.
|
|
37
45
|
Optionally clear cache before fetching.
|
|
38
|
-
|
|
46
|
+
|
|
39
47
|
Args:
|
|
40
48
|
redis_client: Redis client instance
|
|
41
49
|
cache_key: Key to use for caching
|
|
@@ -43,20 +51,22 @@ def get_or_set_cache(
|
|
|
43
51
|
clear_cache: Pattern to clear or boolean to clear specific key
|
|
44
52
|
ttl: Time to live in seconds
|
|
45
53
|
serializer: Function to serialize data before caching
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Cached or fetched data
|
|
46
57
|
"""
|
|
47
58
|
if clear_cache:
|
|
48
59
|
invalidate_cache_keys(
|
|
49
|
-
redis_client,
|
|
50
|
-
clear_cache if isinstance(clear_cache, str) else cache_key
|
|
60
|
+
redis_client, clear_cache if isinstance(clear_cache, str) else cache_key
|
|
51
61
|
)
|
|
52
|
-
|
|
62
|
+
|
|
53
63
|
if redis_client.exists(cache_key):
|
|
54
64
|
return json.loads(redis_client.get(cache_key))
|
|
55
|
-
|
|
65
|
+
|
|
56
66
|
data = fetch_func()
|
|
57
67
|
if serializer:
|
|
58
68
|
data = serializer(data)
|
|
59
|
-
|
|
69
|
+
|
|
60
70
|
encoded_data = jsonable_encoder(data, custom_encoder={ObjectId: str})
|
|
61
71
|
redis_client.set(cache_key, json.dumps(encoded_data), ex=ttl)
|
|
62
72
|
return encoded_data
|
|
@@ -65,11 +75,11 @@ def get_or_set_cache(
|
|
|
65
75
|
def invalidate_cache_keys(redis_client, pattern: str) -> int:
|
|
66
76
|
"""
|
|
67
77
|
Utility to invalidate cache keys matching a pattern.
|
|
68
|
-
|
|
78
|
+
|
|
69
79
|
Args:
|
|
70
80
|
redis_client: Redis client instance
|
|
71
81
|
pattern: Pattern to match keys for deletion
|
|
72
|
-
|
|
82
|
+
|
|
73
83
|
Returns:
|
|
74
84
|
Number of keys deleted
|
|
75
85
|
"""
|
|
@@ -85,4 +95,132 @@ def invalidate_cache_keys(redis_client, pattern: str) -> int:
|
|
|
85
95
|
if cursor == 0:
|
|
86
96
|
break
|
|
87
97
|
|
|
88
|
-
return total_deleted
|
|
98
|
+
return total_deleted
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def build_cache_key(
|
|
102
|
+
request: Request, api_name: str, collection: str, endpoint_type: str
|
|
103
|
+
) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Builds a cache key based on the request, api name, collection, and endpoint type.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
request: FastAPI request object
|
|
109
|
+
api_name: Name of the API
|
|
110
|
+
collection: Name of the collection
|
|
111
|
+
endpoint_type: Type of endpoint
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Cache key string
|
|
115
|
+
"""
|
|
116
|
+
query_hashed = generate_cache_id(request.query_params)
|
|
117
|
+
return f"{api_name}:{collection}:{endpoint_type}:{query_hashed}"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def cache_response(
|
|
121
|
+
redis_client,
|
|
122
|
+
api_name: str,
|
|
123
|
+
collection: str,
|
|
124
|
+
endpoint_type: str,
|
|
125
|
+
tags: list[str],
|
|
126
|
+
ttl: int | None = None,
|
|
127
|
+
use_cache: bool = True,
|
|
128
|
+
):
|
|
129
|
+
"""
|
|
130
|
+
Decorator to cache the response of an endpoint.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
redis_client: Redis client instance
|
|
134
|
+
api_name: Name of the API
|
|
135
|
+
collection: Name of the collection
|
|
136
|
+
endpoint_type: Type of endpoint
|
|
137
|
+
tags: List of tags to use for caching
|
|
138
|
+
ttl: Time to live in seconds
|
|
139
|
+
use_cache: Whether to use cache
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Decorator function
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def decorator(func: Callable):
|
|
146
|
+
@functools.wraps(func)
|
|
147
|
+
async def wrapper(*args, **kwargs):
|
|
148
|
+
if not use_cache:
|
|
149
|
+
return await func(*args, **kwargs)
|
|
150
|
+
|
|
151
|
+
# Detectar el Request
|
|
152
|
+
request: Optional[Request] = kwargs.get("request")
|
|
153
|
+
if request is None:
|
|
154
|
+
for arg in args:
|
|
155
|
+
if isinstance(arg, Request):
|
|
156
|
+
request = arg
|
|
157
|
+
break
|
|
158
|
+
if request is None:
|
|
159
|
+
raise ValueError("Request object not found in endpoint parameters.")
|
|
160
|
+
|
|
161
|
+
query_params = request.query_params
|
|
162
|
+
if query_params.get("clear_cache", False):
|
|
163
|
+
invalidate_tag(redis_client, f"{collection}")
|
|
164
|
+
|
|
165
|
+
# Construir clave
|
|
166
|
+
cache_key = build_cache_key(request, api_name, collection, endpoint_type)
|
|
167
|
+
if redis_client.exists(cache_key):
|
|
168
|
+
return json.loads(redis_client.get(cache_key))
|
|
169
|
+
|
|
170
|
+
response = await func(*args, **kwargs)
|
|
171
|
+
encoded_data = jsonable_encoder(response, custom_encoder={ObjectId: str})
|
|
172
|
+
redis_client.set(cache_key, json.dumps(encoded_data), ex=ttl)
|
|
173
|
+
for tag in tags:
|
|
174
|
+
redis_client.sadd(f"tag:{tag}", cache_key)
|
|
175
|
+
|
|
176
|
+
return encoded_data
|
|
177
|
+
|
|
178
|
+
return wrapper
|
|
179
|
+
|
|
180
|
+
return decorator
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def cache_factory(redis_client, api_name: str, collection: str):
|
|
184
|
+
"""
|
|
185
|
+
Factory to create cache decorators for different endpoint types.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
redis_client: Redis client instance
|
|
189
|
+
api_name: Name of the API
|
|
190
|
+
collection: Name of the collection
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Decorator function
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
def wrapper(
|
|
197
|
+
endpoint_type: str,
|
|
198
|
+
tags: list[str],
|
|
199
|
+
ttl: int | None = None,
|
|
200
|
+
use_cache: bool = True,
|
|
201
|
+
):
|
|
202
|
+
return cache_response(
|
|
203
|
+
redis_client=redis_client,
|
|
204
|
+
api_name=api_name,
|
|
205
|
+
collection=collection,
|
|
206
|
+
endpoint_type=endpoint_type,
|
|
207
|
+
tags=tags,
|
|
208
|
+
ttl=ttl,
|
|
209
|
+
use_cache=use_cache,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
return wrapper
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def invalidate_tag(redis_client, tag: str):
|
|
216
|
+
"""
|
|
217
|
+
Utility to invalidate cache keys associated with a tag.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
redis_client: Redis client instance
|
|
221
|
+
tag: Tag to invalidate
|
|
222
|
+
"""
|
|
223
|
+
keys = redis_client.smembers(f"tag:{tag}")
|
|
224
|
+
for key in keys:
|
|
225
|
+
redis_client.delete(key)
|
|
226
|
+
redis_client.delete(f"tag:{tag}")
|