Skip to content

Commit

Permalink
Rename GrpcStatusError to GrpcError
Browse files Browse the repository at this point in the history
Also improve the docs for the class slightly to be more clear about
which errors should be more protocol-independent and safer to catch.

Signed-off-by: Leandro Lucarella <[email protected]>
  • Loading branch information
llucax committed May 29, 2024
1 parent 72126af commit 684f353
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 38 deletions.
4 changes: 2 additions & 2 deletions src/frequenz/client/microgrid/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
DataLoss,
EntityAlreadyExists,
EntityNotFound,
GrpcStatusError,
GrpcError,
InternalError,
InvalidArgument,
OperationAborted,
Expand Down Expand Up @@ -69,7 +69,7 @@
"EntityNotFound",
"Fuse",
"GridMetadata",
"GrpcStatusError",
"GrpcError",
"InternalError",
"InvalidArgument",
"InverterData",
Expand Down
8 changes: 4 additions & 4 deletions src/frequenz/client/microgrid/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ async def components(self) -> Iterable[Component]:
Raises:
ClientError: If the are any errors communicating with the Microgrid API,
most likely a subclass of
[GrpcStatusError][frequenz.client.microgrid.GrpcStatusError].
[GrpcError][frequenz.client.microgrid.GrpcError].
"""
try:
component_list = await self.api.list_components(
Expand Down Expand Up @@ -172,7 +172,7 @@ async def connections(
Raises:
ClientError: If the are any errors communicating with the Microgrid API,
most likely a subclass of
[GrpcStatusError][frequenz.client.microgrid.GrpcStatusError].
[GrpcError][frequenz.client.microgrid.GrpcError].
"""
connection_filter = pb_microgrid.ConnectionFilter(
starts=list(starts), ends=list(ends)
Expand Down Expand Up @@ -391,7 +391,7 @@ async def set_power(self, component_id: int, power_w: float) -> None:
Raises:
ClientError: If the are any errors communicating with the Microgrid API,
most likely a subclass of
[GrpcStatusError][frequenz.client.microgrid.GrpcStatusError].
[GrpcError][frequenz.client.microgrid.GrpcError].
"""
try:
await self.api.set_power_active(
Expand Down Expand Up @@ -425,7 +425,7 @@ async def set_bounds(
greater than 0.
ClientError: If the are any errors communicating with the Microgrid API,
most likely a subclass of
[GrpcStatusError][frequenz.client.microgrid.GrpcStatusError].
[GrpcError][frequenz.client.microgrid.GrpcError].
"""
if upper < 0:
raise ValueError(f"Upper bound {upper} must be greater than or equal to 0.")
Expand Down
49 changes: 25 additions & 24 deletions src/frequenz/client/microgrid/_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def from_grpc_error(
server_url: str,
operation: str,
grpc_error: grpclib.GRPCError,
) -> GrpcStatusError:
) -> GrpcError:
"""Create an instance of the appropriate subclass from a gRPC error.
Args:
Expand All @@ -68,16 +68,16 @@ def from_grpc_error(
Returns:
An instance of
[GrpcStatusError][frequenz.client.microgrid.GrpcStatusError] if
[GrpcError][frequenz.client.microgrid.GrpcError] if
the gRPC status is not recognized, or an appropriate subclass if it is.
"""

class Ctor(Protocol):
"""A protocol for the constructor of a subclass of `GrpcStatusError`."""
"""A protocol for the constructor of a subclass of `GrpcError`."""

def __call__(
self, *, server_url: str, operation: str, grpc_error: grpclib.GRPCError
) -> GrpcStatusError: ...
) -> GrpcError: ...

status_map: dict[grpclib.Status, Ctor] = {
grpclib.Status.CANCELLED: OperationCancelled,
Expand Down Expand Up @@ -109,11 +109,12 @@ def __call__(
)


class GrpcStatusError(ClientError):
"""The gRPC server returned an error status code.
class GrpcError(ClientError):
"""The gRPC server returned an error with a status code.
These errors are specific to gRPC. If you want to use the client in
a protocol-independent way, you should avoid catching this exception.
a protocol-independent way, you should avoid catching this exception. Catching
subclasses that don't have *grpc* in their name should be protocol-independent.
References:
* [gRPC status
Expand Down Expand Up @@ -152,7 +153,7 @@ def __init__( # pylint: disable=too-many-arguments
"""The original gRPC error."""


class UnrecognizedGrpcStatus(GrpcStatusError):
class UnrecognizedGrpcStatus(GrpcError):
"""The gRPC server returned an unrecognized status code."""

def __init__(
Expand All @@ -174,7 +175,7 @@ def __init__(
)


class OperationCancelled(GrpcStatusError):
class OperationCancelled(GrpcError):
"""The operation was cancelled."""

def __init__(
Expand All @@ -196,7 +197,7 @@ def __init__(
)


class UnknownError(GrpcStatusError):
class UnknownError(GrpcError):
"""There was an error that can't be described using other statuses."""

def __init__(
Expand All @@ -218,7 +219,7 @@ def __init__(
)


class InvalidArgument(GrpcStatusError, ValueError):
class InvalidArgument(GrpcError, ValueError):
"""The client specified an invalid argument.
Note that this error differs from
Expand Down Expand Up @@ -246,7 +247,7 @@ def __init__(
)


class OperationTimedOut(GrpcStatusError):
class OperationTimedOut(GrpcError):
"""The time limit was exceeded while waiting for the operationt o complete.
For operations that change the state of the system, this error may be returned even
Expand Down Expand Up @@ -274,7 +275,7 @@ def __init__(
)


class EntityNotFound(GrpcStatusError):
class EntityNotFound(GrpcError):
"""The requested entity was not found.
Note that this error differs from
Expand All @@ -301,7 +302,7 @@ def __init__(
)


class EntityAlreadyExists(GrpcStatusError):
class EntityAlreadyExists(GrpcError):
"""The entity that we attempted to create already exists."""

def __init__(
Expand All @@ -323,7 +324,7 @@ def __init__(
)


class PermissionDenied(GrpcStatusError):
class PermissionDenied(GrpcError):
"""The caller does not have permission to execute the specified operation.
Note that when the operation is rejected due to other reasons, such as the resources
Expand Down Expand Up @@ -354,7 +355,7 @@ def __init__(
)


class ResourceExhausted(GrpcStatusError):
class ResourceExhausted(GrpcError):
"""Some resource has been exhausted (for example per-user quota, disk space, etc.)."""

def __init__(
Expand All @@ -377,7 +378,7 @@ def __init__(
)


class OperationPreconditionFailed(GrpcStatusError):
class OperationPreconditionFailed(GrpcError):
"""The operation was rejected because the system is not in a required state.
For example, the directory to be deleted is non-empty, an rmdir operation is applied
Expand Down Expand Up @@ -405,7 +406,7 @@ def __init__(
)


class OperationAborted(GrpcStatusError):
class OperationAborted(GrpcError):
"""The operation was aborted.
Typically due to a concurrency issue or transaction abort.
Expand All @@ -430,7 +431,7 @@ def __init__(
)


class OperationOutOfRange(GrpcStatusError):
class OperationOutOfRange(GrpcError):
"""The operation was attempted past the valid range.
Unlike [InvalidArgument][frequenz.client.microgrid.InvalidArgument], this
Expand Down Expand Up @@ -461,7 +462,7 @@ def __init__(
)


class OperationNotImplemented(GrpcStatusError):
class OperationNotImplemented(GrpcError):
"""The operation is not implemented or not supported/enabled in this service."""

def __init__(
Expand All @@ -484,7 +485,7 @@ def __init__(
)


class InternalError(GrpcStatusError):
class InternalError(GrpcError):
"""Some invariants expected by the underlying system have been broken.
This error code is reserved for serious errors.
Expand All @@ -510,7 +511,7 @@ def __init__(
)


class ServiceUnavailable(GrpcStatusError):
class ServiceUnavailable(GrpcError):
"""The service is currently unavailable.
This is most likely a transient condition, which can be corrected by retrying with
Expand All @@ -536,7 +537,7 @@ def __init__(
)


class DataLoss(GrpcStatusError):
class DataLoss(GrpcError):
"""Unrecoverable data loss or corruption."""

def __init__(
Expand All @@ -558,7 +559,7 @@ def __init__(
)


class OperationUnauthenticated(GrpcStatusError):
class OperationUnauthenticated(GrpcError):
"""The request does not have valid authentication credentials for the operation."""

def __init__(
Expand Down
16 changes: 8 additions & 8 deletions tests/test_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
DataLoss,
EntityAlreadyExists,
EntityNotFound,
GrpcStatusError,
GrpcError,
InternalError,
InvalidArgument,
OperationAborted,
Expand All @@ -33,15 +33,15 @@
)


class _GrpcStatusErrorCtor(Protocol):
"""A protocol for the constructor of a subclass of `GrpcStatusError`."""
class _GrpcErrorCtor(Protocol):
"""A protocol for the constructor of a subclass of `GrpcErrorCtor`."""

def __call__(
self, *, server_url: str, operation: str, grpc_error: grpclib.GRPCError
) -> GrpcStatusError: ...
) -> GrpcError: ...


ERROR_TUPLES: list[tuple[type[GrpcStatusError], grpclib.Status, str, bool]] = [
ERROR_TUPLES: list[tuple[type[GrpcError], grpclib.Status, str, bool]] = [
(
UnrecognizedGrpcStatus,
mock.MagicMock(name="unknown_status"),
Expand Down Expand Up @@ -146,7 +146,7 @@ def __call__(
"exception_class, grpc_status, expected_description, retryable", ERROR_TUPLES
)
def test_grpc_status_error(
exception_class: _GrpcStatusErrorCtor,
exception_class: _GrpcErrorCtor,
grpc_status: grpclib.Status,
expected_description: str,
retryable: bool,
Expand Down Expand Up @@ -176,7 +176,7 @@ def test_grpc_unknown_status_error() -> None:
"grpc error message",
"grpc error details",
)
exception = GrpcStatusError(
exception = GrpcError(
server_url="http://testserver",
operation="test_operation",
description=expected_description,
Expand Down Expand Up @@ -210,7 +210,7 @@ def test_client_error() -> None:
"exception_class, grpc_status, expected_description, retryable", ERROR_TUPLES
)
def test_from_grpc_error(
exception_class: type[GrpcStatusError],
exception_class: type[GrpcError],
grpc_status: grpclib.Status,
expected_description: str,
retryable: bool,
Expand Down

0 comments on commit 684f353

Please sign in to comment.