0
# Error Handling
1
2
Comprehensive exception hierarchy for handling different types of errors with specific exceptions for client, database, document, and operation-specific failures.
3
4
## Capabilities
5
6
### Exception Hierarchy
7
8
All Cloudant library exceptions inherit from the base `CloudantException` class.
9
10
```python { .api }
11
class CloudantException(Exception):
12
"""
13
Base exception class for all Cloudant library errors.
14
15
All other Cloudant exceptions inherit from this class, allowing
16
for comprehensive error handling with a single catch block.
17
"""
18
19
class CloudantArgumentError(CloudantException):
20
"""
21
Exception for invalid arguments passed to library functions.
22
23
Raised when:
24
- Required parameters are missing
25
- Parameter values are invalid or out of range
26
- Conflicting parameters are provided
27
"""
28
29
class ResultException(CloudantException):
30
"""
31
Exception for errors in result processing and iteration.
32
33
Raised when:
34
- Result iteration fails
35
- Result data is malformed
36
- Pagination errors occur
37
"""
38
39
class CloudantClientException(CloudantException):
40
"""
41
Exception for client connection and authentication errors.
42
43
Raised when:
44
- Authentication fails
45
- Connection to server fails
46
- Session management errors occur
47
- Network connectivity issues
48
"""
49
50
class CloudantDatabaseException(CloudantException):
51
"""
52
Exception for database-level operations.
53
54
Raised when:
55
- Database creation/deletion fails
56
- Database access denied
57
- Database metadata operations fail
58
- Query execution errors
59
"""
60
61
class CloudantDesignDocumentException(CloudantException):
62
"""
63
Exception for design document operations.
64
65
Raised when:
66
- Design document creation/update fails
67
- View definition errors
68
- Show/list function execution fails
69
- Update handler errors
70
"""
71
72
class CloudantDocumentException(CloudantException):
73
"""
74
Exception for document-level operations.
75
76
Raised when:
77
- Document CRUD operations fail
78
- Document conflict errors
79
- Attachment operations fail
80
- Document validation errors
81
"""
82
83
class CloudantFeedException(CloudantException):
84
"""
85
Exception for feed-related operations.
86
87
Raised when:
88
- Change feed connection fails
89
- Feed consumption errors
90
- Feed parameter validation fails
91
"""
92
93
class CloudantIndexException(CloudantException):
94
"""
95
Exception for index management operations.
96
97
Raised when:
98
- Index creation/deletion fails
99
- Index definition errors
100
- Index query optimization issues
101
"""
102
103
class CloudantReplicatorException(CloudantException):
104
"""
105
Exception for replication operations.
106
107
Raised when:
108
- Replication setup fails
109
- Replication monitoring errors
110
- Replication state management issues
111
"""
112
113
class CloudantViewException(CloudantException):
114
"""
115
Exception for view-related operations.
116
117
Raised when:
118
- View query execution fails
119
- View parameter validation errors
120
- MapReduce processing issues
121
"""
122
```
123
124
## Usage Examples
125
126
### Basic Error Handling
127
128
```python
129
from cloudant import cloudant
130
from cloudant.error import CloudantException
131
132
with cloudant('user', 'pass', account='myaccount') as client:
133
try:
134
# Any Cloudant operation
135
db = client['my_database']
136
doc = db['my_document']
137
doc.fetch()
138
139
except CloudantException as e:
140
print(f"Cloudant operation failed: {e}")
141
print(f"Error type: {type(e).__name__}")
142
```
143
144
### Specific Exception Handling
145
146
```python
147
from cloudant import cloudant
148
from cloudant.error import (
149
CloudantClientException,
150
CloudantDatabaseException,
151
CloudantDocumentException,
152
CloudantArgumentError
153
)
154
155
try:
156
with cloudant('invalid_user', 'invalid_pass', account='myaccount') as client:
157
# This will fail at connection time
158
db = client['my_database']
159
160
except CloudantClientException as e:
161
print(f"Authentication failed: {e}")
162
# Handle authentication error
163
# Could prompt for new credentials, use fallback auth, etc.
164
165
try:
166
with cloudant('user', 'pass', account='myaccount') as client:
167
# Try to access non-existent database
168
db = client['non_existent_database']
169
if not db.exists():
170
db.create()
171
172
except CloudantDatabaseException as e:
173
print(f"Database operation failed: {e}")
174
# Handle database error
175
# Could create database, use different database, etc.
176
177
try:
178
with cloudant('user', 'pass', account='myaccount') as client:
179
db = client['my_database']
180
doc = db['non_existent_document']
181
doc.fetch() # This will fail
182
183
except CloudantDocumentException as e:
184
print(f"Document operation failed: {e}")
185
# Handle document error
186
# Could create document, use default values, etc.
187
188
try:
189
with cloudant('user', 'pass', account='myaccount') as client:
190
# Invalid parameter
191
db = client.create_database('') # Empty name
192
193
except CloudantArgumentError as e:
194
print(f"Invalid argument: {e}")
195
# Handle argument error
196
# Could validate inputs, provide defaults, etc.
197
```
198
199
### Conflict Resolution
200
201
```python
202
from cloudant import cloudant
203
from cloudant.error import CloudantDocumentException
204
import time
205
import random
206
207
def save_with_retry(doc, max_retries=5):
208
"""Save document with automatic conflict resolution."""
209
210
for attempt in range(max_retries):
211
try:
212
doc.save()
213
print(f"Document saved successfully on attempt {attempt + 1}")
214
return True
215
216
except CloudantDocumentException as e:
217
error_msg = str(e).lower()
218
219
if 'conflict' in error_msg or '409' in str(e):
220
print(f"Conflict detected on attempt {attempt + 1}")
221
222
if attempt < max_retries - 1:
223
# Fetch latest version and retry
224
doc.fetch()
225
226
# Add random delay to reduce conflict probability
227
time.sleep(random.uniform(0.1, 0.5))
228
continue
229
else:
230
print(f"Max retries reached, conflict not resolved")
231
raise
232
else:
233
# Non-conflict error, don't retry
234
print(f"Non-conflict error: {e}")
235
raise
236
237
return False
238
239
# Usage
240
with cloudant('user', 'pass', account='myaccount') as client:
241
db = client['my_database']
242
doc = db['conflicted_document']
243
244
doc['updated_field'] = 'new_value'
245
doc['timestamp'] = time.time()
246
247
save_with_retry(doc)
248
```
249
250
### Network Error Handling
251
252
```python
253
from cloudant import cloudant
254
from cloudant.error import CloudantClientException
255
import time
256
257
def robust_operation(client, operation_func, max_retries=3):
258
"""Execute operation with retry on network errors."""
259
260
for attempt in range(max_retries):
261
try:
262
return operation_func(client)
263
264
except CloudantClientException as e:
265
error_msg = str(e).lower()
266
267
# Check if it's a network-related error
268
if any(keyword in error_msg for keyword in [
269
'timeout', 'connection', 'network', 'unreachable'
270
]):
271
print(f"Network error on attempt {attempt + 1}: {e}")
272
273
if attempt < max_retries - 1:
274
# Exponential backoff
275
wait_time = 2 ** attempt
276
print(f"Retrying in {wait_time} seconds...")
277
time.sleep(wait_time)
278
continue
279
else:
280
print("Max retries reached for network error")
281
raise
282
else:
283
# Non-network error, don't retry
284
print(f"Non-network client error: {e}")
285
raise
286
287
def my_database_operation(client):
288
"""Example database operation."""
289
db = client['my_database']
290
return db.all_docs(limit=10)
291
292
# Usage
293
with cloudant('user', 'pass', account='myaccount') as client:
294
try:
295
result = robust_operation(client, my_database_operation)
296
print(f"Operation successful: {len(list(result))} documents")
297
except CloudantClientException as e:
298
print(f"Operation failed permanently: {e}")
299
```
300
301
### Query Error Handling
302
303
```python
304
from cloudant import cloudant
305
from cloudant.error import CloudantDatabaseException, CloudantIndexException
306
307
def safe_query(db, selector, **kwargs):
308
"""Execute query with comprehensive error handling."""
309
310
try:
311
return db.get_query_result(selector, **kwargs)
312
313
except CloudantDatabaseException as e:
314
error_msg = str(e).lower()
315
316
if 'no_usable_index' in error_msg:
317
print("No suitable index found for query")
318
print("Suggestion: Create an index on the queried fields")
319
320
# Try to suggest index creation
321
fields = list(selector.keys())
322
print(f"Consider creating index on fields: {fields}")
323
324
# Could automatically create index here if desired
325
# db.create_query_index(fields=fields)
326
327
elif 'invalid_selector' in error_msg:
328
print(f"Invalid query selector: {selector}")
329
print("Check selector syntax and field names")
330
331
elif 'request_timeout' in error_msg:
332
print("Query timed out")
333
print("Try adding a limit or creating better indexes")
334
335
raise # Re-raise after logging
336
337
def safe_index_creation(db, fields, index_name):
338
"""Create index with error handling."""
339
340
try:
341
return db.create_query_index(
342
fields=fields,
343
index_name=index_name
344
)
345
346
except CloudantIndexException as e:
347
error_msg = str(e).lower()
348
349
if 'index_exists' in error_msg or 'conflict' in error_msg:
350
print(f"Index {index_name} already exists")
351
return None
352
353
elif 'invalid_fields' in error_msg:
354
print(f"Invalid index fields: {fields}")
355
print("Check field names and types")
356
357
raise
358
359
# Usage
360
with cloudant('user', 'pass', account='myaccount') as client:
361
db = client['my_database']
362
363
# Try query with error handling
364
try:
365
selector = {'type': 'user', 'status': 'active'}
366
result = safe_query(db, selector, limit=50)
367
368
for doc in result:
369
print(f"User: {doc.get('name', 'N/A')}")
370
371
except CloudantDatabaseException as e:
372
print(f"Query failed: {e}")
373
374
# Try index creation with error handling
375
try:
376
safe_index_creation(db, ['type', 'status'], 'type_status_idx')
377
print("Index created successfully")
378
except CloudantIndexException as e:
379
print(f"Index creation failed: {e}")
380
```
381
382
### Replication Error Handling
383
384
```python
385
from cloudant import cloudant
386
from cloudant.replicator import Replicator
387
from cloudant.error import CloudantReplicatorException
388
import time
389
390
def monitor_replication_with_error_handling(replicator, repl_id, timeout=300):
391
"""Monitor replication with comprehensive error handling."""
392
393
start_time = time.time()
394
395
while time.time() - start_time < timeout:
396
try:
397
state = replicator.replication_state(repl_id)
398
repl_state = state.get('_replication_state', 'unknown')
399
400
if repl_state == 'completed':
401
stats = state.get('_replication_stats', {})
402
docs_written = stats.get('docs_written', 0)
403
print(f"Replication completed: {docs_written} documents")
404
return True
405
406
elif repl_state == 'error':
407
error_reason = state.get('_replication_state_reason', 'Unknown error')
408
print(f"Replication failed: {error_reason}")
409
410
# Handle specific replication errors
411
if 'unauthorized' in error_reason.lower():
412
print("Authentication error - check credentials")
413
elif 'not_found' in error_reason.lower():
414
print("Database not found - check database names")
415
elif 'timeout' in error_reason.lower():
416
print("Network timeout - check connectivity")
417
418
return False
419
420
elif repl_state in ['triggered', 'running']:
421
stats = state.get('_replication_stats', {})
422
docs_read = stats.get('docs_read', 0)
423
docs_written = stats.get('docs_written', 0)
424
print(f"Replication progress: {docs_written}/{docs_read}")
425
426
time.sleep(5) # Check every 5 seconds
427
428
except CloudantReplicatorException as e:
429
print(f"Error checking replication state: {e}")
430
431
# If replication document was deleted, it might have completed
432
if 'not_found' in str(e).lower():
433
print("Replication document not found - may have completed")
434
return None
435
436
time.sleep(10) # Wait longer on error
437
438
print(f"Replication monitoring timed out after {timeout} seconds")
439
return None
440
441
# Usage
442
with cloudant('user', 'pass', account='myaccount') as client:
443
replicator = Replicator(client)
444
445
try:
446
# Start replication with error handling
447
repl_doc = replicator.create_replication(
448
source_db='source_db',
449
target_db='target_db',
450
create_target=True
451
)
452
453
repl_id = repl_doc['_id']
454
print(f"Started replication: {repl_id}")
455
456
# Monitor with error handling
457
success = monitor_replication_with_error_handling(replicator, repl_id)
458
459
if success:
460
print("Replication completed successfully")
461
elif success is False:
462
print("Replication failed")
463
else:
464
print("Replication status uncertain")
465
466
except CloudantReplicatorException as e:
467
print(f"Failed to start replication: {e}")
468
```
469
470
### Feed Error Handling
471
472
```python
473
from cloudant import cloudant
474
from cloudant.error import CloudantFeedException
475
import time
476
477
def robust_change_feed(db, max_reconnects=5):
478
"""Change feed with automatic reconnection on errors."""
479
480
reconnect_count = 0
481
last_seq = '0'
482
483
while reconnect_count < max_reconnects:
484
try:
485
print(f"Starting change feed from sequence: {last_seq}")
486
487
changes = db.changes(
488
since=last_seq,
489
feed='continuous',
490
include_docs=True,
491
heartbeat=30000,
492
timeout=60000
493
)
494
495
for change in changes:
496
if change: # Skip heartbeat messages
497
doc_id = change['id']
498
last_seq = change['seq']
499
500
print(f"Change detected: {doc_id} (seq: {last_seq})")
501
502
# Process change here
503
if change.get('deleted'):
504
print(f"Document deleted: {doc_id}")
505
else:
506
doc = change.get('doc', {})
507
print(f"Document updated: {doc_id}")
508
509
# If we reach here, feed ended normally
510
print("Change feed ended normally")
511
break
512
513
except CloudantFeedException as e:
514
reconnect_count += 1
515
error_msg = str(e).lower()
516
517
print(f"Feed error (attempt {reconnect_count}/{max_reconnects}): {e}")
518
519
if 'timeout' in error_msg or 'connection' in error_msg:
520
print("Network-related error, will retry")
521
elif 'unauthorized' in error_msg:
522
print("Authentication error, check credentials")
523
break
524
elif 'not_found' in error_msg:
525
print("Database not found")
526
break
527
528
if reconnect_count < max_reconnects:
529
wait_time = min(2 ** reconnect_count, 60) # Cap at 60 seconds
530
print(f"Reconnecting in {wait_time} seconds...")
531
time.sleep(wait_time)
532
else:
533
print("Max reconnection attempts reached")
534
raise
535
536
except KeyboardInterrupt:
537
print("Change feed interrupted by user")
538
break
539
except Exception as e:
540
print(f"Unexpected error in change feed: {e}")
541
break
542
543
# Usage
544
with cloudant('user', 'pass', account='myaccount') as client:
545
db = client['my_database']
546
547
try:
548
robust_change_feed(db)
549
except CloudantFeedException as e:
550
print(f"Change feed failed permanently: {e}")
551
```
552
553
### Error Context and Debugging
554
555
```python
556
from cloudant import cloudant
557
from cloudant.error import CloudantException
558
import traceback
559
import logging
560
561
# Set up logging for better error tracking
562
logging.basicConfig(
563
level=logging.INFO,
564
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
565
)
566
logger = logging.getLogger('cloudant_app')
567
568
def detailed_error_handling():
569
"""Example of detailed error context extraction."""
570
571
try:
572
with cloudant('user', 'pass', account='myaccount') as client:
573
db = client['my_database']
574
575
# Some operation that might fail
576
doc = db['problem_document']
577
doc.fetch()
578
579
except CloudantException as e:
580
# Log detailed error information
581
logger.error(f"Cloudant operation failed:")
582
logger.error(f" Error type: {type(e).__name__}")
583
logger.error(f" Error message: {str(e)}")
584
585
# Check if there's additional error context
586
if hasattr(e, 'response'):
587
logger.error(f" HTTP status: {e.response.status_code}")
588
logger.error(f" Response headers: {dict(e.response.headers)}")
589
590
if hasattr(e, 'request'):
591
logger.error(f" Request URL: {e.request.url}")
592
logger.error(f" Request method: {e.request.method}")
593
594
# Full stack trace for debugging
595
logger.debug("Full stack trace:")
596
logger.debug(traceback.format_exc())
597
598
# Re-raise with additional context
599
raise type(e)(f"Operation failed with context: {str(e)}") from e
600
601
def error_recovery_example():
602
"""Example of implementing error recovery strategies."""
603
604
max_retries = 3
605
retry_count = 0
606
607
while retry_count < max_retries:
608
try:
609
with cloudant('user', 'pass', account='myaccount') as client:
610
db = client['my_database']
611
612
# Critical operation
613
doc = db.create_document({
614
'type': 'important_record',
615
'data': 'critical_data',
616
'timestamp': time.time()
617
})
618
619
logger.info(f"Document created successfully: {doc['_id']}")
620
return doc
621
622
except CloudantDocumentException as e:
623
retry_count += 1
624
logger.warning(f"Document operation failed (attempt {retry_count}): {e}")
625
626
if retry_count < max_retries:
627
# Implement recovery strategy
628
if 'conflict' in str(e).lower():
629
# Generate new ID for conflicts
630
logger.info("Generating new document ID due to conflict")
631
time.sleep(0.5) # Brief delay
632
else:
633
# General retry with exponential backoff
634
wait_time = 2 ** retry_count
635
logger.info(f"Retrying in {wait_time} seconds...")
636
time.sleep(wait_time)
637
else:
638
logger.error("Max retries exceeded, operation failed permanently")
639
raise
640
641
except CloudantClientException as e:
642
logger.error(f"Client error, cannot retry: {e}")
643
raise
644
except Exception as e:
645
logger.error(f"Unexpected error: {e}")
646
raise
647
648
return None
649
650
# Usage examples
651
if __name__ == "__main__":
652
try:
653
detailed_error_handling()
654
except CloudantException as e:
655
print(f"Final error: {e}")
656
657
try:
658
doc = error_recovery_example()
659
if doc:
660
print(f"Operation succeeded: {doc['_id']}")
661
except CloudantException as e:
662
print(f"Operation failed permanently: {e}")
663
```
664
665
## Best Practices
666
667
### Error Handling Guidelines
668
669
1. **Use Specific Exceptions**: Catch specific exception types rather than the generic `CloudantException` when you need different handling logic.
670
671
2. **Implement Retry Logic**: For network-related errors, implement exponential backoff retry strategies.
672
673
3. **Log Error Context**: Include error type, message, and relevant context (HTTP status, request details) in logs.
674
675
4. **Graceful Degradation**: Design your application to continue functioning with reduced capabilities when possible.
676
677
5. **Monitor Error Patterns**: Track error frequencies and types to identify system issues early.
678
679
6. **Validate Inputs**: Use `CloudantArgumentError` to validate parameters before making requests.
680
681
7. **Handle Conflicts**: Implement conflict resolution strategies for document operations in multi-user environments.
682
683
8. **Set Appropriate Timeouts**: Use reasonable timeout values to prevent indefinite blocking while allowing sufficient time for operations.
684
685
9. **Resource Cleanup**: Always ensure proper cleanup of resources (connections, feeds) in error scenarios.
686
687
10. **User-Friendly Messages**: Convert technical error messages into user-friendly explanations when appropriate.