miso-client 0.1.0__py3-none-any.whl → 0.4.0__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.
Potentially problematic release.
This version of miso-client might be problematic. Click here for more details.
- miso_client/__init__.py +104 -84
- miso_client/errors.py +30 -4
- miso_client/models/__init__.py +4 -0
- miso_client/models/config.py +56 -35
- miso_client/models/error_response.py +41 -0
- miso_client/services/__init__.py +5 -5
- miso_client/services/auth.py +65 -48
- miso_client/services/cache.py +42 -41
- miso_client/services/encryption.py +18 -17
- miso_client/services/logger.py +115 -100
- miso_client/services/permission.py +27 -36
- miso_client/services/redis.py +17 -15
- miso_client/services/role.py +25 -36
- miso_client/utils/__init__.py +3 -3
- miso_client/utils/config_loader.py +24 -16
- miso_client/utils/data_masker.py +104 -33
- miso_client/utils/http_client.py +462 -254
- miso_client/utils/internal_http_client.py +471 -0
- miso_client/utils/jwt_tools.py +14 -17
- miso_client/utils/sensitive_fields_loader.py +116 -0
- {miso_client-0.1.0.dist-info → miso_client-0.4.0.dist-info}/METADATA +165 -3
- miso_client-0.4.0.dist-info/RECORD +26 -0
- miso_client-0.1.0.dist-info/RECORD +0 -23
- {miso_client-0.1.0.dist-info → miso_client-0.4.0.dist-info}/WHEEL +0 -0
- {miso_client-0.1.0.dist-info → miso_client-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {miso_client-0.1.0.dist-info → miso_client-0.4.0.dist-info}/top_level.txt +0 -0
miso_client/__init__.py
CHANGED
|
@@ -7,36 +7,38 @@ for authentication, role-based access control, permission management, and loggin
|
|
|
7
7
|
|
|
8
8
|
from typing import Any, Optional
|
|
9
9
|
|
|
10
|
+
from .errors import (
|
|
11
|
+
AuthenticationError,
|
|
12
|
+
AuthorizationError,
|
|
13
|
+
ConfigurationError,
|
|
14
|
+
ConnectionError,
|
|
15
|
+
MisoClientError,
|
|
16
|
+
)
|
|
10
17
|
from .models.config import (
|
|
11
|
-
RedisConfig,
|
|
12
|
-
MisoClientConfig,
|
|
13
|
-
UserInfo,
|
|
14
18
|
AuthResult,
|
|
15
|
-
|
|
16
|
-
RoleResult,
|
|
17
|
-
PermissionResult,
|
|
19
|
+
ClientLoggingOptions,
|
|
18
20
|
ClientTokenResponse,
|
|
21
|
+
LogEntry,
|
|
22
|
+
MisoClientConfig,
|
|
19
23
|
PerformanceMetrics,
|
|
20
|
-
|
|
24
|
+
PermissionResult,
|
|
25
|
+
RedisConfig,
|
|
26
|
+
RoleResult,
|
|
27
|
+
UserInfo,
|
|
21
28
|
)
|
|
29
|
+
from .models.error_response import ErrorResponse
|
|
22
30
|
from .services.auth import AuthService
|
|
23
|
-
from .services.
|
|
31
|
+
from .services.cache import CacheService
|
|
32
|
+
from .services.encryption import EncryptionService
|
|
33
|
+
from .services.logger import LoggerChain, LoggerService
|
|
24
34
|
from .services.permission import PermissionService
|
|
25
|
-
from .services.logger import LoggerService, LoggerChain
|
|
26
35
|
from .services.redis import RedisService
|
|
27
|
-
from .services.
|
|
28
|
-
from .services.cache import CacheService
|
|
29
|
-
from .utils.http_client import HttpClient
|
|
36
|
+
from .services.role import RoleService
|
|
30
37
|
from .utils.config_loader import load_config
|
|
31
|
-
from .
|
|
32
|
-
|
|
33
|
-
AuthenticationError,
|
|
34
|
-
AuthorizationError,
|
|
35
|
-
ConnectionError,
|
|
36
|
-
ConfigurationError,
|
|
37
|
-
)
|
|
38
|
+
from .utils.http_client import HttpClient
|
|
39
|
+
from .utils.internal_http_client import InternalHttpClient
|
|
38
40
|
|
|
39
|
-
__version__ = "0.
|
|
41
|
+
__version__ = "0.4.0"
|
|
40
42
|
__author__ = "AI Fabrix Team"
|
|
41
43
|
__license__ = "MIT"
|
|
42
44
|
|
|
@@ -44,30 +46,45 @@ __license__ = "MIT"
|
|
|
44
46
|
class MisoClient:
|
|
45
47
|
"""
|
|
46
48
|
Main MisoClient SDK class for authentication, authorization, and logging.
|
|
47
|
-
|
|
49
|
+
|
|
48
50
|
This client provides a unified interface for:
|
|
49
51
|
- Token validation and user management
|
|
50
52
|
- Role-based access control
|
|
51
53
|
- Permission management
|
|
52
54
|
- Application logging with Redis caching
|
|
53
55
|
"""
|
|
54
|
-
|
|
56
|
+
|
|
55
57
|
def __init__(self, config: MisoClientConfig):
|
|
56
58
|
"""
|
|
57
59
|
Initialize MisoClient with configuration.
|
|
58
|
-
|
|
60
|
+
|
|
59
61
|
Args:
|
|
60
62
|
config: MisoClient configuration including controller URL, client credentials, etc.
|
|
61
63
|
"""
|
|
62
64
|
self.config = config
|
|
63
|
-
|
|
65
|
+
|
|
66
|
+
# Create InternalHttpClient first (pure HTTP functionality, no logging)
|
|
67
|
+
self._internal_http_client = InternalHttpClient(config)
|
|
68
|
+
|
|
69
|
+
# Create Redis service
|
|
64
70
|
self.redis = RedisService(config.redis)
|
|
71
|
+
|
|
72
|
+
# Create LoggerService with InternalHttpClient (to avoid circular dependency)
|
|
73
|
+
# LoggerService uses InternalHttpClient for sending logs to prevent audit loops
|
|
74
|
+
self.logger = LoggerService(self._internal_http_client, self.redis)
|
|
75
|
+
|
|
76
|
+
# Create public HttpClient wrapping InternalHttpClient with logger
|
|
77
|
+
# This HttpClient adds automatic ISO 27001 compliant audit and debug logging
|
|
78
|
+
self.http_client = HttpClient(config, self.logger)
|
|
79
|
+
|
|
65
80
|
# Cache service (uses Redis if available, falls back to in-memory)
|
|
66
81
|
self.cache = CacheService(self.redis)
|
|
82
|
+
|
|
83
|
+
# Services use public HttpClient (with audit logging)
|
|
67
84
|
self.auth = AuthService(self.http_client, self.redis)
|
|
68
85
|
self.roles = RoleService(self.http_client, self.cache)
|
|
69
86
|
self.permissions = PermissionService(self.http_client, self.cache)
|
|
70
|
-
|
|
87
|
+
|
|
71
88
|
# Encryption service (reads ENCRYPTION_KEY from environment by default)
|
|
72
89
|
self.encryption = EncryptionService()
|
|
73
90
|
self.initialized = False
|
|
@@ -75,7 +92,7 @@ class MisoClient:
|
|
|
75
92
|
async def initialize(self) -> None:
|
|
76
93
|
"""
|
|
77
94
|
Initialize the client (connect to Redis if configured).
|
|
78
|
-
|
|
95
|
+
|
|
79
96
|
This method should be called before using the client. It will attempt
|
|
80
97
|
to connect to Redis if configured, but will gracefully fall back to
|
|
81
98
|
controller-only mode if Redis is unavailable.
|
|
@@ -105,16 +122,18 @@ class MisoClient:
|
|
|
105
122
|
def get_token(self, req: dict) -> str | None:
|
|
106
123
|
"""
|
|
107
124
|
Extract Bearer token from request headers.
|
|
108
|
-
|
|
125
|
+
|
|
109
126
|
Supports common request object patterns (dict with headers).
|
|
110
|
-
|
|
127
|
+
|
|
111
128
|
Args:
|
|
112
129
|
req: Request object with headers dict containing 'authorization' key
|
|
113
|
-
|
|
130
|
+
|
|
114
131
|
Returns:
|
|
115
132
|
Bearer token string or None if not found
|
|
116
133
|
"""
|
|
117
|
-
headers_obj =
|
|
134
|
+
headers_obj = (
|
|
135
|
+
req.get("headers", {}) if isinstance(req, dict) else getattr(req, "headers", {})
|
|
136
|
+
)
|
|
118
137
|
headers: dict[str, Any] = headers_obj if isinstance(headers_obj, dict) else {}
|
|
119
138
|
auth_value = headers.get("authorization") or headers.get("Authorization")
|
|
120
139
|
if not isinstance(auth_value, str):
|
|
@@ -123,16 +142,16 @@ class MisoClient:
|
|
|
123
142
|
# Support "Bearer <token>" format
|
|
124
143
|
if auth_value.startswith("Bearer "):
|
|
125
144
|
return auth_value[7:]
|
|
126
|
-
|
|
145
|
+
|
|
127
146
|
# If no Bearer prefix, assume the whole header is the token
|
|
128
147
|
return auth_value
|
|
129
148
|
|
|
130
149
|
async def get_environment_token(self) -> str:
|
|
131
150
|
"""
|
|
132
151
|
Get environment token using client credentials.
|
|
133
|
-
|
|
152
|
+
|
|
134
153
|
This is called automatically by HttpClient but can be called manually.
|
|
135
|
-
|
|
154
|
+
|
|
136
155
|
Returns:
|
|
137
156
|
Client token string
|
|
138
157
|
"""
|
|
@@ -141,12 +160,12 @@ class MisoClient:
|
|
|
141
160
|
def login(self, redirect_uri: str) -> str:
|
|
142
161
|
"""
|
|
143
162
|
Initiate login flow by redirecting to controller.
|
|
144
|
-
|
|
163
|
+
|
|
145
164
|
Returns the login URL for browser redirect or manual navigation.
|
|
146
|
-
|
|
165
|
+
|
|
147
166
|
Args:
|
|
148
167
|
redirect_uri: URI to redirect to after successful login
|
|
149
|
-
|
|
168
|
+
|
|
150
169
|
Returns:
|
|
151
170
|
Login URL string
|
|
152
171
|
"""
|
|
@@ -155,10 +174,10 @@ class MisoClient:
|
|
|
155
174
|
async def validate_token(self, token: str) -> bool:
|
|
156
175
|
"""
|
|
157
176
|
Validate token with controller.
|
|
158
|
-
|
|
177
|
+
|
|
159
178
|
Args:
|
|
160
179
|
token: JWT token to validate
|
|
161
|
-
|
|
180
|
+
|
|
162
181
|
Returns:
|
|
163
182
|
True if token is valid, False otherwise
|
|
164
183
|
"""
|
|
@@ -167,10 +186,10 @@ class MisoClient:
|
|
|
167
186
|
async def get_user(self, token: str) -> UserInfo | None:
|
|
168
187
|
"""
|
|
169
188
|
Get user information from token.
|
|
170
|
-
|
|
189
|
+
|
|
171
190
|
Args:
|
|
172
191
|
token: JWT token
|
|
173
|
-
|
|
192
|
+
|
|
174
193
|
Returns:
|
|
175
194
|
UserInfo if token is valid, None otherwise
|
|
176
195
|
"""
|
|
@@ -179,10 +198,10 @@ class MisoClient:
|
|
|
179
198
|
async def get_user_info(self, token: str) -> UserInfo | None:
|
|
180
199
|
"""
|
|
181
200
|
Get user information from GET /api/auth/user endpoint.
|
|
182
|
-
|
|
201
|
+
|
|
183
202
|
Args:
|
|
184
203
|
token: JWT token
|
|
185
|
-
|
|
204
|
+
|
|
186
205
|
Returns:
|
|
187
206
|
UserInfo if token is valid, None otherwise
|
|
188
207
|
"""
|
|
@@ -191,10 +210,10 @@ class MisoClient:
|
|
|
191
210
|
async def is_authenticated(self, token: str) -> bool:
|
|
192
211
|
"""
|
|
193
212
|
Check if user is authenticated.
|
|
194
|
-
|
|
213
|
+
|
|
195
214
|
Args:
|
|
196
215
|
token: JWT token
|
|
197
|
-
|
|
216
|
+
|
|
198
217
|
Returns:
|
|
199
218
|
True if user is authenticated, False otherwise
|
|
200
219
|
"""
|
|
@@ -209,10 +228,10 @@ class MisoClient:
|
|
|
209
228
|
async def get_roles(self, token: str) -> list[str]:
|
|
210
229
|
"""
|
|
211
230
|
Get user roles (cached in Redis if available).
|
|
212
|
-
|
|
231
|
+
|
|
213
232
|
Args:
|
|
214
233
|
token: JWT token
|
|
215
|
-
|
|
234
|
+
|
|
216
235
|
Returns:
|
|
217
236
|
List of user roles
|
|
218
237
|
"""
|
|
@@ -221,11 +240,11 @@ class MisoClient:
|
|
|
221
240
|
async def has_role(self, token: str, role: str) -> bool:
|
|
222
241
|
"""
|
|
223
242
|
Check if user has specific role.
|
|
224
|
-
|
|
243
|
+
|
|
225
244
|
Args:
|
|
226
245
|
token: JWT token
|
|
227
246
|
role: Role to check
|
|
228
|
-
|
|
247
|
+
|
|
229
248
|
Returns:
|
|
230
249
|
True if user has the role, False otherwise
|
|
231
250
|
"""
|
|
@@ -234,11 +253,11 @@ class MisoClient:
|
|
|
234
253
|
async def has_any_role(self, token: str, roles: list[str]) -> bool:
|
|
235
254
|
"""
|
|
236
255
|
Check if user has any of the specified roles.
|
|
237
|
-
|
|
256
|
+
|
|
238
257
|
Args:
|
|
239
258
|
token: JWT token
|
|
240
259
|
roles: List of roles to check
|
|
241
|
-
|
|
260
|
+
|
|
242
261
|
Returns:
|
|
243
262
|
True if user has any of the roles, False otherwise
|
|
244
263
|
"""
|
|
@@ -247,11 +266,11 @@ class MisoClient:
|
|
|
247
266
|
async def has_all_roles(self, token: str, roles: list[str]) -> bool:
|
|
248
267
|
"""
|
|
249
268
|
Check if user has all of the specified roles.
|
|
250
|
-
|
|
269
|
+
|
|
251
270
|
Args:
|
|
252
271
|
token: JWT token
|
|
253
272
|
roles: List of roles to check
|
|
254
|
-
|
|
273
|
+
|
|
255
274
|
Returns:
|
|
256
275
|
True if user has all roles, False otherwise
|
|
257
276
|
"""
|
|
@@ -260,10 +279,10 @@ class MisoClient:
|
|
|
260
279
|
async def refresh_roles(self, token: str) -> list[str]:
|
|
261
280
|
"""
|
|
262
281
|
Force refresh roles from controller (bypass cache).
|
|
263
|
-
|
|
282
|
+
|
|
264
283
|
Args:
|
|
265
284
|
token: JWT token
|
|
266
|
-
|
|
285
|
+
|
|
267
286
|
Returns:
|
|
268
287
|
Fresh list of user roles
|
|
269
288
|
"""
|
|
@@ -272,10 +291,10 @@ class MisoClient:
|
|
|
272
291
|
async def get_permissions(self, token: str) -> list[str]:
|
|
273
292
|
"""
|
|
274
293
|
Get user permissions (cached in Redis if available).
|
|
275
|
-
|
|
294
|
+
|
|
276
295
|
Args:
|
|
277
296
|
token: JWT token
|
|
278
|
-
|
|
297
|
+
|
|
279
298
|
Returns:
|
|
280
299
|
List of user permissions
|
|
281
300
|
"""
|
|
@@ -284,11 +303,11 @@ class MisoClient:
|
|
|
284
303
|
async def has_permission(self, token: str, permission: str) -> bool:
|
|
285
304
|
"""
|
|
286
305
|
Check if user has specific permission.
|
|
287
|
-
|
|
306
|
+
|
|
288
307
|
Args:
|
|
289
308
|
token: JWT token
|
|
290
309
|
permission: Permission to check
|
|
291
|
-
|
|
310
|
+
|
|
292
311
|
Returns:
|
|
293
312
|
True if user has the permission, False otherwise
|
|
294
313
|
"""
|
|
@@ -297,11 +316,11 @@ class MisoClient:
|
|
|
297
316
|
async def has_any_permission(self, token: str, permissions: list[str]) -> bool:
|
|
298
317
|
"""
|
|
299
318
|
Check if user has any of the specified permissions.
|
|
300
|
-
|
|
319
|
+
|
|
301
320
|
Args:
|
|
302
321
|
token: JWT token
|
|
303
322
|
permissions: List of permissions to check
|
|
304
|
-
|
|
323
|
+
|
|
305
324
|
Returns:
|
|
306
325
|
True if user has any of the permissions, False otherwise
|
|
307
326
|
"""
|
|
@@ -310,11 +329,11 @@ class MisoClient:
|
|
|
310
329
|
async def has_all_permissions(self, token: str, permissions: list[str]) -> bool:
|
|
311
330
|
"""
|
|
312
331
|
Check if user has all of the specified permissions.
|
|
313
|
-
|
|
332
|
+
|
|
314
333
|
Args:
|
|
315
334
|
token: JWT token
|
|
316
335
|
permissions: List of permissions to check
|
|
317
|
-
|
|
336
|
+
|
|
318
337
|
Returns:
|
|
319
338
|
True if user has all permissions, False otherwise
|
|
320
339
|
"""
|
|
@@ -323,10 +342,10 @@ class MisoClient:
|
|
|
323
342
|
async def refresh_permissions(self, token: str) -> list[str]:
|
|
324
343
|
"""
|
|
325
344
|
Force refresh permissions from controller (bypass cache).
|
|
326
|
-
|
|
345
|
+
|
|
327
346
|
Args:
|
|
328
347
|
token: JWT token
|
|
329
|
-
|
|
348
|
+
|
|
330
349
|
Returns:
|
|
331
350
|
Fresh list of user permissions
|
|
332
351
|
"""
|
|
@@ -335,7 +354,7 @@ class MisoClient:
|
|
|
335
354
|
async def clear_permissions_cache(self, token: str) -> None:
|
|
336
355
|
"""
|
|
337
356
|
Clear cached permissions for a user.
|
|
338
|
-
|
|
357
|
+
|
|
339
358
|
Args:
|
|
340
359
|
token: JWT token
|
|
341
360
|
"""
|
|
@@ -347,7 +366,7 @@ class MisoClient:
|
|
|
347
366
|
def log(self) -> LoggerService:
|
|
348
367
|
"""
|
|
349
368
|
Get logger service for application logging.
|
|
350
|
-
|
|
369
|
+
|
|
351
370
|
Returns:
|
|
352
371
|
LoggerService instance
|
|
353
372
|
"""
|
|
@@ -358,12 +377,12 @@ class MisoClient:
|
|
|
358
377
|
def encrypt(self, plaintext: str) -> str:
|
|
359
378
|
"""
|
|
360
379
|
Encrypt sensitive data.
|
|
361
|
-
|
|
380
|
+
|
|
362
381
|
Convenience method that delegates to encryption service.
|
|
363
|
-
|
|
382
|
+
|
|
364
383
|
Args:
|
|
365
384
|
plaintext: Plain text string to encrypt
|
|
366
|
-
|
|
385
|
+
|
|
367
386
|
Returns:
|
|
368
387
|
Base64-encoded encrypted string
|
|
369
388
|
"""
|
|
@@ -372,12 +391,12 @@ class MisoClient:
|
|
|
372
391
|
def decrypt(self, encrypted_text: str) -> str:
|
|
373
392
|
"""
|
|
374
393
|
Decrypt sensitive data.
|
|
375
|
-
|
|
394
|
+
|
|
376
395
|
Convenience method that delegates to encryption service.
|
|
377
|
-
|
|
396
|
+
|
|
378
397
|
Args:
|
|
379
398
|
encrypted_text: Base64-encoded encrypted string
|
|
380
|
-
|
|
399
|
+
|
|
381
400
|
Returns:
|
|
382
401
|
Decrypted plain text string
|
|
383
402
|
"""
|
|
@@ -388,12 +407,12 @@ class MisoClient:
|
|
|
388
407
|
async def cache_get(self, key: str) -> Optional[Any]:
|
|
389
408
|
"""
|
|
390
409
|
Get cached value.
|
|
391
|
-
|
|
410
|
+
|
|
392
411
|
Convenience method that delegates to cache service.
|
|
393
|
-
|
|
412
|
+
|
|
394
413
|
Args:
|
|
395
414
|
key: Cache key
|
|
396
|
-
|
|
415
|
+
|
|
397
416
|
Returns:
|
|
398
417
|
Cached value if found, None otherwise
|
|
399
418
|
"""
|
|
@@ -402,14 +421,14 @@ class MisoClient:
|
|
|
402
421
|
async def cache_set(self, key: str, value: Any, ttl: int) -> bool:
|
|
403
422
|
"""
|
|
404
423
|
Set cached value with TTL.
|
|
405
|
-
|
|
424
|
+
|
|
406
425
|
Convenience method that delegates to cache service.
|
|
407
|
-
|
|
426
|
+
|
|
408
427
|
Args:
|
|
409
428
|
key: Cache key
|
|
410
429
|
value: Value to cache
|
|
411
430
|
ttl: Time to live in seconds
|
|
412
|
-
|
|
431
|
+
|
|
413
432
|
Returns:
|
|
414
433
|
True if successful, False otherwise
|
|
415
434
|
"""
|
|
@@ -418,12 +437,12 @@ class MisoClient:
|
|
|
418
437
|
async def cache_delete(self, key: str) -> bool:
|
|
419
438
|
"""
|
|
420
439
|
Delete cached value.
|
|
421
|
-
|
|
440
|
+
|
|
422
441
|
Convenience method that delegates to cache service.
|
|
423
|
-
|
|
442
|
+
|
|
424
443
|
Args:
|
|
425
444
|
key: Cache key
|
|
426
|
-
|
|
445
|
+
|
|
427
446
|
Returns:
|
|
428
447
|
True if deleted, False otherwise
|
|
429
448
|
"""
|
|
@@ -432,7 +451,7 @@ class MisoClient:
|
|
|
432
451
|
async def cache_clear(self) -> None:
|
|
433
452
|
"""
|
|
434
453
|
Clear all cached values.
|
|
435
|
-
|
|
454
|
+
|
|
436
455
|
Convenience method that delegates to cache service.
|
|
437
456
|
"""
|
|
438
457
|
await self.cache.clear()
|
|
@@ -442,7 +461,7 @@ class MisoClient:
|
|
|
442
461
|
def get_config(self) -> MisoClientConfig:
|
|
443
462
|
"""
|
|
444
463
|
Get current configuration.
|
|
445
|
-
|
|
464
|
+
|
|
446
465
|
Returns:
|
|
447
466
|
Copy of current configuration
|
|
448
467
|
"""
|
|
@@ -451,7 +470,7 @@ class MisoClient:
|
|
|
451
470
|
def is_redis_connected(self) -> bool:
|
|
452
471
|
"""
|
|
453
472
|
Check if Redis is connected.
|
|
454
|
-
|
|
473
|
+
|
|
455
474
|
Returns:
|
|
456
475
|
True if Redis is connected, False otherwise
|
|
457
476
|
"""
|
|
@@ -471,6 +490,7 @@ __all__ = [
|
|
|
471
490
|
"ClientTokenResponse",
|
|
472
491
|
"PerformanceMetrics",
|
|
473
492
|
"ClientLoggingOptions",
|
|
493
|
+
"ErrorResponse",
|
|
474
494
|
"AuthService",
|
|
475
495
|
"RoleService",
|
|
476
496
|
"PermissionService",
|
miso_client/errors.py
CHANGED
|
@@ -4,41 +4,67 @@ SDK exceptions and error handling.
|
|
|
4
4
|
This module defines custom exceptions for the MisoClient SDK.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..models.error_response import ErrorResponse
|
|
11
|
+
|
|
7
12
|
|
|
8
13
|
class MisoClientError(Exception):
|
|
9
14
|
"""Base exception for MisoClient SDK errors."""
|
|
10
|
-
|
|
11
|
-
def __init__(
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
message: str,
|
|
19
|
+
status_code: int | None = None,
|
|
20
|
+
error_body: dict | None = None,
|
|
21
|
+
error_response: "ErrorResponse | None" = None,
|
|
22
|
+
):
|
|
12
23
|
"""
|
|
13
24
|
Initialize MisoClient error.
|
|
14
|
-
|
|
25
|
+
|
|
15
26
|
Args:
|
|
16
27
|
message: Error message
|
|
17
28
|
status_code: HTTP status code if applicable
|
|
18
29
|
error_body: Sanitized error response body (secrets masked)
|
|
30
|
+
error_response: Structured error response object (RFC 7807-style)
|
|
19
31
|
"""
|
|
20
32
|
super().__init__(message)
|
|
21
33
|
self.message = message
|
|
22
34
|
self.status_code = status_code
|
|
23
35
|
self.error_body = error_body if error_body is not None else None
|
|
36
|
+
self.error_response = error_response
|
|
37
|
+
|
|
38
|
+
# Enhance message with structured error information if available
|
|
39
|
+
if error_response and error_response.errors:
|
|
40
|
+
if len(error_response.errors) == 1:
|
|
41
|
+
self.message = error_response.errors[0]
|
|
42
|
+
else:
|
|
43
|
+
self.message = f"{error_response.title}: {'; '.join(error_response.errors)}"
|
|
44
|
+
# Override status_code from structured response if available
|
|
45
|
+
if error_response.statusCode:
|
|
46
|
+
self.status_code = error_response.statusCode
|
|
24
47
|
|
|
25
48
|
|
|
26
49
|
class AuthenticationError(MisoClientError):
|
|
27
50
|
"""Raised when authentication fails."""
|
|
51
|
+
|
|
28
52
|
pass
|
|
29
53
|
|
|
30
54
|
|
|
31
55
|
class AuthorizationError(MisoClientError):
|
|
32
56
|
"""Raised when authorization check fails."""
|
|
57
|
+
|
|
33
58
|
pass
|
|
34
59
|
|
|
35
60
|
|
|
36
61
|
class ConnectionError(MisoClientError):
|
|
37
62
|
"""Raised when connection to controller or Redis fails."""
|
|
63
|
+
|
|
38
64
|
pass
|
|
39
65
|
|
|
40
66
|
|
|
41
67
|
class ConfigurationError(MisoClientError):
|
|
42
68
|
"""Raised when configuration is invalid."""
|
|
43
|
-
pass
|
|
44
69
|
|
|
70
|
+
pass
|