nd-sdk 1.0.0__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.
- nd_sdk-1.0.0/PKG-INFO +16 -0
- nd_sdk-1.0.0/nd_sdk/__init__.py +14 -0
- nd_sdk-1.0.0/nd_sdk/caching/__init__.py +0 -0
- nd_sdk-1.0.0/nd_sdk/caching/base.py +9 -0
- nd_sdk-1.0.0/nd_sdk/caching/factory.py +11 -0
- nd_sdk-1.0.0/nd_sdk/caching/in_memory_cache.py +12 -0
- nd_sdk-1.0.0/nd_sdk/caching/redis_cache.py +425 -0
- nd_sdk-1.0.0/nd_sdk/config/__init__.py +0 -0
- nd_sdk-1.0.0/nd_sdk/config/data_config.py +42 -0
- nd_sdk-1.0.0/nd_sdk/config/factory.py +8 -0
- nd_sdk-1.0.0/nd_sdk/config/loaders.py +11 -0
- nd_sdk-1.0.0/nd_sdk/config/settings.py +5 -0
- nd_sdk-1.0.0/nd_sdk/observability/__init__.py +0 -0
- nd_sdk-1.0.0/nd_sdk/observability/context_manager.py +703 -0
- nd_sdk-1.0.0/nd_sdk/observability/factory.py +25 -0
- nd_sdk-1.0.0/nd_sdk/observability/logging/__init__.py +0 -0
- nd_sdk-1.0.0/nd_sdk/observability/logging/base.py +24 -0
- nd_sdk-1.0.0/nd_sdk/observability/logging/cloudwatch_logger.py +23 -0
- nd_sdk-1.0.0/nd_sdk/observability/logging/otel_logger.py +601 -0
- nd_sdk-1.0.0/nd_sdk/observability/logging/std_logger.py +23 -0
- nd_sdk-1.0.0/nd_sdk/observability/metrics/__init__.py +0 -0
- nd_sdk-1.0.0/nd_sdk/observability/metrics/base.py +50 -0
- nd_sdk-1.0.0/nd_sdk/observability/metrics/otel_metrics.py +611 -0
- nd_sdk-1.0.0/nd_sdk/observability/otel_exporter.py +372 -0
- nd_sdk-1.0.0/nd_sdk/observability/tracing/__init__.py +0 -0
- nd_sdk-1.0.0/nd_sdk/observability/tracing/base.py +144 -0
- nd_sdk-1.0.0/nd_sdk/observability/tracing/otel_tracer.py +1135 -0
- nd_sdk-1.0.0/nd_sdk/storage/__init__.py +0 -0
- nd_sdk-1.0.0/nd_sdk/storage/base.py +22 -0
- nd_sdk-1.0.0/nd_sdk/storage/factory.py +19 -0
- nd_sdk-1.0.0/nd_sdk/storage/storage_clients/__init__.py +0 -0
- nd_sdk-1.0.0/nd_sdk/storage/storage_clients/azure_client.py +57 -0
- nd_sdk-1.0.0/nd_sdk/storage/storage_clients/minio_client.py +49 -0
- nd_sdk-1.0.0/nd_sdk/storage/storage_clients/s3_client.py +52 -0
- nd_sdk-1.0.0/nd_sdk/storage/storage_factory/__init__.py +0 -0
- nd_sdk-1.0.0/nd_sdk/storage/storage_factory/azure_factory.py +24 -0
- nd_sdk-1.0.0/nd_sdk/storage/storage_factory/minio_factory.py +24 -0
- nd_sdk-1.0.0/nd_sdk/storage/storage_factory/s3_factory.py +24 -0
- nd_sdk-1.0.0/nd_sdk/utils/__init__.py +0 -0
- nd_sdk-1.0.0/nd_sdk/utils/decorators.py +8 -0
- nd_sdk-1.0.0/nd_sdk/utils/exceptions.py +2 -0
- nd_sdk-1.0.0/nd_sdk/utils/file_handler.py +154 -0
- nd_sdk-1.0.0/nd_sdk/utils/singleton.py +36 -0
- nd_sdk-1.0.0/nd_sdk/utils/string_utils.py +7 -0
- nd_sdk-1.0.0/nd_sdk/web/__init__.py +0 -0
- nd_sdk-1.0.0/nd_sdk/web/base.py +12 -0
- nd_sdk-1.0.0/nd_sdk/web/flask_wrapper.py +61 -0
- nd_sdk-1.0.0/nd_sdk.egg-info/PKG-INFO +16 -0
- nd_sdk-1.0.0/nd_sdk.egg-info/SOURCES.txt +52 -0
- nd_sdk-1.0.0/nd_sdk.egg-info/dependency_links.txt +1 -0
- nd_sdk-1.0.0/nd_sdk.egg-info/requires.txt +9 -0
- nd_sdk-1.0.0/nd_sdk.egg-info/top_level.txt +1 -0
- nd_sdk-1.0.0/setup.cfg +4 -0
- nd_sdk-1.0.0/setup.py +22 -0
nd_sdk-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: nd-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Unified SDK for Observability, Caching and Storage
|
|
5
|
+
Author: Jeyesh Vishnu
|
|
6
|
+
Author-email: jeyesh.vishnu@novacisdigital.com
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Requires-Dist: azure-storage-blob~=12.16.0
|
|
9
|
+
Requires-Dist: azure-core~=1.29.5
|
|
10
|
+
Requires-Dist: Flask~=2.1.2
|
|
11
|
+
Requires-Dist: redis~=6.1.1
|
|
12
|
+
Requires-Dist: Werkzeug~=2.2.2
|
|
13
|
+
Requires-Dist: opentelemetry-api>=1.20.0
|
|
14
|
+
Requires-Dist: opentelemetry-sdk>=1.20.0
|
|
15
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20.0
|
|
16
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Your Company SDK - Unified Framework for Observability, Storage & Caching
|
|
3
|
+
"""
|
|
4
|
+
from .storage.factory import get_storage
|
|
5
|
+
from .caching.factory import get_cache
|
|
6
|
+
from .observability.factory import get_logger, get_tracer, get_metrics
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"get_logger",
|
|
10
|
+
"get_tracer",
|
|
11
|
+
"get_metrics",
|
|
12
|
+
"get_storage",
|
|
13
|
+
"get_cache",
|
|
14
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .in_memory_cache import InMemoryCache
|
|
2
|
+
from .redis_cache import RedisCache
|
|
3
|
+
from ..utils.singleton import singleton
|
|
4
|
+
|
|
5
|
+
@singleton
|
|
6
|
+
def get_cache(provider="redis", environ="dev"):
|
|
7
|
+
if provider == "memory":
|
|
8
|
+
return InMemoryCache()
|
|
9
|
+
if provider == "redis":
|
|
10
|
+
return RedisCache(environment=environ)
|
|
11
|
+
raise ValueError(f"Unknown cache provider: {provider}")
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
from .base import CacheProvider
|
|
2
|
+
from ..observability.factory import get_logger
|
|
3
|
+
from ..config.loaders import load_env
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
6
|
+
import redis
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RedisCache(CacheProvider):
|
|
12
|
+
"""
|
|
13
|
+
Enhanced Redis cache provider with pattern matching support.
|
|
14
|
+
|
|
15
|
+
Features:
|
|
16
|
+
- Wildcard pattern matching with SCAN for memory-efficient key retrieval
|
|
17
|
+
- Support for multiple wildcards in a single pattern
|
|
18
|
+
- Configurable batch size for SCAN operations
|
|
19
|
+
- Automatic serialization/deserialization
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
DEFAULT_TTL = 3600 # 1 hour
|
|
23
|
+
SCAN_BATCH_SIZE = 1000
|
|
24
|
+
|
|
25
|
+
def __init__(self, scan_batch_size: int = SCAN_BATCH_SIZE, environment: str = "dev"):
|
|
26
|
+
"""
|
|
27
|
+
Initialize Redis cache with configurable scan batch size.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
scan_batch_size: Number of keys to retrieve per SCAN iteration
|
|
31
|
+
"""
|
|
32
|
+
self.cache = redis.Redis(**load_env("cache_"))
|
|
33
|
+
self.serializer = JSONSerializer()
|
|
34
|
+
self.logger = get_logger.get()
|
|
35
|
+
self.scan_batch_size = scan_batch_size
|
|
36
|
+
self.environment = environment
|
|
37
|
+
|
|
38
|
+
def get(self, key: str) -> Any:
|
|
39
|
+
"""
|
|
40
|
+
Retrieve a value from cache.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
key: Cache key
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Deserialized value or None if not found
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
data = self.cache.get(key)
|
|
50
|
+
if data is None:
|
|
51
|
+
self.logger.debug(f"Cache miss for key: {key}")
|
|
52
|
+
return None
|
|
53
|
+
return self.serializer.deserialize(data)
|
|
54
|
+
except Exception as e:
|
|
55
|
+
self.logger.error(f"Error retrieving key '{key}': {e}")
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
def set(self, key: str, value: Any, ttl: Optional[int] = None) -> bool:
|
|
59
|
+
"""
|
|
60
|
+
Store a value in cache with optional TTL.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
key: Cache key
|
|
64
|
+
value: Value to store
|
|
65
|
+
ttl: Time to live in seconds (None for no expiration)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
True if successful, False otherwise
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
serialized = self.serializer.serialize(value)
|
|
72
|
+
if ttl is not None:
|
|
73
|
+
result = self.cache.setex(key, ttl, serialized)
|
|
74
|
+
else:
|
|
75
|
+
result = self.cache.set(key, serialized)
|
|
76
|
+
|
|
77
|
+
self.logger.debug(f"[REDIS] SET {key} = {value} (TTL: {ttl})")
|
|
78
|
+
return bool(result)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
self.logger.error(f"Error setting key '{key}': {e}")
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
def delete(self, key: str) -> bool:
|
|
84
|
+
"""
|
|
85
|
+
Delete a key from cache.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
key: Cache key to delete
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
True if key was deleted, False otherwise
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
result = self.cache.delete(key)
|
|
95
|
+
self.logger.debug(f"[REDIS] DELETE {key}")
|
|
96
|
+
return bool(result)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
self.logger.error(f"Error deleting key '{key}': {e}")
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
def build_pattern(self, pattern_dict: Dict[str, str]) -> str:
|
|
102
|
+
"""
|
|
103
|
+
Build a Redis pattern from a dictionary.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
pattern_dict: Dictionary with keys as pattern components.
|
|
107
|
+
Use "*" for wildcard matching.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Redis SCAN-compatible pattern string
|
|
111
|
+
'*:Cassandra'
|
|
112
|
+
"""
|
|
113
|
+
pattern_dict['environment'] = self.environment
|
|
114
|
+
return ":".join(str(v) for v in pattern_dict.values())
|
|
115
|
+
|
|
116
|
+
def scan_keys(self, pattern: str) -> List[str]:
|
|
117
|
+
"""
|
|
118
|
+
Scan for keys matching a pattern using SCAN command.
|
|
119
|
+
|
|
120
|
+
Uses SCAN instead of KEYS for production safety - it doesn't
|
|
121
|
+
block the Redis server and works incrementally.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
pattern: Redis pattern (supports * and ? wildcards)
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
List of matching keys (as strings)
|
|
128
|
+
"""
|
|
129
|
+
pattern = f"{self.environment}:{pattern}"
|
|
130
|
+
matched_keys = []
|
|
131
|
+
cursor = 0
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
while True:
|
|
135
|
+
cursor, keys = self.cache.scan(
|
|
136
|
+
cursor=cursor,
|
|
137
|
+
match=pattern,
|
|
138
|
+
count=self.scan_batch_size
|
|
139
|
+
)
|
|
140
|
+
# Decode bytes to strings
|
|
141
|
+
matched_keys.extend([k.decode('utf-8') if isinstance(k, bytes) else k
|
|
142
|
+
for k in keys])
|
|
143
|
+
|
|
144
|
+
if cursor == 0:
|
|
145
|
+
break
|
|
146
|
+
|
|
147
|
+
self.logger.info(f"Found {len(matched_keys)} keys matching pattern: {pattern}")
|
|
148
|
+
return matched_keys
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
self.logger.error(f"Error scanning keys with pattern '{pattern}': {e}")
|
|
152
|
+
return []
|
|
153
|
+
|
|
154
|
+
def get_multiple_by_pattern(self, pattern: str) -> Dict[str, Any]:
|
|
155
|
+
"""
|
|
156
|
+
Retrieve all key-value pairs matching a pattern.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
pattern: Redis pattern with wildcards
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Dictionary mapping keys to deserialized values
|
|
163
|
+
"""
|
|
164
|
+
pattern = f"{self.environment}:{pattern}"
|
|
165
|
+
results = {}
|
|
166
|
+
keys = self.scan_keys(pattern)
|
|
167
|
+
|
|
168
|
+
if not keys:
|
|
169
|
+
return results
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
# Use pipeline for efficient multi-get
|
|
173
|
+
pipeline = self.cache.pipeline()
|
|
174
|
+
for key in keys:
|
|
175
|
+
pipeline.get(key)
|
|
176
|
+
|
|
177
|
+
values = pipeline.execute()
|
|
178
|
+
|
|
179
|
+
for key, value in zip(keys, values):
|
|
180
|
+
if value is not None:
|
|
181
|
+
try:
|
|
182
|
+
results[key] = self.serializer.deserialize(value)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
self.logger.warning(f"Failed to deserialize key '{key}': {e}")
|
|
185
|
+
results[key] = None
|
|
186
|
+
|
|
187
|
+
return results
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
self.logger.error(f"Error retrieving values for pattern '{pattern}': {e}")
|
|
191
|
+
return {}
|
|
192
|
+
|
|
193
|
+
def get_by_pattern(self, pattern: str) -> Optional[Any]:
|
|
194
|
+
"""
|
|
195
|
+
Retrieve all key-value pairs matching a pattern.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
pattern: Redis pattern with wildcards
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Dictionary mapping keys to deserialized values
|
|
202
|
+
"""
|
|
203
|
+
pattern = f"{self.environment}:{pattern}"
|
|
204
|
+
keys = self.scan_keys(pattern)
|
|
205
|
+
|
|
206
|
+
if not keys:
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
# Use pipeline for efficient multi-get
|
|
211
|
+
pipeline = self.cache.pipeline()
|
|
212
|
+
for key in keys:
|
|
213
|
+
pipeline.get(key)
|
|
214
|
+
|
|
215
|
+
values = pipeline.execute()
|
|
216
|
+
|
|
217
|
+
for key, value in zip(keys, values):
|
|
218
|
+
if value is not None:
|
|
219
|
+
try:
|
|
220
|
+
return self.serializer.deserialize(value)
|
|
221
|
+
except Exception as e:
|
|
222
|
+
self.logger.warning(f"Failed to deserialize key '{key}': {e}")
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
except Exception as e:
|
|
228
|
+
self.logger.error(f"Error retrieving values for pattern '{pattern}': {e}")
|
|
229
|
+
return {}
|
|
230
|
+
|
|
231
|
+
def get_by_dict(self, pattern_dict: Dict[str, str], multiple: bool = False) -> Any:
|
|
232
|
+
"""
|
|
233
|
+
Retrieve key-value pairs using a pattern dictionary.
|
|
234
|
+
|
|
235
|
+
Convenience method that builds the pattern and retrieves values.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
pattern_dict: Dictionary defining the pattern
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Dictionary mapping keys to values
|
|
242
|
+
:param pattern_dict:
|
|
243
|
+
:param multiple:
|
|
244
|
+
"""
|
|
245
|
+
pattern_dict['environment'] = self.environment
|
|
246
|
+
pattern = self.build_pattern(pattern_dict)
|
|
247
|
+
if multiple:
|
|
248
|
+
return self.get_multiple_by_pattern(pattern)
|
|
249
|
+
return self.get_by_pattern(pattern)
|
|
250
|
+
|
|
251
|
+
def delete_by_pattern(self, pattern: str, batch_size: int = 100) -> int:
|
|
252
|
+
"""
|
|
253
|
+
Delete all keys matching a pattern.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
pattern: Redis pattern with wildcards
|
|
257
|
+
batch_size: Number of keys to delete per batch
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Number of keys deleted
|
|
261
|
+
"""
|
|
262
|
+
pattern = f"{self.environment}:{pattern}"
|
|
263
|
+
keys = self.scan_keys(pattern)
|
|
264
|
+
|
|
265
|
+
if not keys:
|
|
266
|
+
return 0
|
|
267
|
+
|
|
268
|
+
deleted_count = 0
|
|
269
|
+
try:
|
|
270
|
+
# Delete in batches using pipeline
|
|
271
|
+
for i in range(0, len(keys), batch_size):
|
|
272
|
+
batch = keys[i:i + batch_size]
|
|
273
|
+
pipeline = self.cache.pipeline()
|
|
274
|
+
for key in batch:
|
|
275
|
+
pipeline.delete(key)
|
|
276
|
+
results = pipeline.execute()
|
|
277
|
+
deleted_count += sum(results)
|
|
278
|
+
|
|
279
|
+
self.logger.info(f"Deleted {deleted_count} keys matching pattern: {pattern}")
|
|
280
|
+
return deleted_count
|
|
281
|
+
|
|
282
|
+
except Exception as e:
|
|
283
|
+
self.logger.error(f"Error deleting keys with pattern '{pattern}': {e}")
|
|
284
|
+
return deleted_count
|
|
285
|
+
|
|
286
|
+
def exists(self, key: str) -> bool:
|
|
287
|
+
"""
|
|
288
|
+
Check if a key exists in cache.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
key: Cache key to check
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
True if key exists, False otherwise
|
|
295
|
+
"""
|
|
296
|
+
try:
|
|
297
|
+
return bool(self.cache.exists(key))
|
|
298
|
+
except Exception as e:
|
|
299
|
+
self.logger.error(f"Error checking existence of key '{key}': {e}")
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
def get_ttl(self, key: str) -> Optional[int]:
|
|
303
|
+
"""
|
|
304
|
+
Get the remaining time to live for a key.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
key: Cache key
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
TTL in seconds, -1 if no expiration, None if key doesn't exist
|
|
311
|
+
"""
|
|
312
|
+
try:
|
|
313
|
+
ttl = self.cache.ttl(key)
|
|
314
|
+
if ttl == -2: # Key doesn't exist
|
|
315
|
+
return None
|
|
316
|
+
return ttl
|
|
317
|
+
except Exception as e:
|
|
318
|
+
self.logger.error(f"Error getting TTL for key '{key}': {e}")
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class BaseSerializer(ABC):
|
|
323
|
+
"""Abstract base class for serializers."""
|
|
324
|
+
|
|
325
|
+
@abstractmethod
|
|
326
|
+
def serialize(self, obj: Any) -> bytes:
|
|
327
|
+
"""Serialize an object to bytes."""
|
|
328
|
+
pass
|
|
329
|
+
|
|
330
|
+
@abstractmethod
|
|
331
|
+
def deserialize(self, data: bytes) -> Any:
|
|
332
|
+
"""Deserialize bytes to an object."""
|
|
333
|
+
pass
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class JSONSerializer(BaseSerializer):
|
|
337
|
+
"""JSON serializer for common data types."""
|
|
338
|
+
|
|
339
|
+
def __init__(self):
|
|
340
|
+
self.logger = get_logger.get()
|
|
341
|
+
|
|
342
|
+
def serialize(self, obj: Any) -> bytes:
|
|
343
|
+
"""
|
|
344
|
+
Serialize object to JSON bytes.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
obj: Object to serialize
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
UTF-8 encoded JSON bytes
|
|
351
|
+
|
|
352
|
+
Raises:
|
|
353
|
+
TypeError: If object is not JSON serializable
|
|
354
|
+
ValueError: If serialization fails
|
|
355
|
+
"""
|
|
356
|
+
try:
|
|
357
|
+
return json.dumps(obj, ensure_ascii=False).encode('utf-8')
|
|
358
|
+
except (TypeError, ValueError) as e:
|
|
359
|
+
self.logger.error(f"JSON serialization error: {e}")
|
|
360
|
+
raise
|
|
361
|
+
|
|
362
|
+
def deserialize(self, data: bytes) -> Any:
|
|
363
|
+
"""
|
|
364
|
+
Deserialize JSON bytes to object.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
data: UTF-8 encoded JSON bytes
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
Deserialized Python object
|
|
371
|
+
|
|
372
|
+
Raises:
|
|
373
|
+
json.JSONDecodeError: If data is not valid JSON
|
|
374
|
+
UnicodeDecodeError: If data is not valid UTF-8
|
|
375
|
+
"""
|
|
376
|
+
if data is None:
|
|
377
|
+
return None
|
|
378
|
+
|
|
379
|
+
try:
|
|
380
|
+
if isinstance(data, bytes):
|
|
381
|
+
return json.loads(data.decode('utf-8'))
|
|
382
|
+
return json.loads(data)
|
|
383
|
+
except (json.JSONDecodeError, UnicodeDecodeError) as e:
|
|
384
|
+
self.logger.error(f"JSON deserialization error: {e}")
|
|
385
|
+
raise
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# Usage example (for documentation purposes)
|
|
389
|
+
"""
|
|
390
|
+
# Initialize cache
|
|
391
|
+
cache = RedisCache()
|
|
392
|
+
|
|
393
|
+
# 1. Basic operations
|
|
394
|
+
cache.set("user:123", {"name": "John", "age": 30}, ttl=3600)
|
|
395
|
+
user = cache.get("user:123")
|
|
396
|
+
|
|
397
|
+
# 2. Pattern matching with wildcards
|
|
398
|
+
pattern_dict = {
|
|
399
|
+
"environment": "dev",
|
|
400
|
+
"service": "*", # Match any service
|
|
401
|
+
"provider": "Cassandra",
|
|
402
|
+
"category": "provider",
|
|
403
|
+
"identifier": "*" # Match any identifier
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
# Get all matching keys and values
|
|
407
|
+
results = cache.get_by_dict(pattern_dict)
|
|
408
|
+
for key, value in results.items():
|
|
409
|
+
print(f"{key}: {value}")
|
|
410
|
+
|
|
411
|
+
# 3. Direct pattern usage
|
|
412
|
+
results = cache.get_by_pattern("*:Cassandra:*:file")
|
|
413
|
+
|
|
414
|
+
# 4. Get only keys (no values)
|
|
415
|
+
keys = cache.scan_keys("dev:api-*:*:provider:config")
|
|
416
|
+
|
|
417
|
+
# 5. Delete by pattern
|
|
418
|
+
deleted = cache.delete_by_pattern("temp:*:*")
|
|
419
|
+
print(f"Deleted {deleted} temporary keys")
|
|
420
|
+
|
|
421
|
+
# 6. Check operations
|
|
422
|
+
if cache.exists("user:123"):
|
|
423
|
+
ttl = cache.get_ttl("user:123")
|
|
424
|
+
print(f"Key expires in {ttl} seconds")
|
|
425
|
+
"""
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
class ServiceConfig:
|
|
4
|
+
"""Service configuration - set by application at startup"""
|
|
5
|
+
service_name: str = os.getenv("service_name", "IDP Service")
|
|
6
|
+
service_version: str = os.getenv("service_version", "1.0.0")
|
|
7
|
+
environment: str = os.getenv("service_environment", "Dev")
|
|
8
|
+
container_logs: bool = os.getenv("container_logs", True)
|
|
9
|
+
export_logs: bool = os.getenv("export_logs", True)
|
|
10
|
+
|
|
11
|
+
def __init__(self, **kwargs):
|
|
12
|
+
for key, value in kwargs.items():
|
|
13
|
+
setattr(self, key, value)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LogConfig:
|
|
17
|
+
log_level: str = os.getenv("log_level", "INFO")
|
|
18
|
+
log_exporter_format: str = os.getenv("log_exporter_format", "otlp")
|
|
19
|
+
log_exporter_protocol: str = os.getenv("log_exporter_protocol", "http")
|
|
20
|
+
log_provider: str = os.getenv("log_provider", "otel")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ExporterConfig:
|
|
24
|
+
exporter_mode: str = os.getenv("exporter_mode", "common")
|
|
25
|
+
log_endpoint: str = os.getenv("log_endpoint", "http://localhost:4318/v1/logs")
|
|
26
|
+
trace_endpoint: str = os.getenv("trace_endpoint", "http://localhost:4318/v1/traces")
|
|
27
|
+
metrics_endpoint: str = os.getenv("metrics_endpoint", "http://localhost:4318/v1/metrics")
|
|
28
|
+
|
|
29
|
+
class TraceConfig:
|
|
30
|
+
trace_provider: str = os.getenv("trace_provider", "otel")
|
|
31
|
+
trace_exporter_format: str = os.getenv("trace_exporter_format", "otlp")
|
|
32
|
+
trace_exporter_protocol: str = os.getenv("trace_exporter_protocol", "http")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MetricsConfig:
|
|
36
|
+
metrics_provider: str = os.getenv("metrics_provider", "otel")
|
|
37
|
+
metrics_exporter_format: str = os.getenv("metrics_exporter_format", "otlp")
|
|
38
|
+
metrics_exporter_protocol: str = os.getenv("metrics_exporter_protocol", "http")
|
|
39
|
+
metrics_export_interval_millis: int = int(
|
|
40
|
+
os.getenv("metrics_export_interval_millis", "60000")) # 60 seconds default
|
|
41
|
+
environment: str = os.getenv("environment", "Dev")
|
|
42
|
+
max_workers: int = int(os.getenv("max_metrics_worker", 4))
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from ..utils.string_utils import to_snake_case
|
|
4
|
+
|
|
5
|
+
def load_json(path):
|
|
6
|
+
with open(path) as f:
|
|
7
|
+
return json.load(f)
|
|
8
|
+
|
|
9
|
+
def load_env(prefix="SDK_"):
|
|
10
|
+
prefix = to_snake_case(prefix)
|
|
11
|
+
return {f"{k[len(prefix):]}".lower(): v for k, v in os.environ.items() if k.startswith(prefix.upper())}
|
|
File without changes
|