Skip to content

Commit 971e757

Browse files
committed
docs: Add comprehensive error handling improvements documentation
1 parent 9a82c20 commit 971e757

1 file changed

Lines changed: 311 additions & 0 deletions

File tree

ERROR_HANDLING_IMPROVEMENTS.md

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
# Error Handling Improvements - OpenFGA Python SDK
2+
3+
## Summary
4+
5+
This document outlines the improvements made to error handling in the OpenFGA Python SDK. These changes make errors easier to work with, more informative, and less verbose to handle.
6+
7+
## What Changed
8+
9+
### 1. Convenience Properties
10+
11+
Instead of nested access patterns, errors now provide direct property access.
12+
13+
#### Before (Old Way)
14+
```python
15+
try:
16+
client.check(...)
17+
except ApiException as e:
18+
# Verbose nested access
19+
code = e.parsed_exception.code if e.parsed_exception else None
20+
message = e.parsed_exception.message if e.parsed_exception else None
21+
request_id = e.header.get('fga-request-id')
22+
store_id = e.header.get('store_id')
23+
model_id = e.header.get('openfga_authorization_model_id')
24+
```
25+
26+
#### After (New Way)
27+
```python
28+
try:
29+
client.check(...)
30+
except ApiException as e:
31+
# Direct, clean access
32+
code = e.code
33+
message = e.error_message
34+
request_id = e.request_id
35+
store_id = e.store_id
36+
model_id = e.authorization_model_id
37+
```
38+
39+
### 2. Helper Methods for Error Classification
40+
41+
No more manual type checking or status code comparisons.
42+
43+
#### Before (Old Way)
44+
```python
45+
try:
46+
client.write(...)
47+
except ApiException as e:
48+
# Manual type checking
49+
if isinstance(e, ValidationException):
50+
# Handle validation error
51+
pass
52+
elif isinstance(e, NotFoundException):
53+
# Handle not found
54+
pass
55+
elif e.status == 429:
56+
# Handle rate limit
57+
pass
58+
elif e.status >= 500:
59+
# Handle server error
60+
pass
61+
```
62+
63+
#### After (New Way)
64+
```python
65+
try:
66+
client.write(...)
67+
except ApiException as e:
68+
# Clean, semantic methods
69+
if e.is_validation_error():
70+
# Handle validation error
71+
pass
72+
elif e.is_not_found_error():
73+
# Handle not found
74+
pass
75+
elif e.is_rate_limit_error():
76+
# Handle rate limit
77+
pass
78+
elif e.is_server_error():
79+
# Handle server error
80+
pass
81+
elif e.is_retryable():
82+
# Retry the operation
83+
pass
84+
```
85+
86+
### 3. Enhanced Error Messages
87+
88+
Error messages now include comprehensive context.
89+
90+
#### Before (Old Way)
91+
```
92+
(400)
93+
Reason: Bad Request
94+
HTTP response body: {"code":"validation_error","message":"Invalid tuple format"}
95+
```
96+
97+
#### After (New Way)
98+
```
99+
Operation: Check
100+
Status: 400
101+
Error Code: validation_error
102+
Message: Invalid tuple format
103+
Request ID: abc-123-def-456
104+
Store ID: 01HXXX...
105+
Authorization Model ID: 01GYYY...
106+
```
107+
108+
### 4. Operation Context
109+
110+
Errors now track which operation failed, making debugging much easier.
111+
112+
```python
113+
try:
114+
client.check(...)
115+
except ApiException as e:
116+
print(f"Operation '{e.operation_name}' failed")
117+
# Output: Operation 'Check' failed
118+
```
119+
120+
## Available Properties
121+
122+
All `ApiException` instances now have these properties:
123+
124+
| Property | Type | Description |
125+
|----------|------|-------------|
126+
| `code` | `str \| None` | Error code (e.g., "validation_error") |
127+
| `error_message` | `str \| None` | Human-readable error message |
128+
| `request_id` | `str \| None` | FGA request ID for tracing |
129+
| `store_id` | `str \| None` | Store ID context |
130+
| `authorization_model_id` | `str \| None` | Authorization model ID context |
131+
| `operation_name` | `str \| None` | Operation that failed (e.g., "Check", "Write") |
132+
133+
## Available Helper Methods
134+
135+
All `ApiException` instances now have these methods:
136+
137+
| Method | Returns | Description |
138+
|--------|---------|-------------|
139+
| `is_validation_error()` | `bool` | True if this is a validation error (4xx) |
140+
| `is_not_found_error()` | `bool` | True if this is a not found error (404) |
141+
| `is_authentication_error()` | `bool` | True if this is an authentication error (401) |
142+
| `is_authorization_error()` | `bool` | True if this is an authorization error (403) |
143+
| `is_rate_limit_error()` | `bool` | True if this is a rate limit error (429) |
144+
| `is_server_error()` | `bool` | True if this is a server error (5xx) |
145+
| `is_retryable()` | `bool` | True if this error should be retried |
146+
147+
## Backward Compatibility
148+
149+
All changes are **fully backward compatible**. Existing code continues to work without modifications:
150+
151+
```python
152+
# Old code still works
153+
try:
154+
client.check(...)
155+
except ApiException as e:
156+
if e.parsed_exception:
157+
code = e.parsed_exception.code # Still works!
158+
request_id = e.header.get('fga-request-id') # Still works!
159+
```
160+
161+
## Testing
162+
163+
### Unit Tests
164+
Run the comprehensive unit test suite:
165+
```bash
166+
python -m unittest test.error_handling_improvements_test -v
167+
```
168+
169+
17 test cases verify:
170+
- Convenience properties work correctly
171+
- Helper methods classify errors properly
172+
- Enhanced error messages include all context
173+
- Backward compatibility is maintained
174+
175+
### Integration Tests
176+
Run tests against a real OpenFGA server:
177+
```bash
178+
# Start OpenFGA server
179+
docker compose -f docker-compose.integration-test.yml up -d
180+
181+
# Run integration tests
182+
python -m unittest test.integration_error_handling_test -v
183+
184+
# Or use the helper script
185+
./run_integration_tests.sh
186+
```
187+
188+
Integration tests demonstrate:
189+
- Real error scenarios with live server
190+
- All new features working end-to-end
191+
- Practical examples of improved error handling
192+
193+
See `test/README_INTEGRATION_TESTS.md` for detailed testing instructions.
194+
195+
## Example: Complete Error Handling Pattern
196+
197+
Here's a complete example showing how to use the new features:
198+
199+
```python
200+
from openfga_sdk import OpenFgaClient
201+
from openfga_sdk.exceptions import ApiException
202+
import asyncio
203+
204+
async def main():
205+
client = OpenFgaClient(...)
206+
207+
try:
208+
result = await client.check(
209+
body=CheckRequest(
210+
tuple_key=TupleKey(
211+
user="user:anne",
212+
relation="reader",
213+
object="document:budget"
214+
)
215+
)
216+
)
217+
except ApiException as e:
218+
# Use convenient properties
219+
print(f"Operation: {e.operation_name}")
220+
print(f"Error Code: {e.code}")
221+
print(f"Message: {e.error_message}")
222+
print(f"Request ID: {e.request_id}")
223+
224+
# Use helper methods for control flow
225+
if e.is_validation_error():
226+
print("Fix your request and try again")
227+
elif e.is_authentication_error():
228+
print("Check your credentials")
229+
elif e.is_rate_limit_error():
230+
print("Slow down and retry")
231+
elif e.is_retryable():
232+
print("Temporary issue, retrying...")
233+
# Implement retry logic
234+
else:
235+
print("Unrecoverable error")
236+
raise
237+
238+
# Or just print the enhanced error message
239+
print(str(e))
240+
241+
asyncio.run(main())
242+
```
243+
244+
## Migration Guide
245+
246+
No migration needed! But you can improve your existing code:
247+
248+
### Quick Wins
249+
250+
1. **Replace nested access with properties:**
251+
```python
252+
# Before
253+
code = e.parsed_exception.code if e.parsed_exception else None
254+
# After
255+
code = e.code
256+
```
257+
258+
2. **Use helper methods instead of type checks:**
259+
```python
260+
# Before
261+
if isinstance(e, ValidationException):
262+
# After
263+
if e.is_validation_error():
264+
```
265+
266+
3. **Leverage enhanced error messages:**
267+
```python
268+
# Before
269+
print(f"Error {e.status}: {e.reason}")
270+
# After
271+
print(str(e)) # Much more informative!
272+
```
273+
274+
## Implementation Details
275+
276+
### Files Modified
277+
278+
1. **openfga_sdk/exceptions.py**
279+
- Added `operation_name` parameter to all exception classes
280+
- Added convenience properties: `code`, `error_message`, `request_id`, `store_id`, `authorization_model_id`
281+
- Added helper methods: `is_validation_error()`, `is_not_found_error()`, etc.
282+
- Enhanced `__str__()` method with comprehensive formatting
283+
284+
2. **openfga_sdk/api_client.py**
285+
- Added `_operation_name` parameter to `call_api()` and `__call_api()` methods
286+
- Set `operation_name` on exceptions before raising
287+
288+
3. **openfga_sdk/sync/api_client.py**
289+
- Same changes as async version for synchronous client
290+
291+
### Design Principles
292+
293+
- **Backward Compatibility:** All existing code continues to work
294+
- **No Breaking Changes:** Only additive changes
295+
- **Pythonic:** Uses properties and methods, not nested data structures
296+
- **Type Safe:** All properties and methods properly typed
297+
- **Well Tested:** 17 unit tests + integration tests
298+
299+
## Benefits
300+
301+
**Less Verbose:** Direct property access instead of nested conditionals
302+
**More Readable:** Semantic helper methods instead of type checking
303+
**Better DX:** Enhanced error messages with full context
304+
**Easier Debugging:** Operation names show what failed
305+
**Safer:** Type hints and proper error classification
306+
**Backward Compatible:** No breaking changes
307+
308+
## Questions?
309+
310+
See `test/error_handling_improvements_test.py` for comprehensive examples of all features.
311+
See `test/integration_error_handling_test.py` for real-world usage patterns.

0 commit comments

Comments
 (0)