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.
@@ -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.1"
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}")