0
# Exception Handling
1
2
Comprehensive error handling with specialized exception types for validation errors, HTTP operation failures, authentication issues, and client request problems. The exception system provides detailed error information and supports proper error handling patterns.
3
4
## Capabilities
5
6
### Base Exception
7
8
Base exception class for all MSRest client runtime errors.
9
10
```python { .api }
11
class ClientException(Exception):
12
def __init__(self, message: str, inner_exception=None, *args, **kwargs):
13
"""
14
Base client exception.
15
16
Parameters:
17
- message: Error description
18
- inner_exception: Nested exception (optional)
19
- args: Additional positional arguments
20
- kwargs: Additional keyword arguments
21
"""
22
23
inner_exception: any
24
```
25
26
### Validation Errors
27
28
Errors that occur during request parameter validation.
29
30
```python { .api }
31
class ValidationError(ClientException):
32
def __init__(self, rule: str, target: str, value: str, *args, **kwargs):
33
"""
34
Request parameter validation error.
35
36
Parameters:
37
- rule: Validation rule that failed
38
- target: Parameter name that failed validation
39
- value: Invalid value that caused the error
40
- args: Additional arguments
41
- kwargs: Additional keyword arguments
42
"""
43
44
rule: str # Validation rule (e.g., 'required', 'min_length')
45
target: str # Parameter name
46
47
# Validation rule messages
48
_messages: dict = {
49
"min_length": "must have length greater than {!r}.",
50
"max_length": "must have length less than {!r}.",
51
"minimum_ex": "must be greater than {!r}.",
52
"maximum_ex": "must be less than {!r}.",
53
"minimum": "must be equal to or greater than {!r}.",
54
"maximum": "must be equal to or less than {!r}.",
55
"min_items": "must contain at least {!r} items.",
56
"max_items": "must contain at most {!r} items.",
57
"pattern": "must conform to the following pattern: {!r}.",
58
"unique": "must contain only unique items.",
59
"multiple": "must be a multiple of {!r}.",
60
"required": "can not be None.",
61
"type": "must be of type {!r}"
62
}
63
```
64
65
### HTTP Operation Errors
66
67
Errors that occur during HTTP operations with detailed response information.
68
69
```python { .api }
70
class HttpOperationError(ClientException):
71
def __init__(self, deserialize, response, resp_type=None, *args, **kwargs):
72
"""
73
HTTP operation error with response details.
74
75
Parameters:
76
- deserialize: Deserializer for error response
77
- response: HTTP response object
78
- resp_type: Expected response type for deserialization
79
- args: Additional arguments
80
- kwargs: Additional keyword arguments
81
"""
82
83
error: any # Deserialized error object
84
message: str # Error message
85
response: any # HTTP response object
86
87
_DEFAULT_MESSAGE: str = "Unknown error"
88
```
89
90
### Authentication Errors
91
92
Errors related to authentication and authorization.
93
94
```python { .api }
95
class AuthenticationError(ClientException):
96
"""Client request failed to authenticate."""
97
98
class TokenExpiredError(ClientException):
99
"""OAuth token expired, request failed."""
100
```
101
102
### Client Request Errors
103
104
General client request failures.
105
106
```python { .api }
107
class ClientRequestError(ClientException):
108
"""Client request failed."""
109
```
110
111
### Serialization Errors
112
113
Errors during data serialization and deserialization (imported from azure.core).
114
115
```python { .api }
116
# Imported from azure.core.exceptions
117
class SerializationError(Exception):
118
"""Error during data serialization."""
119
120
class DeserializationError(Exception):
121
"""Error during data deserialization."""
122
```
123
124
### Utility Functions
125
126
Helper functions for exception handling.
127
128
```python { .api }
129
def raise_with_traceback(exception, message="", *args, **kwargs):
130
"""
131
Raise exception preserving original traceback.
132
Must be called within an except block.
133
134
Parameters:
135
- exception: Exception class to raise
136
- message: Error message
137
- args: Additional arguments for exception
138
- kwargs: Additional keyword arguments for exception
139
"""
140
```
141
142
## Usage Examples
143
144
### Basic Exception Handling
145
146
```python
147
from msrest import ServiceClient, Configuration
148
from msrest.exceptions import (
149
ClientException, ValidationError, HttpOperationError,
150
AuthenticationError, ClientRequestError
151
)
152
153
config = Configuration(base_url='https://api.example.com')
154
client = ServiceClient(None, config)
155
156
try:
157
request = client.get('/data')
158
response = client.send(request)
159
160
except AuthenticationError as e:
161
print(f"Authentication failed: {e}")
162
# Handle authentication error (e.g., refresh token)
163
164
except HttpOperationError as e:
165
print(f"HTTP operation failed: {e}")
166
print(f"Status code: {e.response.status_code}")
167
print(f"Response body: {e.response.text}")
168
169
except ClientRequestError as e:
170
print(f"Client request error: {e}")
171
172
except ClientException as e:
173
print(f"General client error: {e}")
174
if e.inner_exception:
175
print(f"Inner exception: {e.inner_exception}")
176
```
177
178
### Validation Error Handling
179
180
```python
181
from msrest.serialization import Serializer
182
from msrest.exceptions import ValidationError
183
184
serializer = Serializer()
185
186
try:
187
# This will fail validation
188
result = serializer.url('user_id', None, 'int', required=True)
189
190
except ValidationError as e:
191
print(f"Validation failed for parameter '{e.target}'")
192
print(f"Rule: {e.rule}")
193
print(f"Message: {e}")
194
195
# Handle specific validation rules
196
if e.rule == 'required':
197
print("Parameter is required but was None")
198
elif e.rule == 'min_length':
199
print("Value is too short")
200
elif e.rule == 'pattern':
201
print("Value doesn't match required pattern")
202
```
203
204
### HTTP Error Response Handling
205
206
```python
207
from msrest.exceptions import HttpOperationError
208
from msrest.serialization import Deserializer
209
import json
210
211
def handle_api_errors(client, request):
212
"""Handle various HTTP error responses."""
213
214
try:
215
response = client.send(request)
216
return response
217
218
except HttpOperationError as e:
219
status_code = e.response.status_code
220
221
# Handle specific status codes
222
if status_code == 400:
223
print("Bad Request - check request parameters")
224
# Try to parse error details
225
try:
226
error_data = json.loads(e.response.text)
227
print(f"Error details: {error_data}")
228
except json.JSONDecodeError:
229
print(f"Raw error response: {e.response.text}")
230
231
elif status_code == 401:
232
print("Unauthorized - authentication required")
233
raise AuthenticationError("Authentication required")
234
235
elif status_code == 403:
236
print("Forbidden - insufficient permissions")
237
238
elif status_code == 404:
239
print("Not Found - resource doesn't exist")
240
241
elif status_code == 429:
242
print("Rate Limited - too many requests")
243
# Could implement retry with backoff
244
245
elif status_code >= 500:
246
print(f"Server Error ({status_code}) - service unavailable")
247
# Could implement retry logic
248
249
else:
250
print(f"Unexpected HTTP error: {status_code}")
251
252
# Re-raise the original exception
253
raise
254
255
# Usage
256
try:
257
response = handle_api_errors(client, request)
258
except Exception as e:
259
print(f"Request ultimately failed: {e}")
260
```
261
262
### Custom Exception Classes
263
264
```python
265
from msrest.exceptions import ClientException
266
267
class CustomAPIError(ClientException):
268
"""Custom exception for specific API errors."""
269
270
def __init__(self, error_code, error_message, response=None):
271
super(CustomAPIError, self).__init__(error_message)
272
self.error_code = error_code
273
self.response = response
274
275
class RateLimitError(CustomAPIError):
276
"""Rate limiting error with retry information."""
277
278
def __init__(self, retry_after=None, response=None):
279
message = f"Rate limit exceeded"
280
if retry_after:
281
message += f", retry after {retry_after} seconds"
282
283
super(RateLimitError, self).__init__("RATE_LIMIT", message, response)
284
self.retry_after = retry_after
285
286
# Usage with custom exceptions
287
def make_api_request(client, request):
288
try:
289
response = client.send(request)
290
return response
291
292
except HttpOperationError as e:
293
if e.response.status_code == 429:
294
# Extract retry-after header
295
retry_after = e.response.headers.get('Retry-After')
296
if retry_after:
297
retry_after = int(retry_after)
298
raise RateLimitError(retry_after, e.response)
299
300
# Check for custom API error format
301
try:
302
error_data = json.loads(e.response.text)
303
if 'error_code' in error_data:
304
raise CustomAPIError(
305
error_data['error_code'],
306
error_data.get('error_message', 'Unknown error'),
307
e.response
308
)
309
except (json.JSONDecodeError, KeyError):
310
pass
311
312
# Re-raise original exception if not handled
313
raise
314
315
# Handle custom exceptions
316
try:
317
response = make_api_request(client, request)
318
except RateLimitError as e:
319
print(f"Rate limited: {e}")
320
if e.retry_after:
321
print(f"Can retry after {e.retry_after} seconds")
322
except CustomAPIError as e:
323
print(f"API Error {e.error_code}: {e}")
324
```
325
326
### Serialization Error Handling
327
328
```python
329
from msrest.serialization import Serializer, Deserializer, Model
330
from msrest.exceptions import SerializationError, DeserializationError
331
332
class User(Model):
333
_attribute_map = {
334
'name': {'key': 'name', 'type': 'str'},
335
'age': {'key': 'age', 'type': 'int'}
336
}
337
338
serializer = Serializer({'User': User})
339
deserializer = Deserializer({'User': User})
340
341
# Serialization error handling
342
try:
343
# This might fail if data is invalid
344
user_data = {'name': 'John', 'age': 'invalid_age'}
345
serialized = serializer.body(user_data, 'User')
346
347
except SerializationError as e:
348
print(f"Serialization failed: {e}")
349
# Could try data type conversion or validation
350
351
# Deserialization error handling
352
try:
353
# This might fail with invalid JSON or missing fields
354
response_text = '{"name": "John", "age": "not_a_number"}'
355
user = deserializer('User', response_text)
356
357
except DeserializationError as e:
358
print(f"Deserialization failed: {e}")
359
# Could implement fallback parsing or error recovery
360
```
361
362
### Exception Context and Traceback
363
364
```python
365
import sys
366
import traceback
367
from msrest.exceptions import raise_with_traceback, ClientException
368
369
def process_with_context():
370
"""Demonstrate exception handling with context preservation."""
371
372
try:
373
# Some operation that might fail
374
result = risky_operation()
375
return result
376
377
except ValueError as e:
378
# Preserve original traceback while raising different exception
379
try:
380
raise_with_traceback(
381
ClientException,
382
f"Processing failed: {e}",
383
inner_exception=e
384
)
385
except Exception:
386
# If raise_with_traceback fails, raise normally
387
raise ClientException(f"Processing failed: {e}", inner_exception=e)
388
389
except Exception as e:
390
# Log full traceback for debugging
391
print("Unexpected error occurred:")
392
traceback.print_exc()
393
394
# Create exception with context
395
exc_type, exc_value, exc_traceback = sys.exc_info()
396
raise ClientException(
397
f"Unexpected error: {exc_type.__name__}: {exc_value}",
398
inner_exception=e
399
)
400
401
# Usage
402
try:
403
result = process_with_context()
404
except ClientException as e:
405
print(f"Process failed: {e}")
406
if e.inner_exception:
407
print(f"Root cause: {type(e.inner_exception).__name__}: {e.inner_exception}")
408
```
409
410
### Comprehensive Error Handling Strategy
411
412
```python
413
import time
414
import logging
415
from msrest.exceptions import *
416
417
# Configure logging for error tracking
418
logging.basicConfig(level=logging.INFO)
419
logger = logging.getLogger(__name__)
420
421
class APIClient:
422
"""Client with comprehensive error handling."""
423
424
def __init__(self, client):
425
self.client = client
426
self.max_retries = 3
427
self.base_retry_delay = 1
428
429
def make_request(self, request, **kwargs):
430
"""Make request with full error handling and retry logic."""
431
432
last_exception = None
433
434
for attempt in range(self.max_retries + 1):
435
try:
436
response = self.client.send(request, **kwargs)
437
438
# Success
439
logger.info(f"Request successful on attempt {attempt + 1}")
440
return response
441
442
except ValidationError as e:
443
# Validation errors are not retryable
444
logger.error(f"Validation error: {e}")
445
raise
446
447
except AuthenticationError as e:
448
# Authentication errors might be retryable if token can be refreshed
449
logger.warning(f"Authentication error: {e}")
450
if attempt < self.max_retries:
451
logger.info("Attempting to refresh authentication")
452
# Implement token refresh logic here
453
time.sleep(1)
454
continue
455
raise
456
457
except HttpOperationError as e:
458
status_code = e.response.status_code
459
460
# Client errors (4xx) - generally not retryable
461
if 400 <= status_code < 500:
462
if status_code == 429: # Rate limit - retryable
463
retry_after = e.response.headers.get('Retry-After', self.base_retry_delay)
464
if attempt < self.max_retries:
465
logger.warning(f"Rate limited, retrying after {retry_after}s")
466
time.sleep(int(retry_after))
467
continue
468
469
logger.error(f"Client error ({status_code}): {e}")
470
raise
471
472
# Server errors (5xx) - retryable
473
elif status_code >= 500:
474
if attempt < self.max_retries:
475
delay = self.base_retry_delay * (2 ** attempt) # Exponential backoff
476
logger.warning(f"Server error ({status_code}), retrying in {delay}s")
477
time.sleep(delay)
478
last_exception = e
479
continue
480
481
logger.error(f"Server error after {self.max_retries} retries: {e}")
482
raise
483
484
else:
485
logger.error(f"Unexpected HTTP status {status_code}: {e}")
486
raise
487
488
except ClientRequestError as e:
489
# Network/connection errors - retryable
490
if attempt < self.max_retries:
491
delay = self.base_retry_delay * (2 ** attempt)
492
logger.warning(f"Request error, retrying in {delay}s: {e}")
493
time.sleep(delay)
494
last_exception = e
495
continue
496
497
logger.error(f"Request failed after {self.max_retries} retries: {e}")
498
raise
499
500
except ClientException as e:
501
# General client exceptions
502
logger.error(f"Client exception: {e}")
503
if e.inner_exception:
504
logger.error(f"Inner exception: {e.inner_exception}")
505
raise
506
507
except Exception as e:
508
# Unexpected errors
509
logger.error(f"Unexpected error: {type(e).__name__}: {e}")
510
raise ClientException(f"Unexpected error: {e}", inner_exception=e)
511
512
# All retries exhausted
513
if last_exception:
514
raise last_exception
515
516
# Usage
517
api_client = APIClient(client)
518
519
try:
520
request = client.get('/api/data')
521
response = api_client.make_request(request)
522
print("Request successful")
523
524
except ValidationError as e:
525
print(f"Invalid request parameters: {e}")
526
except AuthenticationError as e:
527
print(f"Authentication failed: {e}")
528
except HttpOperationError as e:
529
print(f"HTTP operation failed: {e}")
530
except ClientException as e:
531
print(f"Client error: {e}")
532
except Exception as e:
533
print(f"Unexpected error: {e}")
534
```
535
536
## Exception Hierarchy
537
538
```
539
Exception
540
└── ClientException
541
├── ValidationError
542
├── ClientRequestError
543
├── AuthenticationError
544
│ └── TokenExpiredError
545
└── HttpOperationError
546
```
547
548
## Error Response Formats
549
550
Common error response formats that HttpOperationError can deserialize:
551
552
```python
553
# OData v4 format (used by Azure ARM)
554
{
555
"error": {
556
"code": "ResourceNotFound",
557
"message": "The requested resource was not found"
558
}
559
}
560
561
# Simple format
562
{
563
"message": "Validation failed for field 'email'"
564
}
565
566
# Detailed format
567
{
568
"error_code": "INVALID_INPUT",
569
"error_message": "Email address is required",
570
"details": {
571
"field": "email",
572
"value": null
573
}
574
}
575
```