0
# Error Handling
1
2
Comprehensive exception hierarchy for robust error handling across all MinIO operations. The SDK provides specific exception types for different error conditions, enabling precise error handling and debugging.
3
4
## Capabilities
5
6
### Base Exception Classes
7
8
Foundation exception classes providing common functionality for all MinIO-related errors.
9
10
```python { .api }
11
class MinioException(Exception):
12
"""Base exception class for all MinIO operations."""
13
def __init__(self, message: str) -> None:
14
"""
15
Initialize MinIO exception.
16
17
Args:
18
message: Error message describing the exception
19
"""
20
21
def __str__(self) -> str:
22
"""Return string representation of the exception."""
23
```
24
25
### S3 Operation Errors
26
27
Exceptions for S3 API operation failures with detailed error information from the server.
28
29
```python { .api }
30
class S3Error(MinioException):
31
"""Exception raised when S3 operation fails with error response."""
32
def __init__(
33
self,
34
code: str,
35
message: str,
36
resource: str | None = None,
37
request_id: str | None = None,
38
host_id: str | None = None,
39
response: urllib3.HTTPResponse | None = None,
40
bucket_name: str | None = None,
41
object_name: str | None = None
42
) -> None:
43
"""
44
Initialize S3 error.
45
46
Args:
47
code: S3 error code (e.g., "NoSuchBucket", "AccessDenied")
48
message: Human-readable error message
49
resource: S3 resource that caused the error
50
request_id: AWS request ID for debugging
51
host_id: AWS host ID for debugging
52
response: Original HTTP response object
53
bucket_name: Bucket involved in the operation
54
object_name: Object involved in the operation
55
"""
56
57
code: str
58
message: str
59
resource: str | None
60
request_id: str | None
61
host_id: str | None
62
response: urllib3.HTTPResponse | None
63
bucket_name: str | None
64
object_name: str | None
65
66
@classmethod
67
def fromxml(cls, response: urllib3.HTTPResponse) -> S3Error:
68
"""
69
Create S3Error from XML error response.
70
71
Args:
72
response: HTTP response containing XML error
73
74
Returns:
75
S3Error instance parsed from XML
76
"""
77
78
def copy(self, code: str | None = None, message: str | None = None) -> S3Error:
79
"""
80
Create copy of S3Error with modified code/message.
81
82
Args:
83
code: New error code (optional)
84
message: New error message (optional)
85
86
Returns:
87
New S3Error instance with updated values
88
"""
89
```
90
91
### HTTP Server Errors
92
93
Exceptions for HTTP-level errors including network issues and server failures.
94
95
```python { .api }
96
class ServerError(MinioException):
97
"""Exception raised when S3 service returns HTTP server error."""
98
def __init__(self, message: str, status_code: int) -> None:
99
"""
100
Initialize server error.
101
102
Args:
103
message: Error message
104
status_code: HTTP status code (5xx series)
105
"""
106
107
status_code: int
108
109
def __str__(self) -> str:
110
"""Return formatted error message with status code."""
111
```
112
113
### Response Format Errors
114
115
Exceptions for malformed or unexpected server responses.
116
117
```python { .api }
118
class InvalidResponseError(MinioException):
119
"""Exception raised when server returns non-XML response."""
120
def __init__(
121
self,
122
code: int | None = None,
123
content_type: str | None = None,
124
body: str | None = None
125
) -> None:
126
"""
127
Initialize invalid response error.
128
129
Args:
130
code: HTTP status code
131
content_type: Response content type
132
body: Response body content
133
"""
134
135
code: int | None
136
content_type: str | None
137
body: str | None
138
```
139
140
### Admin Operation Errors
141
142
Exceptions specific to MinIO administrative operations.
143
144
```python { .api }
145
class MinioAdminException(MinioException):
146
"""Exception raised for MinIO admin API execution errors."""
147
def __init__(self, code: int, body: str) -> None:
148
"""
149
Initialize admin exception.
150
151
Args:
152
code: HTTP status code
153
body: Response body containing error details
154
"""
155
156
code: int
157
body: str
158
```
159
160
## Common S3 Error Codes
161
162
The SDK handles numerous standard S3 error codes. Understanding these helps in implementing appropriate error handling strategies.
163
164
### Access and Authentication Errors
165
166
```python
167
# Common error codes for access issues:
168
# - "AccessDenied": Insufficient permissions
169
# - "InvalidAccessKeyId": Invalid access key
170
# - "SignatureDoesNotMatch": Invalid secret key or signature
171
# - "TokenRefreshRequired": STS token needs refresh
172
# - "ExpiredToken": STS token has expired
173
```
174
175
### Bucket-Related Errors
176
177
```python
178
# Common bucket operation error codes:
179
# - "NoSuchBucket": Bucket doesn't exist
180
# - "BucketAlreadyExists": Bucket name already taken
181
# - "BucketNotEmpty": Cannot delete non-empty bucket
182
# - "InvalidBucketName": Bucket name violates naming rules
183
```
184
185
### Object-Related Errors
186
187
```python
188
# Common object operation error codes:
189
# - "NoSuchKey": Object doesn't exist
190
# - "InvalidObjectName": Object name violates naming rules
191
# - "EntityTooLarge": Object exceeds size limits
192
# - "InvalidPart": Multipart upload part is invalid
193
# - "InvalidPartOrder": Multipart parts not in order
194
```
195
196
### Precondition and Constraint Errors
197
198
```python
199
# Common constraint error codes:
200
# - "PreconditionFailed": If-Match/If-None-Match condition failed
201
# - "NotModified": Object not modified since specified date
202
# - "InvalidRange": Byte range is invalid
203
# - "RequestTimeout": Request took too long
204
```
205
206
## Usage Examples
207
208
### Basic Error Handling
209
210
```python
211
from minio import Minio, S3Error, ServerError, InvalidResponseError
212
213
client = Minio("localhost:9000", "minio", "minio123")
214
215
try:
216
# Attempt bucket operation
217
client.make_bucket("my-bucket")
218
print("Bucket created successfully")
219
220
except S3Error as e:
221
if e.code == "BucketAlreadyExists":
222
print("Bucket already exists, continuing...")
223
elif e.code == "AccessDenied":
224
print("Access denied. Check credentials and permissions.")
225
else:
226
print(f"S3 error: {e.code} - {e.message}")
227
228
except ServerError as e:
229
print(f"Server error {e.status_code}: {e}")
230
231
except InvalidResponseError as e:
232
print(f"Invalid response: {e.code} - {e.content_type}")
233
234
except Exception as e:
235
print(f"Unexpected error: {e}")
236
```
237
238
### Specific Error Code Handling
239
240
```python
241
def safe_bucket_operations(client: Minio, bucket_name: str):
242
"""Demonstrate handling specific S3 error scenarios."""
243
244
try:
245
# Check if bucket exists
246
if not client.bucket_exists(bucket_name):
247
client.make_bucket(bucket_name)
248
print(f"Created bucket: {bucket_name}")
249
else:
250
print(f"Bucket {bucket_name} already exists")
251
252
except S3Error as e:
253
if e.code == "NoSuchBucket":
254
print("Bucket doesn't exist and cannot be created")
255
elif e.code == "BucketAlreadyOwnedByYou":
256
print("Bucket already owned by you")
257
elif e.code == "InvalidBucketName":
258
print(f"Invalid bucket name: {bucket_name}")
259
else:
260
print(f"Bucket operation failed: {e.code} - {e.message}")
261
raise
262
263
try:
264
# List objects in bucket
265
objects = client.list_objects(bucket_name)
266
for obj in objects:
267
print(f"Object: {obj.object_name}")
268
269
except S3Error as e:
270
if e.code == "AccessDenied":
271
print("Cannot list objects: access denied")
272
else:
273
print(f"List operation failed: {e.code}")
274
raise
275
```
276
277
### Multipart Upload Error Handling
278
279
```python
280
import io
281
from minio.error import S3Error
282
283
def robust_multipart_upload(client: Minio, bucket_name: str, object_name: str, file_path: str):
284
"""Handle errors in multipart upload with proper cleanup."""
285
286
upload_id = None
287
try:
288
# Initiate multipart upload
289
upload_id = client.initiate_multipart_upload(bucket_name, object_name)
290
291
parts = []
292
part_number = 1
293
294
with open(file_path, 'rb') as f:
295
while True:
296
chunk = f.read(5 * 1024 * 1024) # 5MB chunks
297
if not chunk:
298
break
299
300
try:
301
part = client.upload_part(
302
bucket_name, object_name, upload_id,
303
part_number, io.BytesIO(chunk), len(chunk)
304
)
305
parts.append(part)
306
part_number += 1
307
308
except S3Error as e:
309
if e.code == "InvalidPart":
310
print(f"Invalid part {part_number}, retrying...")
311
continue # Retry this part
312
else:
313
raise # Re-raise for cleanup
314
315
# Complete upload
316
result = client.complete_multipart_upload(
317
bucket_name, object_name, upload_id, parts
318
)
319
print(f"Upload completed: {result.etag}")
320
return result
321
322
except S3Error as e:
323
print(f"Multipart upload failed: {e.code} - {e.message}")
324
325
# Cleanup incomplete upload
326
if upload_id:
327
try:
328
client.abort_multipart_upload(bucket_name, object_name, upload_id)
329
print("Aborted incomplete multipart upload")
330
except S3Error as abort_error:
331
print(f"Failed to abort upload: {abort_error.code}")
332
333
raise
334
335
except Exception as e:
336
print(f"Unexpected error during upload: {e}")
337
338
# Cleanup on any error
339
if upload_id:
340
try:
341
client.abort_multipart_upload(bucket_name, object_name, upload_id)
342
except:
343
pass # Best effort cleanup
344
345
raise
346
```
347
348
### Credential and Authentication Error Handling
349
350
```python
351
from minio.credentials import ChainedProvider, EnvAWSProvider, StaticProvider
352
353
def create_resilient_client(endpoint: str) -> Minio:
354
"""Create client with robust credential handling."""
355
356
# Try multiple credential sources
357
providers = [
358
EnvAWSProvider(),
359
StaticProvider("fallback-key", "fallback-secret")
360
]
361
362
credential_chain = ChainedProvider(providers)
363
364
try:
365
client = Minio(endpoint, credentials=credential_chain)
366
367
# Test credentials by listing buckets
368
buckets = client.list_buckets()
369
print(f"Successfully authenticated, found {len(buckets)} buckets")
370
return client
371
372
except S3Error as e:
373
if e.code in ["AccessDenied", "InvalidAccessKeyId", "SignatureDoesNotMatch"]:
374
print(f"Authentication failed: {e.code}")
375
print("Please check your credentials")
376
elif e.code == "ExpiredToken":
377
print("STS token has expired, refresh required")
378
else:
379
print(f"Authentication error: {e.code} - {e.message}")
380
raise
381
382
except Exception as e:
383
print(f"Client creation failed: {e}")
384
raise
385
```
386
387
### Admin Operation Error Handling
388
389
```python
390
from minio import MinioAdmin
391
from minio.error import MinioAdminException
392
from minio.credentials import StaticProvider
393
394
def safe_admin_operations():
395
"""Handle MinIO admin operation errors."""
396
397
try:
398
admin = MinioAdmin(
399
"localhost:9000",
400
credentials=StaticProvider("admin", "password")
401
)
402
403
# Add user with error handling
404
try:
405
result = admin.user_add("newuser", "password123")
406
print(f"User created: {result}")
407
408
except MinioAdminException as e:
409
if e.code == 409: # Conflict - user already exists
410
print("User already exists")
411
elif e.code == 403: # Forbidden - insufficient permissions
412
print("Insufficient permissions to create user")
413
else:
414
print(f"User creation failed: {e.code} - {e.body}")
415
416
# Set policy with error handling
417
try:
418
policy_json = '{"Version": "2012-10-17", "Statement": []}'
419
admin.policy_add("test-policy", policy_json)
420
421
except MinioAdminException as e:
422
if e.code == 409:
423
print("Policy already exists")
424
else:
425
print(f"Policy creation failed: {e.code}")
426
427
except Exception as e:
428
print(f"Admin client error: {e}")
429
```
430
431
### Retry Logic with Exponential Backoff
432
433
```python
434
import time
435
import random
436
from typing import Callable, TypeVar
437
438
T = TypeVar('T')
439
440
def retry_with_backoff(
441
func: Callable[[], T],
442
max_retries: int = 3,
443
base_delay: float = 1.0,
444
max_delay: float = 60.0
445
) -> T:
446
"""Retry function with exponential backoff for transient errors."""
447
448
for attempt in range(max_retries + 1):
449
try:
450
return func()
451
452
except S3Error as e:
453
# Don't retry on client errors (4xx)
454
if e.code in ["AccessDenied", "NoSuchBucket", "InvalidBucketName"]:
455
raise
456
457
# Retry on server errors and throttling
458
if attempt < max_retries and e.code in ["InternalError", "ServiceUnavailable", "SlowDown"]:
459
delay = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
460
print(f"Retrying after {delay:.2f}s (attempt {attempt + 1}/{max_retries + 1})")
461
time.sleep(delay)
462
continue
463
464
raise
465
466
except ServerError as e:
467
# Retry on 5xx server errors
468
if attempt < max_retries and e.status_code >= 500:
469
delay = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
470
print(f"Server error, retrying after {delay:.2f}s")
471
time.sleep(delay)
472
continue
473
474
raise
475
476
except Exception as e:
477
# Don't retry on unexpected errors
478
raise
479
480
# Usage example
481
def upload_with_retry(client: Minio, bucket: str, object_name: str, data: bytes):
482
"""Upload with automatic retry on transient errors."""
483
484
def upload_operation():
485
return client.put_object(
486
bucket,
487
object_name,
488
io.BytesIO(data),
489
len(data)
490
)
491
492
try:
493
result = retry_with_backoff(upload_operation, max_retries=3)
494
print(f"Upload successful: {result.etag}")
495
return result
496
except Exception as e:
497
print(f"Upload failed after retries: {e}")
498
raise
499
```
500
501
### Comprehensive Error Logging
502
503
```python
504
import logging
505
from datetime import datetime
506
507
# Configure logging
508
logging.basicConfig(
509
level=logging.INFO,
510
format='%(asctime)s - %(levelname)s - %(message)s'
511
)
512
logger = logging.getLogger(__name__)
513
514
def logged_operation(operation_name: str, func: Callable, *args, **kwargs):
515
"""Execute operation with comprehensive error logging."""
516
517
start_time = datetime.now()
518
logger.info(f"Starting {operation_name}")
519
520
try:
521
result = func(*args, **kwargs)
522
duration = (datetime.now() - start_time).total_seconds()
523
logger.info(f"Completed {operation_name} in {duration:.2f}s")
524
return result
525
526
except S3Error as e:
527
duration = (datetime.now() - start_time).total_seconds()
528
logger.error(
529
f"S3Error in {operation_name} after {duration:.2f}s: "
530
f"Code={e.code}, Message={e.message}, "
531
f"Bucket={e.bucket_name}, Object={e.object_name}, "
532
f"RequestId={e.request_id}"
533
)
534
raise
535
536
except ServerError as e:
537
duration = (datetime.now() - start_time).total_seconds()
538
logger.error(
539
f"ServerError in {operation_name} after {duration:.2f}s: "
540
f"Status={e.status_code}, Message={e}"
541
)
542
raise
543
544
except MinioAdminException as e:
545
duration = (datetime.now() - start_time).total_seconds()
546
logger.error(
547
f"AdminError in {operation_name} after {duration:.2f}s: "
548
f"Code={e.code}, Body={e.body}"
549
)
550
raise
551
552
except Exception as e:
553
duration = (datetime.now() - start_time).total_seconds()
554
logger.error(
555
f"UnexpectedError in {operation_name} after {duration:.2f}s: "
556
f"Type={type(e).__name__}, Message={e}"
557
)
558
raise
559
560
# Usage
561
client = Minio("localhost:9000", "minio", "minio123")
562
563
logged_operation(
564
"bucket_creation",
565
client.make_bucket,
566
"test-bucket"
567
)
568
569
logged_operation(
570
"object_upload",
571
client.put_object,
572
"test-bucket",
573
"test-object.txt",
574
io.BytesIO(b"test data"),
575
9
576
)
577
```