From 060dbe9ab4438a3992ef30fefc2f255dcc100403 Mon Sep 17 00:00:00 2001 From: ivanauth Date: Tue, 16 Jun 2026 19:02:42 -0400 Subject: [PATCH 1/2] feat: add AuthzedError unified exception for sync/async gRPC errors --- src/grpcutil/__init__.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/grpcutil/__init__.py b/src/grpcutil/__init__.py index 143900a..ca991f1 100644 --- a/src/grpcutil/__init__.py +++ b/src/grpcutil/__init__.py @@ -3,6 +3,23 @@ import grpc +class AuthzedError(Exception): + """Unified exception for both sync and async gRPC errors. + + This exception wraps both grpc.RpcError (sync) and grpc.aio.AioRpcError (async) + to provide a consistent exception type for error handling. + """ + + def __init__(self, message: str, grpc_error: grpc.RpcError): + super().__init__(message) + self.grpc_error = grpc_error + + @classmethod + def from_grpc_error(cls, error: grpc.RpcError) -> "AuthzedError": + """Create an AuthzedError from a gRPC error.""" + return cls(str(error), error) + + def bearer_token_credentials(token: str, certChain: Optional[bytes] = None): """ gRPC credentials for a service that requires a Bearer Token. From 62bef6729f14744274c1e5a2bcdc8bb5324f4305 Mon Sep 17 00:00:00 2001 From: ivanauth Date: Tue, 16 Jun 2026 22:24:08 -0400 Subject: [PATCH 2/2] fix(grpcutil): make AuthzedError actually catch sync and async errors (#14) The previous AuthzedError subclassed Exception and was never raised, so 'except AuthzedError' caught nothing. grpc.aio.AioRpcError already subclasses grpc.RpcError, so alias AuthzedError to grpc.RpcError; a single except now catches errors from both the sync and async clients. Adds a regression test. --- src/grpcutil/__init__.py | 26 ++++++++++---------------- tests/misc_test.py | 13 +++++++++++++ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/grpcutil/__init__.py b/src/grpcutil/__init__.py index ca991f1..71f7457 100644 --- a/src/grpcutil/__init__.py +++ b/src/grpcutil/__init__.py @@ -2,22 +2,16 @@ import grpc - -class AuthzedError(Exception): - """Unified exception for both sync and async gRPC errors. - - This exception wraps both grpc.RpcError (sync) and grpc.aio.AioRpcError (async) - to provide a consistent exception type for error handling. - """ - - def __init__(self, message: str, grpc_error: grpc.RpcError): - super().__init__(message) - self.grpc_error = grpc_error - - @classmethod - def from_grpc_error(cls, error: grpc.RpcError) -> "AuthzedError": - """Create an AuthzedError from a gRPC error.""" - return cls(str(error), error) +# Unified exception type for catching errors from both the sync and async +# clients. grpc.aio.AioRpcError (raised by the async client) already subclasses +# grpc.RpcError (raised by the sync client), so a single ``except AuthzedError`` +# catches errors from either: +# +# try: +# client.CheckPermission(...) +# except AuthzedError as err: +# ... +AuthzedError = grpc.RpcError def bearer_token_credentials(token: str, certChain: Optional[bytes] = None): diff --git a/tests/misc_test.py b/tests/misc_test.py index dcd96d6..e7249c7 100644 --- a/tests/misc_test.py +++ b/tests/misc_test.py @@ -1,7 +1,20 @@ +import grpc +import grpc.aio import pytest from protovalidate import ValidationError, validate from authzed.api.v1 import ObjectReference, Relationship +from grpcutil import AuthzedError + + +def test_authzed_error_catches_sync_and_async_grpc_errors(): + # The sync client raises grpc.RpcError; the async client raises + # grpc.aio.AioRpcError. AuthzedError must catch both. + with pytest.raises(AuthzedError): + raise grpc.RpcError("sync error") + + assert issubclass(grpc.RpcError, AuthzedError) + assert issubclass(grpc.aio.AioRpcError, AuthzedError) def test_type_error_does_not_segfault():