0
# Error Handling and Exceptions
1
2
The Google API Python Client provides a comprehensive error handling system with structured exceptions that contain detailed information about failures, enabling robust error recovery and debugging.
3
4
## Capabilities
5
6
### Base Exception Classes
7
8
Foundation exception classes that provide common error handling functionality.
9
10
```python { .api }
11
class Error(Exception):
12
"""Base exception class for all Google API client errors."""
13
pass
14
```
15
16
### HTTP Error Classes
17
18
Exceptions related to HTTP request failures with detailed response information.
19
20
```python { .api }
21
class HttpError(Error):
22
"""HTTP request failed with an error response from the server."""
23
24
def __init__(self, resp, content, uri=None):
25
"""
26
Initialize an HTTP error.
27
28
Args:
29
resp (httplib2.Response): HTTP response object
30
content (bytes): Response body content
31
uri (str, optional): URI that was requested
32
"""
33
34
@property
35
def resp(self):
36
"""
37
Get the HTTP response object.
38
39
Returns:
40
httplib2.Response: The HTTP response with status, headers, etc.
41
"""
42
43
@property
44
def content(self):
45
"""
46
Get the response body content.
47
48
Returns:
49
bytes: Raw response content
50
"""
51
52
@property
53
def error_details(self):
54
"""
55
Get structured error details from the response.
56
57
Returns:
58
list: List of error detail dictionaries, or empty list if none
59
"""
60
61
@property
62
def status_code(self):
63
"""
64
Get the HTTP status code.
65
66
Returns:
67
int: HTTP status code (e.g., 404, 500)
68
"""
69
70
def _get_reason(self):
71
"""
72
Extract the error reason from the response.
73
74
Returns:
75
str: Human-readable error reason
76
"""
77
78
class MediaUploadSizeError(HttpError):
79
"""Media upload size exceeds the allowed limits."""
80
pass
81
82
class BatchError(HttpError):
83
"""Error occurred during batch request processing."""
84
85
def __init__(self, reason, resp=None, content=None):
86
"""
87
Initialize a batch error.
88
89
Args:
90
reason (str): Description of the batch error
91
resp (httplib2.Response, optional): HTTP response object
92
content (bytes, optional): Response content
93
"""
94
95
class ResumableUploadError(HttpError):
96
"""Error occurred during resumable upload processing."""
97
pass
98
```
99
100
### API-Specific Error Classes
101
102
Exceptions for specific API operation failures.
103
104
```python { .api }
105
class InvalidJsonError(Error):
106
"""Response contained invalid JSON that could not be parsed."""
107
pass
108
109
class UnknownFileType(Error):
110
"""Media upload failed due to unknown or unsupported file type."""
111
pass
112
113
class UnknownLinkType(Error):
114
"""Link type unknown or unexpected."""
115
116
def __init__(self):
117
"""Initialize an unknown link type error."""
118
119
class UnacceptableMimeTypeError(Error):
120
"""That is an unacceptable mimetype for this operation."""
121
122
def __init__(self):
123
"""Initialize an unacceptable MIME type error."""
124
125
class InvalidChunkSizeError(Error):
126
"""The given chunksize is not valid."""
127
128
def __init__(self):
129
"""Initialize an invalid chunk size error."""
130
131
class InvalidNotificationError(Error):
132
"""The channel Notification is invalid."""
133
134
def __init__(self):
135
"""Initialize an invalid notification error."""
136
137
class UnknownApiNameOrVersion(Error):
138
"""No API with that name and version exists."""
139
140
def __init__(self):
141
"""Initialize an unknown API name or version error."""
142
143
class UnexpectedMethodError(Error):
144
"""Exception raised by RequestMockBuilder on unexpected calls."""
145
146
def __init__(self, methodId=None):
147
"""
148
Initialize an unexpected method error.
149
150
Args:
151
methodId (str, optional): Method identifier that was unexpected
152
"""
153
154
class UnexpectedBodyError(Error):
155
"""Exception raised by RequestMockBuilder on unexpected bodies."""
156
157
def __init__(self, expected, provided):
158
"""
159
Initialize an unexpected body error.
160
161
Args:
162
expected: Expected body content
163
provided: Provided body content
164
"""
165
```
166
167
## Usage Examples
168
169
### Basic Error Handling
170
171
```python
172
from googleapiclient import discovery
173
from googleapiclient.errors import HttpError
174
175
service = discovery.build('gmail', 'v1', credentials=credentials)
176
177
try:
178
# Attempt to get a message that might not exist
179
message = service.users().messages().get(
180
userId='me',
181
id='nonexistent_message_id'
182
).execute()
183
except HttpError as error:
184
print(f'HTTP Error {error.status_code}: {error._get_reason()}')
185
print(f'Response content: {error.content}')
186
```
187
188
### Detailed Error Information
189
190
```python
191
from googleapiclient.errors import HttpError
192
import json
193
194
try:
195
response = service.users().messages().list(userId='invalid_user').execute()
196
except HttpError as error:
197
print(f'Status Code: {error.status_code}')
198
print(f'Reason: {error._get_reason()}')
199
200
# Parse error details from response content
201
try:
202
error_content = json.loads(error.content.decode('utf-8'))
203
if 'error' in error_content:
204
error_info = error_content['error']
205
print(f'Error Code: {error_info.get("code")}')
206
print(f'Error Message: {error_info.get("message")}')
207
208
# Check for detailed error information
209
if 'errors' in error_info:
210
for detail in error_info['errors']:
211
print(f' - {detail.get("reason")}: {detail.get("message")}')
212
except (json.JSONDecodeError, UnicodeDecodeError):
213
print(f'Raw error content: {error.content}')
214
```
215
216
### Error-Specific Handling
217
218
```python
219
from googleapiclient.errors import (
220
HttpError, BatchError, MediaUploadSizeError,
221
UnknownApiNameOrVersion, InvalidJsonError
222
)
223
224
try:
225
# Various operations that might fail
226
service = discovery.build('unknown-api', 'v1', credentials=credentials)
227
228
except UnknownApiNameOrVersion as error:
229
print(f'API not available: {error}')
230
# Fallback to a different API or version
231
232
except HttpError as error:
233
if error.status_code == 401:
234
print('Authentication failed - check credentials')
235
elif error.status_code == 403:
236
print('Access forbidden - check permissions/quotas')
237
elif error.status_code == 404:
238
print('Resource not found')
239
elif error.status_code == 429:
240
print('Rate limit exceeded - implement backoff')
241
elif error.status_code >= 500:
242
print('Server error - retry may help')
243
else:
244
print(f'HTTP error {error.status_code}: {error._get_reason()}')
245
246
except BatchError as error:
247
print(f'Batch processing failed: {error}')
248
249
except MediaUploadSizeError as error:
250
print(f'File too large for upload: {error}')
251
252
except InvalidJsonError as error:
253
print(f'Invalid JSON in response: {error}')
254
```
255
256
### Retry Logic with Error Handling
257
258
```python
259
from googleapiclient.errors import HttpError
260
import time
261
import random
262
263
def execute_with_retry(request, max_retries=3, backoff_factor=1.5):
264
"""
265
Execute a request with exponential backoff retry logic.
266
267
Args:
268
request: The HttpRequest to execute
269
max_retries (int): Maximum number of retry attempts
270
backoff_factor (float): Multiplier for exponential backoff
271
272
Returns:
273
Response object from successful request
274
275
Raises:
276
HttpError: When all retry attempts are exhausted
277
"""
278
last_error = None
279
280
for attempt in range(max_retries + 1):
281
try:
282
return request.execute()
283
284
except HttpError as error:
285
last_error = error
286
287
# Don't retry on client errors (4xx) except for specific cases
288
if 400 <= error.status_code < 500:
289
if error.status_code not in [429, 408]: # Rate limit, timeout
290
raise
291
292
# Don't retry on the last attempt
293
if attempt == max_retries:
294
raise
295
296
# Calculate wait time with jitter
297
wait_time = (backoff_factor ** attempt) + random.uniform(0, 1)
298
print(f'Request failed (attempt {attempt + 1}), retrying in {wait_time:.1f}s...')
299
time.sleep(wait_time)
300
301
# This should never be reached, but just in case
302
raise last_error
303
304
# Usage
305
try:
306
request = service.users().messages().list(userId='me')
307
response = execute_with_retry(request, max_retries=3)
308
print(f'Success: {len(response.get("messages", []))} messages')
309
except HttpError as error:
310
print(f'Request failed after retries: {error._get_reason()}')
311
```
312
313
### Batch Error Handling
314
315
```python
316
from googleapiclient import http
317
from googleapiclient.errors import HttpError, BatchError
318
319
def batch_callback(request_id, response, exception):
320
"""Handle individual batch request results with error processing."""
321
if exception is not None:
322
if isinstance(exception, HttpError):
323
print(f'Request {request_id} failed with HTTP {exception.status_code}')
324
print(f' Reason: {exception._get_reason()}')
325
326
# Handle specific error codes
327
if exception.status_code == 404:
328
print(f' Resource not found for request {request_id}')
329
elif exception.status_code == 403:
330
print(f' Access denied for request {request_id}')
331
else:
332
print(f'Request {request_id} failed: {exception}')
333
else:
334
print(f'Request {request_id} succeeded')
335
336
try:
337
batch = http.BatchHttpRequest(callback=batch_callback)
338
339
# Add requests that might fail
340
batch.add(service.users().messages().get(userId='me', id='valid_id'),
341
request_id='valid_message')
342
batch.add(service.users().messages().get(userId='me', id='invalid_id'),
343
request_id='invalid_message')
344
345
batch.execute()
346
347
except BatchError as error:
348
print(f'Batch execution failed: {error}')
349
except HttpError as error:
350
print(f'Batch request failed: {error._get_reason()}')
351
```
352
353
### Custom Error Handler
354
355
```python
356
from googleapiclient.errors import HttpError
357
import logging
358
359
class ApiErrorHandler:
360
"""Custom error handler for Google API operations."""
361
362
def __init__(self, logger=None):
363
self.logger = logger or logging.getLogger(__name__)
364
365
def handle_error(self, error, operation_name, **context):
366
"""
367
Handle API errors with logging and context.
368
369
Args:
370
error (Exception): The error that occurred
371
operation_name (str): Name of the operation that failed
372
**context: Additional context information
373
"""
374
if isinstance(error, HttpError):
375
self.logger.error(
376
f'{operation_name} failed: HTTP {error.status_code} - {error._get_reason()}',
377
extra={
378
'status_code': error.status_code,
379
'operation': operation_name,
380
'context': context
381
}
382
)
383
384
# Return suggested action
385
if error.status_code == 401:
386
return 'refresh_credentials'
387
elif error.status_code == 403:
388
return 'check_permissions'
389
elif error.status_code == 429:
390
return 'implement_backoff'
391
elif error.status_code >= 500:
392
return 'retry_request'
393
else:
394
return 'check_request_parameters'
395
else:
396
self.logger.error(f'{operation_name} failed: {error}', extra={
397
'operation': operation_name,
398
'context': context
399
})
400
return 'unknown_error'
401
402
# Usage
403
error_handler = ApiErrorHandler()
404
405
try:
406
messages = service.users().messages().list(userId='me').execute()
407
except Exception as error:
408
action = error_handler.handle_error(
409
error,
410
'list_messages',
411
user_id='me',
412
service='gmail'
413
)
414
415
if action == 'refresh_credentials':
416
# Implement credential refresh logic
417
pass
418
elif action == 'implement_backoff':
419
# Implement rate limiting backoff
420
pass
421
```
422
423
### Error Details Extraction
424
425
```python
426
from googleapiclient.errors import HttpError
427
import json
428
429
def extract_error_details(error):
430
"""
431
Extract detailed error information from HttpError.
432
433
Args:
434
error (HttpError): The HTTP error to analyze
435
436
Returns:
437
dict: Structured error information
438
"""
439
details = {
440
'status_code': error.status_code,
441
'reason': error._get_reason(),
442
'errors': []
443
}
444
445
try:
446
# Parse JSON error response
447
content = json.loads(error.content.decode('utf-8'))
448
449
if 'error' in content:
450
error_info = content['error']
451
details['code'] = error_info.get('code')
452
details['message'] = error_info.get('message')
453
454
# Extract individual error details
455
if 'errors' in error_info:
456
for err in error_info['errors']:
457
details['errors'].append({
458
'domain': err.get('domain'),
459
'reason': err.get('reason'),
460
'message': err.get('message'),
461
'location': err.get('location'),
462
'location_type': err.get('locationType')
463
})
464
465
except (json.JSONDecodeError, UnicodeDecodeError, KeyError):
466
# Fallback to raw content
467
details['raw_content'] = error.content.decode('utf-8', errors='ignore')
468
469
return details
470
471
# Usage
472
try:
473
response = service.users().messages().get(userId='me', id='bad_id').execute()
474
except HttpError as error:
475
error_details = extract_error_details(error)
476
print(f'Error: {error_details["message"]}')
477
478
for err in error_details['errors']:
479
print(f' - {err["reason"]}: {err["message"]}')
480
if err['location']:
481
print(f' Location: {err["location"]} ({err["location_type"]})')
482
```