Skip to content

Conversation

@SoulPancake
Copy link
Member

@SoulPancake SoulPancake commented Nov 21, 2025

Description

What problem is being solved?

How is it being solved?

What changes are made to solve it?

References

Review Checklist

  • I have clicked on "allow edits by maintainers".
  • I have added documentation for new/changed functionality in this PR or in a PR to openfga.dev [Provide a link to any relevant PRs in the references section above]
  • The correct base branch is being used, if not main
  • I have added tests to validate that the change in functionality is working as expected

Summary by CodeRabbit

  • New Features

    • Enhanced error messages with operation context, HTTP status codes, error codes, and request IDs.
    • Added helper methods for error categorization (validation, authentication, rate limit, retryable, etc.).
  • Documentation

    • Added "Error Handling" section to README with comprehensive examples and best practices.
  • Tests

    • Added unit and integration tests for enhanced error handling.

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings November 21, 2025 11:35
@SoulPancake SoulPancake requested review from a team as code owners November 21, 2025 11:35
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

The changes enhance error handling across the SDK by adding operation context to API exceptions, improving error message formatting with structured details (status, code, message, request ID), introducing helper methods for error categorization (validation, not found, authentication, rate limit, retryable, client/server errors), and adding comprehensive documentation and tests. Both async and sync implementations are updated consistently.

Changes

Cohort / File(s) Summary
Documentation
README.md
Added comprehensive error handling section documenting operation context, API error codes/messages, and helper methods for error categorization with Python usage examples
Exception Infrastructure
openfga_sdk/exceptions.py
Enhanced ApiException with operation_name parameter, structured str method, new properties (code, error_message, request_id), and helper predicates (is_validation_error, is_not_found_error, is_authentication_error, is_rate_limit_error, is_retryable, is_client_error, is_server_error). Updated all exception subclass constructors to accept and propagate operation_name
Error Handling (Async)
openfga_sdk/api_client.py
Added operation_name attachment to ApiException from telemetry attributes in exception handling paths before re-raising
Error Handling (Sync)
openfga_sdk/sync/api_client.py
Added operation_name propagation to ApiException from telemetry in both RateLimitExceededError/ServiceException and ApiException handling paths
Unit Tests
test/error_handling_test.py
New comprehensive unit test module validating error message formatting, property accessibility, helper method behavior, and edge cases with mock HTTP responses
Integration Tests
test/error_integration_test.py
New integration test module with async and sync variants exercising client operations against a running OpenFGA server to validate error handling across validation, not found, categorization, and helper method scenarios

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant APIClient as API Client
    participant Telemetry as Telemetry<br/>Attributes
    participant Exception as ApiException
    
    Client->>APIClient: Make API call
    APIClient->>APIClient: __call_api()
    
    alt HTTP Error Response
        APIClient->>APIClient: Parse error response
        APIClient->>Telemetry: Get fga_client_request_method
        Telemetry-->>APIClient: operation method name
        
        rect rgb(200, 220, 240)
            note over APIClient,Exception: NEW: Attach operation context
            APIClient->>Exception: exception.operation_name =<br/>method_name.lower()
        end
        
        APIClient->>APIClient: Raise exception
    end
    
    Exception-->>Client: Throw enhanced exception<br/>with operation context
    Client->>Exception: Access properties:<br/>operation_name, code,<br/>error_message, request_id
    Client->>Exception: Call helper methods:<br/>is_validation_error(),<br/>is_retryable(), etc.
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Areas requiring extra attention:

  • openfga_sdk/exceptions.py: Dense logic changes with multiple new properties (code, error_message, request_id extraction logic), revised str method with structured formatting, and helper predicates requiring correctness verification across all exception types
  • Exception subclass constructors: All seven exception subclasses modified; verify consistency in operation_name parameter propagation to parent ApiException
  • Async vs Sync consistency: Verify that openfga_sdk/api_client.py and openfga_sdk/sync/api_client.py implement identical error handling logic for operation_name attachment
  • Integration tests (error_integration_test.py): High complexity test module; requires verification that server assumptions are clearly documented and test setup/cleanup is robust

Suggested reviewers

  • ttrzeng

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: improve error messaging' accurately reflects the main changes: enhanced error handling with better error messages, structured output, helper methods, and operation context tracking across multiple SDK files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/improve-errors

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link

codecov-commenter commented Nov 21, 2025

Codecov Report

❌ Patch coverage is 96.42857% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.17%. Comparing base (0604026) to head (10fbf4c).

Files with missing lines Patch % Lines
openfga_sdk/exceptions.py 95.38% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #245      +/-   ##
==========================================
+ Coverage   70.99%   71.17%   +0.18%     
==========================================
  Files         137      137              
  Lines       11038    11100      +62     
==========================================
+ Hits         7836     7900      +64     
+ Misses       3202     3200       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR enhances error handling in the OpenFGA Python SDK by adding structured error messages with operation context, exposing error codes and request IDs, and providing convenient helper methods for error categorization. The changes make it easier for SDK users to understand, debug, and handle different types of errors programmatically.

Key changes:

  • Enhanced error message formatting with operation context and structured details
  • Added properties (code, error_message, request_id) and helper methods (is_validation_error(), is_retryable(), etc.) to exception classes
  • Modified exception constructors to support an optional operation_name parameter
  • Updated API clients to automatically populate operation names from telemetry attributes

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
openfga_sdk/exceptions.py Enhanced ApiException with new properties, helper methods, and improved __str__ formatting; updated all exception subclasses to accept operation_name parameter
openfga_sdk/api_client.py Added logic to set operation_name on exceptions from telemetry attributes in async API client
openfga_sdk/sync/api_client.py Added logic to set operation_name on exceptions from telemetry attributes in sync API client
test/error_handling_test.py Comprehensive unit tests for error formatting, properties, and helper methods
test/error_integration_test.py Integration tests for error handling with real OpenFGA server
README.md Added documentation for error handling features with usage examples

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
README.md (1)

1410-1431: Align error-handling example with existing write API and naming

To keep the docs consistent and avoid confusion:

  • Reuse the fga_client naming from earlier examples.
  • Avoid using tuple as a variable name (shadows built‑in).
  • Show a realistic write call (e.g., via ClientWriteRequest and ClientTuple) so the snippet is copy‑pasteable.

For example:

-from openfga_sdk.exceptions import ApiException
-
-try:
-    await client.write([tuple])
+from openfga_sdk.exceptions import ApiException
+from openfga_sdk.client.models import ClientTuple, ClientWriteRequest
+
+try:
+    await fga_client.write(
+        ClientWriteRequest(
+            writes=[
+                ClientTuple(
+                    user="user:anne",
+                    relation="viewer",
+                    object="document:readme",
+                )
+            ]
+        )
+    )

The rest of the snippet (checking is_validation_error() / is_retryable()) looks good.

openfga_sdk/api_client.py (1)

319-326: Operation name propagation from telemetry into ApiException looks correct

The new blocks correctly:

  • Pull fga_client.request.method from _telemetry_attributes and
  • Normalize it with .lower() before assigning e.operation_name,

so exceptions can render [write], [check], etc. This matches the new ApiException.__str__ semantics.

You could optionally:

  • Drop the isinstance(e, ApiException) checks here (the caught types are already ApiException subclasses), and/or
  • Extract a tiny helper (e.g., _set_operation_name_from_telemetry(e, _telemetry_attributes)) to avoid duplicating the same pattern across both except branches (and in the sync client).

But as‑is, the logic is sound.

Also applies to: 359-366

openfga_sdk/sync/api_client.py (1)

317-326: Sync client error context is now consistent with async client

The sync ApiClient now mirrors the async behaviour by setting e.operation_name from TelemetryAttributes.fga_client_request_method before re‑raising, which ensures consistent [method] ... error strings and helper behaviour across both clients.

As with the async version, you might:

  • Remove the redundant isinstance(e, ApiException) checks, and/or
  • Factor this into a shared helper to avoid repeating the same pattern in two branches and two files.

Functionally, this looks good.

Also applies to: 359-367

test/error_integration_test.py (1)

57-96: Integration tests correctly validate enriched error context; consider closing async client

The async and sync integration tests exercise the right behaviours:

  • They confirm operation_name (write, check, expand) is populated from telemetry.
  • They assert status, code, error_message, helper predicates, and the formatted string across multiple failure modes.
  • Skip logic based on FGA_API_URL//.dockerenv keeps CI flexible.

One small improvement for the async fixture:

  • In fga_client, you create OpenFgaClient(config) instances but never await client.close() or use async with OpenFgaClient(...) in setup/teardown. Depending on the underlying HTTP stack, this can lead to unclosed client sessions and noisy warnings under pytest.

You could, for example, ensure cleanup closes the client:

@@ async def fga_client(self):
-        client = OpenFgaClient(config)
+        client = OpenFgaClient(config)
@@
-        yield client
-
-        # Cleanup: delete the store
-        try:
-            await client.delete_store()
-        except Exception:
-            pass  # Ignore cleanup errors
+        try:
+            yield client
+        finally:
+            try:
+                await client.delete_store()
+            except Exception:
+                pass
+            await client.close()

The sync fixture is fine as‑is; its ApiClient.close() only manages the thread pool and doesn’t require async teardown.

Also applies to: 425-463

openfga_sdk/exceptions.py (1)

233-297: Consider teaching the helper predicates about the specific subclasses

The helpers are handy, but they currently ignore the dedicated subclasses:

  • is_authentication_error doesn’t check AuthenticationError / UnauthorizedException.
  • is_rate_limit_error doesn’t check RateLimitExceededError.

If these subclasses are ever raised without a populated HTTP status/code, the helpers will return False. You could make them a bit more robust with something like:

     def is_authentication_error(self):
@@
-        return self.status == 401
+        return isinstance(self, (AuthenticationError, UnauthorizedException)) or self.status == 401
@@
     def is_rate_limit_error(self):
@@
-        return self.status == 429 or (self.code and "rate_limit" in self.code.lower())
+        return isinstance(self, RateLimitExceededError) or (
+            self.status == 429 or (self.code and "rate_limit" in self.code.lower())
+        )

This keeps status/code checks intact while also honoring the semantic subclasses.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0604026 and e6ed185.

📒 Files selected for processing (6)
  • README.md (1 hunks)
  • openfga_sdk/api_client.py (2 hunks)
  • openfga_sdk/exceptions.py (3 hunks)
  • openfga_sdk/sync/api_client.py (2 hunks)
  • test/error_handling_test.py (1 hunks)
  • test/error_integration_test.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
test/error_integration_test.py (7)
openfga_sdk/client/configuration.py (1)
  • ClientConfiguration (14-78)
openfga_sdk/exceptions.py (12)
  • NotFoundException (299-301)
  • ValidationException (319-321)
  • code (188-201)
  • error_message (204-215)
  • is_not_found_error (244-251)
  • is_client_error (280-287)
  • is_server_error (289-296)
  • is_retryable (271-278)
  • is_validation_error (233-242)
  • is_authentication_error (253-260)
  • is_rate_limit_error (262-269)
  • request_id (218-231)
openfga_sdk/models/create_store_request.py (1)
  • CreateStoreRequest (20-120)
openfga_sdk/client/models/tuple.py (1)
  • ClientTuple (5-75)
openfga_sdk/client/models/write_request.py (1)
  • ClientWriteRequest (6-107)
openfga_sdk/client/models/check_request.py (1)
  • ClientCheckRequest (4-94)
openfga_sdk/client/models/expand_request.py (1)
  • ClientExpandRequest (4-59)
openfga_sdk/api_client.py (3)
openfga_sdk/exceptions.py (1)
  • ApiException (118-296)
openfga_sdk/telemetry/attributes.py (1)
  • TelemetryAttributes (19-369)
openfga_sdk/telemetry/configuration.py (2)
  • fga_client_request_method (175-182)
  • fga_client_request_method (185-193)
openfga_sdk/sync/api_client.py (2)
openfga_sdk/exceptions.py (1)
  • ApiException (118-296)
openfga_sdk/telemetry/attributes.py (1)
  • TelemetryAttributes (19-369)
test/error_handling_test.py (3)
openfga_sdk/exceptions.py (17)
  • ApiException (118-296)
  • NotFoundException (299-301)
  • RateLimitExceededError (329-331)
  • ServiceException (314-316)
  • ValidationException (319-321)
  • code (188-201)
  • parsed_exception (174-178)
  • parsed_exception (181-185)
  • error_message (204-215)
  • request_id (218-231)
  • is_validation_error (233-242)
  • is_client_error (280-287)
  • is_server_error (289-296)
  • is_retryable (271-278)
  • is_not_found_error (244-251)
  • is_authentication_error (253-260)
  • is_rate_limit_error (262-269)
openfga_sdk/models/validation_error_message_response.py (1)
  • ValidationErrorMessageResponse (20-143)
openfga_sdk/models/error_code.py (1)
  • ErrorCode (20-212)
openfga_sdk/exceptions.py (5)
openfga_sdk/sync/rest.py (4)
  • status (84-88)
  • status (91-95)
  • reason (98-102)
  • reason (105-109)
openfga_sdk/rest.py (4)
  • status (84-88)
  • status (91-95)
  • reason (98-102)
  • reason (105-109)
openfga_sdk/models/forbidden_response.py (2)
  • code (54-61)
  • code (64-72)
openfga_sdk/models/internal_error_message_response.py (4)
  • code (54-61)
  • code (64-72)
  • message (75-82)
  • message (85-93)
openfga_sdk/models/validation_error_message_response.py (4)
  • code (54-61)
  • code (64-72)
  • message (75-82)
  • message (85-93)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🔇 Additional comments (5)
test/error_handling_test.py (1)

1-310: Thorough unit coverage of enhanced ApiException behaviour

This test module does a solid job of exercising:

  • Formatted error strings (including operation name, HTTP status, code, message, request‑id),
  • Property accessors (code, error_message, request_id, operation_name),
  • All the helper predicates (validation/not-found/auth/rate‑limit/retryable/client/server),
  • Subclass behaviours and enum‑backed codes.

The MockHTTPResponse / MockHeaders shims match how ApiException consumes http_resp, so this should remain stable even as the HTTP layer evolves.

openfga_sdk/exceptions.py (4)

119-142: Operation name plumbing is straightforward and backward‑compatible

Appending operation_name at the end of the constructor and simply storing it on self.operation_name keeps existing call sites working while enabling richer context; the rest of the init logic is unchanged and looks correct.


143-172: Structured __str__ formatting looks good; watch for callers relying on old format

The new composition [operation] HTTP <status> <message> (<code>) [request-id: ...] is clear and uses the derived helpers appropriately. It does, however, change the exception’s string representation semantics, so just confirm there are no consumers parsing or snapshotting the previous str(ApiException) format (or update them alongside this change).


187-232: Derived properties for code/message/request-id are well factored

Using parsed_exception as the primary source and falling back to HTTP reason/defaults is sensible, and the small enum handling in code plus case-insensitive lookup in request_id match the surrounding models and header handling.


299-332: Subclass constructors correctly forward operation_name

All specialized exceptions mirror the base signature and pass operation_name through, so existing usages remain valid while gaining the extra context.

@SoulPancake SoulPancake added this pull request to the merge queue Nov 25, 2025
Merged via the queue into main with commit 5227a6a Nov 25, 2025
28 checks passed
@SoulPancake SoulPancake deleted the feat/improve-errors branch November 25, 2025 11:09
@coderabbitai coderabbitai bot mentioned this pull request Dec 8, 2025
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants