0
# Exception Handling
1
2
Comprehensive exception hierarchy for handling HTTP errors, parsing failures, and navigation issues. RestNavigator provides detailed error information to help diagnose and handle API interaction problems.
3
4
## Capabilities
5
6
### Main Exception Class
7
8
Primary exception raised for HTTP error responses with detailed context information.
9
10
```python { .api }
11
class HALNavigatorError(Exception):
12
def __init__(self, message: str, nav: 'HALNavigator', response):
13
"""
14
Exception raised for HTTP error responses (4xx, 5xx).
15
16
Parameters:
17
- message: Human-readable error message
18
- nav: HALNavigator instance that caused the error
19
- response: HTTP response object from requests
20
"""
21
22
@property
23
def nav(self) -> 'HALNavigator':
24
"""Navigator that caused the error"""
25
26
@property
27
def response(self):
28
"""HTTP response object"""
29
30
@property
31
def message(self) -> str:
32
"""Error message"""
33
34
@property
35
def status(self) -> int:
36
"""HTTP status code"""
37
```
38
39
### URL and Scheme Validation Errors
40
41
Exceptions for invalid URLs and protocol issues.
42
43
```python { .api }
44
class WileECoyoteException(ValueError):
45
"""
46
Raised for bad URL schemes.
47
48
Occurs when URL has invalid or missing scheme.
49
"""
50
51
class ZachMorrisException(ValueError):
52
"""
53
Raised for URLs with too many schemes.
54
55
Occurs when URL has multiple conflicting schemes.
56
"""
57
```
58
59
### Resource State Errors
60
61
Exceptions for accessing uninitialized or unavailable resource data.
62
63
```python { .api }
64
class NoResponseError(ValueError):
65
"""
66
Raised when accessing unfetched navigator fields.
67
68
Occurs when trying to access state/response data
69
before navigator has been resolved.
70
"""
71
```
72
73
### JSON Parsing Errors
74
75
Exception for non-JSON response handling.
76
77
```python { .api }
78
class UnexpectedlyNotJSON(TypeError):
79
def __init__(self, uri: str, response):
80
"""
81
Raised for non-JSON parseable responses.
82
83
Parameters:
84
- uri: Resource URI that returned non-JSON
85
- response: HTTP response object
86
"""
87
88
@property
89
def uri(self) -> str:
90
"""Resource URI"""
91
92
@property
93
def response(self):
94
"""HTTP response object"""
95
```
96
97
### Navigation Traversal Errors
98
99
Exception for invalid navigation operations and traversal failures.
100
101
```python { .api }
102
class OffTheRailsException(TypeError):
103
def __init__(self, traversal: list, index: int, intermediates: list, exception: Exception):
104
"""
105
Raised for invalid navigation traversals.
106
107
Parameters:
108
- traversal: The attempted traversal path
109
- index: Where in the traversal the failure occurred
110
- intermediates: Successfully traversed navigators
111
- exception: Original exception that caused the failure
112
"""
113
114
@property
115
def traversal(self) -> list:
116
"""The attempted traversal path"""
117
118
@property
119
def index(self) -> int:
120
"""Index where traversal failed"""
121
122
@property
123
def intermediates(self) -> list:
124
"""Intermediate navigators before failure"""
125
126
@property
127
def exception(self) -> Exception:
128
"""Original exception"""
129
```
130
131
## Error Handling Patterns
132
133
### Basic Error Handling
134
135
```python
136
from restnavigator import Navigator
137
from restnavigator.exc import HALNavigatorError
138
139
api = Navigator.hal('https://api.example.com/')
140
141
try:
142
user = api['users', 'nonexistent']()
143
except HALNavigatorError as e:
144
print(f"HTTP Error {e.status}: {e.message}")
145
print(f"Failed URL: {e.nav.uri}")
146
print(f"Response: {e.response.text}")
147
```
148
149
### Graceful Error Handling
150
151
```python
152
# Disable automatic exception raising
153
result = api['users'].create(invalid_data, raise_exc=False)
154
155
if not result: # Check if operation succeeded
156
status_code, reason = result.status
157
print(f"Creation failed: {status_code} {reason}")
158
159
# Access error details from response
160
if hasattr(result, 'state') and result.state:
161
print("Error details:", result.state)
162
```
163
164
### Specific Status Code Handling
165
166
```python
167
try:
168
user_data = api['users', user_id]()
169
except HALNavigatorError as e:
170
if e.status == 404:
171
print("User not found")
172
# Handle missing resource
173
elif e.status == 403:
174
print("Access denied")
175
# Handle permission error
176
elif e.status >= 500:
177
print("Server error, retrying...")
178
# Handle server errors
179
else:
180
print(f"Unexpected error: {e.status}")
181
raise
182
```
183
184
### Navigation Error Handling
185
186
```python
187
from restnavigator.exc import OffTheRailsException
188
189
try:
190
# Complex navigation that might fail
191
data = api['users', 0, 'posts', 'comments', 'invalid']()
192
except OffTheRailsException as e:
193
print(f"Navigation failed at step {e.index}")
194
print(f"Attempted path: {e.traversal}")
195
print(f"Successfully navigated: {len(e.intermediates)} steps")
196
print(f"Error: {e.exception}")
197
198
# Work with successful intermediate results
199
if e.intermediates:
200
last_successful = e.intermediates[-1]
201
print(f"Last successful resource: {last_successful.uri}")
202
```
203
204
### JSON Parsing Error Handling
205
206
```python
207
from restnavigator.exc import UnexpectedlyNotJSON
208
209
try:
210
# Request might return non-JSON (e.g., HTML error page)
211
data = api['csv-export']()
212
except UnexpectedlyNotJSON as e:
213
print(f"Expected JSON from {e.uri}")
214
print(f"Got content type: {e.response.headers.get('content-type')}")
215
216
# Handle non-JSON response appropriately
217
if 'text/csv' in e.response.headers.get('content-type', ''):
218
csv_data = e.response.text
219
# Process CSV data
220
else:
221
print(f"Unexpected content: {e.response.text[:200]}...")
222
```
223
224
### URL Validation Error Handling
225
226
```python
227
from restnavigator.exc import WileECoyoteException, ZachMorrisException
228
229
try:
230
# This might have URL issues
231
api = Navigator.hal('invalid-url')
232
except WileECoyoteException:
233
print("Invalid URL scheme - make sure URL starts with http:// or https://")
234
except ZachMorrisException:
235
print("Multiple conflicting schemes in URL")
236
```
237
238
### Resource State Error Handling
239
240
```python
241
from restnavigator.exc import NoResponseError
242
243
# Create navigator but don't fetch yet
244
user = api['users', user_id]
245
246
try:
247
# This will fail because we haven't fetched the resource
248
print(user.state)
249
except NoResponseError:
250
print("Resource not yet fetched")
251
# Fetch the resource first
252
user_data = user()
253
print(user.state) # Now this works
254
```
255
256
### Comprehensive Error Handling Strategy
257
258
```python
259
def safe_api_call(navigator_func):
260
"""Wrapper for safe API calls with comprehensive error handling"""
261
try:
262
return navigator_func()
263
except HALNavigatorError as e:
264
if e.status in [401, 403]:
265
# Authentication/authorization error
266
handle_auth_error(e)
267
elif e.status == 404:
268
# Resource not found
269
return None
270
elif e.status == 429:
271
# Rate limiting
272
handle_rate_limit(e)
273
elif e.status >= 500:
274
# Server error
275
handle_server_error(e)
276
else:
277
# Other HTTP errors
278
log_error(f"HTTP {e.status}: {e.message}")
279
raise
280
except OffTheRailsException as e:
281
# Navigation error
282
log_error(f"Navigation failed: {e.traversal}")
283
return None
284
except UnexpectedlyNotJSON as e:
285
# Content type error
286
log_error(f"Non-JSON response from {e.uri}")
287
return e.response.text
288
except (WileECoyoteException, ZachMorrisException) as e:
289
# URL validation error
290
log_error(f"Invalid URL: {e}")
291
raise ValueError("Invalid API URL configuration")
292
except NoResponseError:
293
# Resource state error
294
log_error("Attempted to access unfetched resource")
295
return None
296
297
# Usage
298
user_data = safe_api_call(lambda: api['users', user_id]())
299
if user_data is None:
300
print("Failed to retrieve user data")
301
```
302
303
### Error Context and Debugging
304
305
```python
306
def debug_error(exception):
307
"""Extract comprehensive error information for debugging"""
308
if isinstance(exception, HALNavigatorError):
309
print(f"HTTP Error: {exception.status}")
310
print(f"URL: {exception.nav.uri}")
311
print(f"Message: {exception.message}")
312
print(f"Response Headers: {dict(exception.response.headers)}")
313
print(f"Response Body: {exception.response.text}")
314
315
# Check request details
316
request = exception.response.request
317
print(f"Request Method: {request.method}")
318
print(f"Request Headers: {dict(request.headers)}")
319
if request.body:
320
print(f"Request Body: {request.body}")
321
322
elif isinstance(exception, OffTheRailsException):
323
print(f"Navigation Error at step {exception.index}")
324
print(f"Full path: {exception.traversal}")
325
print(f"Completed steps: {len(exception.intermediates)}")
326
for i, nav in enumerate(exception.intermediates):
327
print(f" Step {i}: {nav.uri}")
328
print(f"Underlying error: {exception.exception}")
329
330
# Usage in exception handler
331
try:
332
data = api['complex', 'navigation', 'path']()
333
except Exception as e:
334
debug_error(e)
335
raise
336
```