miso-client 0.1.0__py3-none-any.whl → 3.7.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.
- miso_client/__init__.py +523 -130
- miso_client/api/__init__.py +35 -0
- miso_client/api/auth_api.py +367 -0
- miso_client/api/logs_api.py +91 -0
- miso_client/api/permissions_api.py +88 -0
- miso_client/api/roles_api.py +88 -0
- miso_client/api/types/__init__.py +75 -0
- miso_client/api/types/auth_types.py +183 -0
- miso_client/api/types/logs_types.py +71 -0
- miso_client/api/types/permissions_types.py +31 -0
- miso_client/api/types/roles_types.py +31 -0
- miso_client/errors.py +30 -4
- miso_client/models/__init__.py +4 -0
- miso_client/models/config.py +275 -72
- miso_client/models/error_response.py +39 -0
- miso_client/models/filter.py +255 -0
- miso_client/models/pagination.py +44 -0
- miso_client/models/sort.py +25 -0
- miso_client/services/__init__.py +6 -5
- miso_client/services/auth.py +496 -87
- miso_client/services/cache.py +42 -41
- miso_client/services/encryption.py +18 -17
- miso_client/services/logger.py +467 -328
- miso_client/services/logger_chain.py +288 -0
- miso_client/services/permission.py +130 -67
- miso_client/services/redis.py +28 -23
- miso_client/services/role.py +145 -62
- miso_client/utils/__init__.py +3 -3
- miso_client/utils/audit_log_queue.py +222 -0
- miso_client/utils/auth_strategy.py +88 -0
- miso_client/utils/auth_utils.py +65 -0
- miso_client/utils/circuit_breaker.py +125 -0
- miso_client/utils/client_token_manager.py +244 -0
- miso_client/utils/config_loader.py +88 -17
- miso_client/utils/controller_url_resolver.py +80 -0
- miso_client/utils/data_masker.py +104 -33
- miso_client/utils/environment_token.py +126 -0
- miso_client/utils/error_utils.py +216 -0
- miso_client/utils/fastapi_endpoints.py +166 -0
- miso_client/utils/filter.py +364 -0
- miso_client/utils/filter_applier.py +143 -0
- miso_client/utils/filter_parser.py +110 -0
- miso_client/utils/flask_endpoints.py +169 -0
- miso_client/utils/http_client.py +494 -262
- miso_client/utils/http_client_logging.py +352 -0
- miso_client/utils/http_client_logging_helpers.py +197 -0
- miso_client/utils/http_client_query_helpers.py +138 -0
- miso_client/utils/http_error_handler.py +92 -0
- miso_client/utils/http_log_formatter.py +115 -0
- miso_client/utils/http_log_masker.py +203 -0
- miso_client/utils/internal_http_client.py +435 -0
- miso_client/utils/jwt_tools.py +125 -16
- miso_client/utils/logger_helpers.py +206 -0
- miso_client/utils/logging_helpers.py +70 -0
- miso_client/utils/origin_validator.py +128 -0
- miso_client/utils/pagination.py +275 -0
- miso_client/utils/request_context.py +285 -0
- miso_client/utils/sensitive_fields_loader.py +116 -0
- miso_client/utils/sort.py +116 -0
- miso_client/utils/token_utils.py +114 -0
- miso_client/utils/url_validator.py +66 -0
- miso_client/utils/user_token_refresh.py +245 -0
- miso_client-3.7.2.dist-info/METADATA +1021 -0
- miso_client-3.7.2.dist-info/RECORD +68 -0
- miso_client-0.1.0.dist-info/METADATA +0 -551
- miso_client-0.1.0.dist-info/RECORD +0 -23
- {miso_client-0.1.0.dist-info → miso_client-3.7.2.dist-info}/WHEEL +0 -0
- {miso_client-0.1.0.dist-info → miso_client-3.7.2.dist-info}/licenses/LICENSE +0 -0
- {miso_client-0.1.0.dist-info → miso_client-3.7.2.dist-info}/top_level.txt +0 -0
miso_client/services/cache.py
CHANGED
|
@@ -8,17 +8,18 @@ in-memory TTL-based caching when Redis is unavailable.
|
|
|
8
8
|
|
|
9
9
|
import json
|
|
10
10
|
import time
|
|
11
|
-
from typing import Any,
|
|
11
|
+
from typing import Any, Dict, Optional, Tuple
|
|
12
|
+
|
|
12
13
|
from ..services.redis import RedisService
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class CacheService:
|
|
16
17
|
"""Cache service with Redis and in-memory TTL fallback."""
|
|
17
|
-
|
|
18
|
+
|
|
18
19
|
def __init__(self, redis: Optional[RedisService] = None):
|
|
19
20
|
"""
|
|
20
21
|
Initialize cache service.
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
Args:
|
|
23
24
|
redis: Optional RedisService instance. If provided, Redis will be used
|
|
24
25
|
as primary cache with in-memory as fallback. If None, only
|
|
@@ -29,31 +30,32 @@ class CacheService:
|
|
|
29
30
|
self._memory_cache: Dict[str, Tuple[Any, float]] = {}
|
|
30
31
|
# Cleanup threshold: clean expired entries if cache exceeds this size
|
|
31
32
|
self._cleanup_threshold = 1000
|
|
32
|
-
|
|
33
|
+
|
|
33
34
|
def _is_expired(self, expiration: float) -> bool:
|
|
34
35
|
"""Check if entry has expired."""
|
|
35
36
|
return time.time() > expiration
|
|
36
|
-
|
|
37
|
+
|
|
37
38
|
def _cleanup_expired(self) -> None:
|
|
38
39
|
"""Remove expired entries from memory cache."""
|
|
39
40
|
if len(self._memory_cache) <= self._cleanup_threshold:
|
|
40
41
|
return
|
|
41
|
-
|
|
42
|
+
|
|
42
43
|
expired_keys = [
|
|
43
|
-
key
|
|
44
|
+
key
|
|
45
|
+
for key, (_, expiration) in self._memory_cache.items()
|
|
44
46
|
if self._is_expired(expiration)
|
|
45
47
|
]
|
|
46
|
-
|
|
48
|
+
|
|
47
49
|
for key in expired_keys:
|
|
48
50
|
del self._memory_cache[key]
|
|
49
|
-
|
|
51
|
+
|
|
50
52
|
def _serialize_value(self, value: Any) -> str:
|
|
51
53
|
"""
|
|
52
54
|
Serialize value to JSON string.
|
|
53
|
-
|
|
55
|
+
|
|
54
56
|
Args:
|
|
55
57
|
value: Value to serialize (can be any JSON-serializable type)
|
|
56
|
-
|
|
58
|
+
|
|
57
59
|
Returns:
|
|
58
60
|
JSON string representation
|
|
59
61
|
"""
|
|
@@ -62,23 +64,23 @@ class CacheService:
|
|
|
62
64
|
if isinstance(value, str):
|
|
63
65
|
return value
|
|
64
66
|
return json.dumps(value)
|
|
65
|
-
|
|
67
|
+
|
|
66
68
|
# For complex types, use JSON serialization with a marker
|
|
67
69
|
return json.dumps({"__cached_value__": value})
|
|
68
|
-
|
|
70
|
+
|
|
69
71
|
def _deserialize_value(self, value_str: str) -> Any:
|
|
70
72
|
"""
|
|
71
73
|
Deserialize JSON string back to original value.
|
|
72
|
-
|
|
74
|
+
|
|
73
75
|
Args:
|
|
74
76
|
value_str: JSON string to deserialize
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
Returns:
|
|
77
79
|
Deserialized value
|
|
78
80
|
"""
|
|
79
81
|
if not value_str:
|
|
80
82
|
return None
|
|
81
|
-
|
|
83
|
+
|
|
82
84
|
try:
|
|
83
85
|
# Try to parse as JSON
|
|
84
86
|
parsed = json.loads(value_str)
|
|
@@ -90,16 +92,16 @@ class CacheService:
|
|
|
90
92
|
except (json.JSONDecodeError, TypeError):
|
|
91
93
|
# If JSON parsing fails, assume it's a plain string
|
|
92
94
|
return value_str
|
|
93
|
-
|
|
95
|
+
|
|
94
96
|
async def get(self, key: str) -> Optional[Any]:
|
|
95
97
|
"""
|
|
96
98
|
Get cached value.
|
|
97
|
-
|
|
99
|
+
|
|
98
100
|
Checks Redis first (if available), then falls back to in-memory cache.
|
|
99
|
-
|
|
101
|
+
|
|
100
102
|
Args:
|
|
101
103
|
key: Cache key
|
|
102
|
-
|
|
104
|
+
|
|
103
105
|
Returns:
|
|
104
106
|
Cached value if found, None otherwise
|
|
105
107
|
"""
|
|
@@ -112,7 +114,7 @@ class CacheService:
|
|
|
112
114
|
except Exception:
|
|
113
115
|
# Redis operation failed, fall through to memory cache
|
|
114
116
|
pass
|
|
115
|
-
|
|
117
|
+
|
|
116
118
|
# Fallback to in-memory cache
|
|
117
119
|
if key in self._memory_cache:
|
|
118
120
|
value, expiration = self._memory_cache[key]
|
|
@@ -121,26 +123,26 @@ class CacheService:
|
|
|
121
123
|
else:
|
|
122
124
|
# Entry expired, remove it
|
|
123
125
|
del self._memory_cache[key]
|
|
124
|
-
|
|
126
|
+
|
|
125
127
|
return None
|
|
126
|
-
|
|
128
|
+
|
|
127
129
|
async def set(self, key: str, value: Any, ttl: int) -> bool:
|
|
128
130
|
"""
|
|
129
131
|
Set cached value with TTL.
|
|
130
|
-
|
|
132
|
+
|
|
131
133
|
Stores in both Redis (if available) and in-memory cache.
|
|
132
|
-
|
|
134
|
+
|
|
133
135
|
Args:
|
|
134
136
|
key: Cache key
|
|
135
137
|
value: Value to cache (any JSON-serializable type)
|
|
136
138
|
ttl: Time to live in seconds
|
|
137
|
-
|
|
139
|
+
|
|
138
140
|
Returns:
|
|
139
141
|
True if successful, False otherwise
|
|
140
142
|
"""
|
|
141
143
|
serialized_value = self._serialize_value(value)
|
|
142
144
|
success = False
|
|
143
|
-
|
|
145
|
+
|
|
144
146
|
# Store in Redis if available
|
|
145
147
|
if self.redis and self.redis.is_connected():
|
|
146
148
|
try:
|
|
@@ -148,57 +150,56 @@ class CacheService:
|
|
|
148
150
|
except Exception:
|
|
149
151
|
# Redis operation failed, continue to memory cache
|
|
150
152
|
pass
|
|
151
|
-
|
|
153
|
+
|
|
152
154
|
# Also store in memory cache
|
|
153
155
|
expiration = time.time() + ttl
|
|
154
156
|
self._memory_cache[key] = (value, expiration)
|
|
155
|
-
|
|
157
|
+
|
|
156
158
|
# Cleanup expired entries periodically
|
|
157
159
|
self._cleanup_expired()
|
|
158
|
-
|
|
160
|
+
|
|
159
161
|
return success or True # Return True if at least memory cache succeeded
|
|
160
|
-
|
|
162
|
+
|
|
161
163
|
async def delete(self, key: str) -> bool:
|
|
162
164
|
"""
|
|
163
165
|
Delete cached value.
|
|
164
|
-
|
|
166
|
+
|
|
165
167
|
Deletes from both Redis (if available) and in-memory cache.
|
|
166
|
-
|
|
168
|
+
|
|
167
169
|
Args:
|
|
168
170
|
key: Cache key
|
|
169
|
-
|
|
171
|
+
|
|
170
172
|
Returns:
|
|
171
173
|
True if deleted from at least one cache, False otherwise
|
|
172
174
|
"""
|
|
173
175
|
deleted = False
|
|
174
|
-
|
|
176
|
+
|
|
175
177
|
# Delete from Redis if available
|
|
176
178
|
if self.redis and self.redis.is_connected():
|
|
177
179
|
try:
|
|
178
180
|
deleted = await self.redis.delete(key)
|
|
179
181
|
except Exception:
|
|
180
182
|
pass
|
|
181
|
-
|
|
183
|
+
|
|
182
184
|
# Delete from memory cache
|
|
183
185
|
if key in self._memory_cache:
|
|
184
186
|
del self._memory_cache[key]
|
|
185
187
|
deleted = True
|
|
186
|
-
|
|
188
|
+
|
|
187
189
|
return deleted
|
|
188
|
-
|
|
190
|
+
|
|
189
191
|
async def clear(self) -> None:
|
|
190
192
|
"""
|
|
191
193
|
Clear all cached values.
|
|
192
|
-
|
|
194
|
+
|
|
193
195
|
Clears both Redis (if available) and in-memory cache.
|
|
194
196
|
Note: Redis clear operation only clears keys with the configured prefix,
|
|
195
197
|
not the entire Redis database.
|
|
196
198
|
"""
|
|
197
199
|
# Clear memory cache
|
|
198
200
|
self._memory_cache.clear()
|
|
199
|
-
|
|
201
|
+
|
|
200
202
|
# For Redis, we would need to delete all keys with the prefix
|
|
201
203
|
# This is more complex and potentially dangerous, so we'll skip it
|
|
202
204
|
# Users should use delete() for specific keys or clear Redis manually
|
|
203
205
|
# if needed
|
|
204
|
-
|
|
@@ -6,36 +6,38 @@ in the application. It supports reading the encryption key from environment vari
|
|
|
6
6
|
or accepting it as a constructor parameter.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import os
|
|
10
9
|
import base64
|
|
10
|
+
import os
|
|
11
11
|
from typing import Optional
|
|
12
|
+
|
|
12
13
|
from cryptography.fernet import Fernet
|
|
14
|
+
|
|
13
15
|
from ..errors import ConfigurationError
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class EncryptionService:
|
|
17
19
|
"""Service for encrypting/decrypting sensitive data using Fernet encryption."""
|
|
18
|
-
|
|
20
|
+
|
|
19
21
|
def __init__(self, encryption_key: Optional[str] = None):
|
|
20
22
|
"""
|
|
21
23
|
Initialize encryption service with key from environment or parameter.
|
|
22
|
-
|
|
24
|
+
|
|
23
25
|
Args:
|
|
24
26
|
encryption_key: Optional encryption key. If not provided, reads from EXTENSION_KEY env var.
|
|
25
27
|
If provided, overrides environment variable.
|
|
26
|
-
|
|
28
|
+
|
|
27
29
|
Raises:
|
|
28
30
|
ConfigurationError: If encryption key is not found or invalid
|
|
29
31
|
"""
|
|
30
32
|
# Use provided key, or fall back to environment variable
|
|
31
33
|
key = encryption_key or os.environ.get("ENCRYPTION_KEY")
|
|
32
|
-
|
|
34
|
+
|
|
33
35
|
if not key:
|
|
34
36
|
raise ConfigurationError(
|
|
35
37
|
"ENCRYPTION_KEY not found. Either set ENCRYPTION_KEY environment variable "
|
|
36
38
|
"or pass encryption_key parameter to EncryptionService constructor."
|
|
37
39
|
)
|
|
38
|
-
|
|
40
|
+
|
|
39
41
|
try:
|
|
40
42
|
# Fernet.generate_key() returns bytes, but env vars are strings
|
|
41
43
|
# Convert string to bytes if needed
|
|
@@ -45,49 +47,48 @@ class EncryptionService:
|
|
|
45
47
|
raise ConfigurationError(
|
|
46
48
|
f"Failed to initialize encryption service with provided key: {str(e)}"
|
|
47
49
|
) from e
|
|
48
|
-
|
|
50
|
+
|
|
49
51
|
def encrypt(self, plaintext: str) -> str:
|
|
50
52
|
"""
|
|
51
53
|
Encrypt sensitive data.
|
|
52
|
-
|
|
54
|
+
|
|
53
55
|
Args:
|
|
54
56
|
plaintext: Plain text string to encrypt
|
|
55
|
-
|
|
57
|
+
|
|
56
58
|
Returns:
|
|
57
59
|
Base64-encoded encrypted string
|
|
58
|
-
|
|
60
|
+
|
|
59
61
|
Raises:
|
|
60
62
|
ValueError: If encryption fails
|
|
61
63
|
"""
|
|
62
64
|
if not plaintext:
|
|
63
65
|
return ""
|
|
64
|
-
|
|
66
|
+
|
|
65
67
|
try:
|
|
66
68
|
encrypted = self.fernet.encrypt(plaintext.encode())
|
|
67
69
|
return base64.b64encode(encrypted).decode()
|
|
68
70
|
except Exception as e:
|
|
69
71
|
raise ValueError(f"Failed to encrypt data: {str(e)}") from e
|
|
70
|
-
|
|
72
|
+
|
|
71
73
|
def decrypt(self, encrypted_text: str) -> str:
|
|
72
74
|
"""
|
|
73
75
|
Decrypt sensitive data.
|
|
74
|
-
|
|
76
|
+
|
|
75
77
|
Args:
|
|
76
78
|
encrypted_text: Base64-encoded encrypted string
|
|
77
|
-
|
|
79
|
+
|
|
78
80
|
Returns:
|
|
79
81
|
Decrypted plain text string
|
|
80
|
-
|
|
82
|
+
|
|
81
83
|
Raises:
|
|
82
84
|
ValueError: If decryption fails or data is invalid
|
|
83
85
|
"""
|
|
84
86
|
if not encrypted_text:
|
|
85
87
|
return ""
|
|
86
|
-
|
|
88
|
+
|
|
87
89
|
try:
|
|
88
90
|
decoded = base64.b64decode(encrypted_text.encode())
|
|
89
91
|
decrypted = self.fernet.decrypt(decoded)
|
|
90
92
|
return decrypted.decode()
|
|
91
93
|
except Exception as e:
|
|
92
94
|
raise ValueError(f"Failed to decrypt data: {str(e)}") from e
|
|
93
|
-
|