0
# Exception Handling
1
2
Comprehensive exception hierarchy for handling various error conditions during HTTP operations. These exceptions provide detailed error information and enable robust error handling patterns. All niquests exceptions inherit from `RequestException`, which inherits from Python's built-in `IOError`.
3
4
## Exception Hierarchy
5
6
```
7
IOError
8
└── RequestException
9
├── HTTPError
10
├── ConnectionError
11
│ ├── ProxyError
12
│ ├── SSLError
13
│ └── ConnectTimeout (also inherits from Timeout)
14
├── Timeout
15
│ └── ReadTimeout
16
├── URLRequired
17
├── TooManyRedirects
18
├── InvalidJSONError
19
│ └── JSONDecodeError
20
├── InvalidURL
21
├── InvalidSchema
22
├── InvalidHeader
23
├── MissingSchema
24
├── ChunkedEncodingError
25
├── ContentDecodingError
26
├── StreamConsumedError
27
├── RetryError
28
├── UnrewindableBodyError
29
└── MultiplexingError
30
```
31
32
## Capabilities
33
34
### Base Exception Classes
35
36
Core exception classes that form the foundation of niquests error handling.
37
38
```python { .api }
39
class RequestException(IOError):
40
"""
41
Base exception class for all requests-related exceptions.
42
43
There was an ambiguous exception that occurred while handling your request.
44
All other niquests exceptions inherit from this class.
45
"""
46
47
def __init__(self, *args, **kwargs):
48
"""
49
Initialize RequestException with request and response objects.
50
51
Args:
52
*args: Exception arguments
53
**kwargs: May include 'request' and 'response' objects
54
"""
55
56
# Instance attributes
57
request: PreparedRequest | None # The request that caused the exception
58
response: Response | None # The response (if any) that caused the exception
59
60
class HTTPError(RequestException):
61
"""
62
An HTTP error occurred.
63
64
Raised when an HTTP request returns an unsuccessful status code
65
(4xx or 5xx) and raise_for_status() is called.
66
"""
67
68
class ConnectionError(RequestException):
69
"""
70
A Connection error occurred.
71
72
Raised when there's a network-level connection problem,
73
such as a refused connection or unreachable host.
74
"""
75
```
76
77
### Timeout Exceptions
78
79
Exceptions related to request timeouts and timing issues.
80
81
```python { .api }
82
class Timeout(RequestException):
83
"""
84
The request timed out.
85
86
Catching this error will catch both ConnectTimeout and ReadTimeout.
87
This is the base class for all timeout-related exceptions.
88
"""
89
90
class ConnectTimeout(ConnectionError, Timeout):
91
"""
92
The request timed out while trying to connect to the remote server.
93
94
Requests that produced this error are safe to retry as the connection
95
was never established.
96
"""
97
98
class ReadTimeout(Timeout):
99
"""
100
The server did not send any data in the allotted amount of time.
101
102
The connection was established but the server failed to send data
103
within the specified timeout period.
104
"""
105
```
106
107
### SSL and Security Exceptions
108
109
Exceptions related to SSL/TLS and security issues.
110
111
```python { .api }
112
class SSLError(ConnectionError):
113
"""
114
An SSL error occurred.
115
116
Raised when there's an SSL/TLS-related problem such as:
117
- Certificate verification failures
118
- SSL handshake failures
119
- Protocol version mismatches
120
"""
121
122
class ProxyError(ConnectionError):
123
"""
124
A proxy error occurred.
125
126
Raised when there's an issue with proxy configuration or
127
the proxy server itself.
128
"""
129
```
130
131
### URL and Schema Exceptions
132
133
Exceptions related to URL formatting and validation.
134
135
```python { .api }
136
class URLRequired(RequestException):
137
"""
138
A valid URL is required to make a request.
139
140
Raised when a request is attempted without providing a URL.
141
"""
142
143
class InvalidURL(RequestException, ValueError):
144
"""
145
The URL provided was somehow invalid.
146
147
Raised when the URL cannot be parsed or contains invalid characters.
148
"""
149
150
class InvalidSchema(RequestException, ValueError):
151
"""
152
The URL scheme provided is either invalid or unsupported.
153
154
Raised for unsupported schemes like 'ftp://' or malformed schemes.
155
"""
156
157
class MissingSchema(RequestException, ValueError):
158
"""
159
The URL scheme (e.g. http or https) is missing.
160
161
Raised when a URL is provided without a protocol scheme.
162
"""
163
164
class InvalidHeader(RequestException, ValueError):
165
"""
166
The header value provided was somehow invalid.
167
168
Raised when HTTP headers contain invalid characters or formatting.
169
"""
170
```
171
172
### Content and Encoding Exceptions
173
174
Exceptions related to response content processing and encoding.
175
176
```python { .api }
177
class InvalidJSONError(RequestException):
178
"""
179
A JSON error occurred.
180
181
Base class for JSON-related errors during response processing.
182
"""
183
184
class JSONDecodeError(InvalidJSONError, json.JSONDecodeError):
185
"""
186
Couldn't decode the text into json.
187
188
Raised when response.json() is called but the response content
189
is not valid JSON.
190
"""
191
192
class ChunkedEncodingError(RequestException):
193
"""
194
The server declared chunked encoding but sent an invalid chunk.
195
196
Raised when there's a problem with chunked transfer encoding
197
in the response.
198
"""
199
200
class ContentDecodingError(RequestException):
201
"""
202
Failed to decode response content.
203
204
Raised when automatic content decompression (gzip, deflate, etc.)
205
fails due to corrupted or invalid compressed data.
206
"""
207
208
class StreamConsumedError(RequestException, TypeError):
209
"""
210
The content for this response was already consumed.
211
212
Raised when attempting to read response content that has already
213
been consumed by a previous operation.
214
"""
215
```
216
217
### Advanced Operation Exceptions
218
219
Exceptions for advanced features and operations.
220
221
```python { .api }
222
class TooManyRedirects(RequestException):
223
"""
224
Too many redirects.
225
226
Raised when the maximum number of redirects is exceeded.
227
"""
228
229
class RetryError(RequestException):
230
"""
231
Custom retries logic failed.
232
233
Raised when retry mechanisms are exhausted or encounter
234
unrecoverable errors.
235
"""
236
237
class UnrewindableBodyError(RequestException):
238
"""
239
Requests encountered an error when trying to rewind a body.
240
241
Raised when attempting to retry a request with a body that
242
cannot be rewound (e.g., a consumed stream).
243
"""
244
245
class MultiplexingError(RequestException):
246
"""
247
Requests encountered an unresolvable error in multiplexed mode.
248
249
Raised when HTTP/2 or HTTP/3 multiplexing encounters errors
250
that cannot be resolved.
251
"""
252
```
253
254
### Warning Classes
255
256
Warning classes for non-fatal issues and deprecation notices.
257
258
```python { .api }
259
class RequestsWarning(Warning):
260
"""
261
Base warning for Requests.
262
263
Base class for all niquests-related warnings.
264
"""
265
266
class FileModeWarning(RequestsWarning, DeprecationWarning):
267
"""
268
A file was opened in text mode, but Requests determined its binary length.
269
270
Warning raised when files are opened in text mode but should be
271
opened in binary mode for proper handling.
272
"""
273
274
class RequestsDependencyWarning(RequestsWarning):
275
"""
276
An imported dependency doesn't match the expected version range.
277
278
Warning raised when dependency versions may cause compatibility issues.
279
"""
280
```
281
282
## Usage Examples
283
284
### Basic Exception Handling
285
286
```python
287
import niquests
288
289
try:
290
response = niquests.get('https://api.example.com/data', timeout=5.0)
291
response.raise_for_status() # Raise HTTPError for bad status codes
292
data = response.json()
293
print("Success:", data)
294
295
except niquests.HTTPError as e:
296
print(f"HTTP Error {e.response.status_code}: {e.response.reason}")
297
# Access the response even when there's an error
298
if e.response.headers.get('content-type', '').startswith('application/json'):
299
error_details = e.response.json()
300
print(f"Error details: {error_details}")
301
302
except niquests.ConnectionError:
303
print("Failed to connect to the server")
304
305
except niquests.Timeout:
306
print("Request timed out")
307
308
except niquests.RequestException as e:
309
print(f"An error occurred: {e}")
310
```
311
312
### Specific Exception Handling
313
314
```python
315
import niquests
316
317
def robust_api_call(url, max_retries=3):
318
"""Make API call with comprehensive error handling."""
319
320
for attempt in range(max_retries):
321
try:
322
response = niquests.get(url, timeout=(5.0, 30.0))
323
response.raise_for_status()
324
return response.json()
325
326
except niquests.ConnectTimeout:
327
print(f"Connection timeout on attempt {attempt + 1}")
328
if attempt == max_retries - 1:
329
raise
330
331
except niquests.ReadTimeout:
332
print(f"Read timeout on attempt {attempt + 1}")
333
if attempt == max_retries - 1:
334
raise
335
336
except niquests.SSLError as e:
337
print(f"SSL error: {e}")
338
# SSL errors are usually not retryable
339
raise
340
341
except niquests.HTTPError as e:
342
status_code = e.response.status_code
343
if status_code >= 500:
344
# Server errors might be retryable
345
print(f"Server error {status_code} on attempt {attempt + 1}")
346
if attempt == max_retries - 1:
347
raise
348
else:
349
# Client errors (4xx) are usually not retryable
350
print(f"Client error {status_code}: {e.response.reason}")
351
raise
352
353
except niquests.JSONDecodeError:
354
print("Response is not valid JSON")
355
print(f"Response content: {response.text[:200]}...")
356
raise
357
358
except niquests.TooManyRedirects:
359
print("Too many redirects - possible redirect loop")
360
raise
361
362
# Wait before retry
363
if attempt < max_retries - 1:
364
time.sleep(2 ** attempt) # Exponential backoff
365
366
raise niquests.RequestException("Max retries exceeded")
367
368
# Usage
369
try:
370
data = robust_api_call('https://api.example.com/data')
371
print("API call successful:", data)
372
except niquests.RequestException as e:
373
print(f"API call failed: {e}")
374
```
375
376
### URL Validation and Error Handling
377
378
```python
379
import niquests
380
381
def validate_and_fetch(url):
382
"""Validate URL and fetch content with proper error handling."""
383
384
try:
385
# This will raise various URL-related exceptions
386
response = niquests.get(url)
387
return response.text
388
389
except niquests.MissingSchema:
390
print("URL is missing a scheme (http:// or https://)")
391
# Try to fix by adding https://
392
return validate_and_fetch(f"https://{url}")
393
394
except niquests.InvalidSchema as e:
395
print(f"Invalid or unsupported URL scheme: {e}")
396
raise
397
398
except niquests.InvalidURL as e:
399
print(f"Malformed URL: {e}")
400
raise
401
402
except niquests.ConnectionError:
403
print("Could not connect to the server")
404
raise
405
406
# Usage examples
407
try:
408
content = validate_and_fetch("example.com") # Missing scheme
409
print("Content fetched successfully")
410
except niquests.RequestException as e:
411
print(f"Failed to fetch content: {e}")
412
```
413
414
### Async Exception Handling
415
416
```python
417
import asyncio
418
import niquests
419
420
async def async_request_with_error_handling(url):
421
"""Async request with comprehensive error handling."""
422
423
try:
424
response = await niquests.aget(url, timeout=10.0)
425
426
async with response: # Ensure response is closed
427
response.raise_for_status()
428
data = await response.json()
429
return data
430
431
except niquests.HTTPError as e:
432
print(f"HTTP Error: {e.response.status_code}")
433
# Can still access response data
434
error_content = await e.response.text
435
print(f"Error response: {error_content}")
436
raise
437
438
except niquests.ConnectionError:
439
print("Connection failed")
440
raise
441
442
except niquests.Timeout:
443
print("Request timed out")
444
raise
445
446
except niquests.JSONDecodeError:
447
print("Invalid JSON response")
448
raise
449
450
# Usage
451
async def main():
452
try:
453
data = await async_request_with_error_handling('https://api.example.com/data')
454
print("Success:", data)
455
except niquests.RequestException as e:
456
print(f"Request failed: {e}")
457
458
asyncio.run(main())
459
```
460
461
### Custom Exception Handling
462
463
```python
464
import niquests
465
import logging
466
467
# Set up logging
468
logging.basicConfig(level=logging.INFO)
469
logger = logging.getLogger(__name__)
470
471
class APIClient:
472
"""Example API client with comprehensive error handling."""
473
474
def __init__(self, base_url, timeout=30.0):
475
self.base_url = base_url
476
self.timeout = timeout
477
self.session = niquests.Session()
478
479
def _handle_request_error(self, e, url):
480
"""Centralized error handling and logging."""
481
482
if isinstance(e, niquests.HTTPError):
483
status = e.response.status_code
484
logger.error(f"HTTP {status} error for {url}: {e.response.reason}")
485
486
# Log response content for debugging
487
try:
488
error_data = e.response.json()
489
logger.error(f"Error response: {error_data}")
490
except niquests.JSONDecodeError:
491
logger.error(f"Error response (non-JSON): {e.response.text[:500]}")
492
493
elif isinstance(e, niquests.ConnectionError):
494
logger.error(f"Connection error for {url}: {e}")
495
496
elif isinstance(e, niquests.Timeout):
497
logger.error(f"Timeout error for {url}: {e}")
498
499
elif isinstance(e, niquests.JSONDecodeError):
500
logger.error(f"JSON decode error for {url}: {e}")
501
502
else:
503
logger.error(f"Unexpected error for {url}: {e}")
504
505
def get(self, endpoint, **kwargs):
506
"""Make GET request with error handling."""
507
url = f"{self.base_url}/{endpoint.lstrip('/')}"
508
509
try:
510
response = self.session.get(url, timeout=self.timeout, **kwargs)
511
response.raise_for_status()
512
return response.json()
513
514
except niquests.RequestException as e:
515
self._handle_request_error(e, url)
516
raise
517
518
def __enter__(self):
519
return self
520
521
def __exit__(self, exc_type, exc_val, exc_tb):
522
self.session.close()
523
524
# Usage
525
with APIClient('https://api.example.com') as client:
526
try:
527
users = client.get('/users')
528
print(f"Found {len(users)} users")
529
except niquests.RequestException:
530
print("Failed to fetch users")
531
```
532
533
## Best Practices
534
535
### Exception Handling Strategy
536
537
1. **Catch specific exceptions** rather than using broad `except` clauses
538
2. **Handle retryable vs non-retryable errors** differently
539
3. **Log errors appropriately** with relevant context
540
4. **Preserve error information** for debugging
541
5. **Use response data even for errors** when available
542
543
### Common Patterns
544
545
```python
546
# Pattern 1: Catch and retry
547
def retry_request(url, max_retries=3):
548
for i in range(max_retries):
549
try:
550
return niquests.get(url, timeout=10.0)
551
except (niquests.ConnectionError, niquests.Timeout):
552
if i == max_retries - 1:
553
raise
554
time.sleep(2 ** i)
555
556
# Pattern 2: Graceful degradation
557
def get_user_data(user_id, fallback=None):
558
try:
559
response = niquests.get(f'/api/users/{user_id}')
560
response.raise_for_status()
561
return response.json()
562
except niquests.RequestException:
563
logger.warning(f"Failed to fetch user {user_id}, using fallback")
564
return fallback or {'id': user_id, 'name': 'Unknown User'}
565
566
# Pattern 3: Error categorization
567
def categorize_error(exception):
568
if isinstance(exception, niquests.HTTPError):
569
status = exception.response.status_code
570
if 400 <= status < 500:
571
return 'client_error'
572
elif 500 <= status < 600:
573
return 'server_error'
574
elif isinstance(exception, niquests.ConnectionError):
575
return 'network_error'
576
elif isinstance(exception, niquests.Timeout):
577
return 'timeout_error'
578
return 'unknown_error'
579
```