Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/momento/errors/error_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ def convert_error(
if isinstance(exception, grpc.RpcError):
status_code: grpc.StatusCode = exception.code()
details = exception.details()

# `err` metadata can be found only in the trailers of the grpc.RpcError type
trailers = exception.trailing_metadata()
if isinstance(trailers, list) or isinstance(trailers, tuple):
trailers = _synchronous_metadata_to_metadata(exception.trailing_metadata())
transport_metadata = _combine_metadata_with_trailers(transport_metadata, trailers)

transport_details = MomentoErrorTransportDetails(
MomentoGrpcErrorDetails(status_code, details, transport_metadata)
)
Expand Down Expand Up @@ -106,3 +113,22 @@ def _synchronous_metadata_to_metadata(metadata: list[Tuple[str, str]]) -> Metada
for key, value in metadata:
new_metadata.add(key, value)
return new_metadata


def _combine_metadata_with_trailers(metadata: Optional[Metadata], trailers: Metadata) -> Metadata:
"""Combine two metadata objects into a single metadata object.

Args:
metadata (Optional[Metadata]): The first metadata object.
trailers (Metadata): The second metadata object.

Returns:
Metadata: The combined metadata object.
"""
combined_metadata = Metadata()
if metadata is not None:
for key, value in metadata:
combined_metadata.add(key, value)
for key, value in trailers:
combined_metadata.add(key, value)
return combined_metadata
50 changes: 49 additions & 1 deletion src/momento/errors/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from enum import Enum
from typing import Optional

from momento.errors import MomentoErrorCode, MomentoErrorTransportDetails
Expand Down Expand Up @@ -213,10 +214,57 @@ def __init__(
MomentoErrorCode.LIMIT_EXCEEDED_ERROR,
service,
transport_details,
message_wrapper="Request rate, bandwidth, or object size exceeded the limits for this account. To resolve this error, reduce your usage as appropriate or contact us at support@momentohq.com to request a limit increase", # noqa: E501
message_wrapper=determineLimitExceededMessageWrapper(transport_details), # noqa: E501
)


class LimitExceededMessageWrapper(Enum):
TOPIC_SUBSCRIPTIONS_LIMIT_EXCEEDED = "Topic subscriptions limit exceeded for this account"
OPERATIONS_RATE_LIMIT_EXCEEDED = "Request rate limit exceeded for this account"
THROUGHPUT_LIMIT_EXCEEDED = "Bandwidth limit exceeded for this account"
REQUEST_SIZE_LIMIT_EXCEEDED = "Request size limit exceeded for this account"
ITEM_SIZE_LIMIT_EXCEEDED = "Item size limit exceeded for this account"
ELEMENTS_SIZE_LIMIT_EXCEEDED = "Element size limit exceeded for this account"
UNKNOWN_LIMIT_EXCEEDED = "Limit exceeded for this account"


LIMIT_EXCEEDED_ERROR_TO_MESSAGE_WRAPPER = {
"topic_subscriptions_limit_exceeded": LimitExceededMessageWrapper.TOPIC_SUBSCRIPTIONS_LIMIT_EXCEEDED.value,
"operations_rate_limit_exceeded": LimitExceededMessageWrapper.OPERATIONS_RATE_LIMIT_EXCEEDED.value,
"throughput_rate_limit_exceeded": LimitExceededMessageWrapper.THROUGHPUT_LIMIT_EXCEEDED.value,
"request_size_limit_exceeded": LimitExceededMessageWrapper.REQUEST_SIZE_LIMIT_EXCEEDED.value,
"item_size_limit_exceeded": LimitExceededMessageWrapper.ITEM_SIZE_LIMIT_EXCEEDED.value,
"element_size_limit_exceeded": LimitExceededMessageWrapper.ELEMENTS_SIZE_LIMIT_EXCEEDED.value,
}


def determineLimitExceededMessageWrapper(transport_details: Optional[MomentoErrorTransportDetails] = None) -> str:
# If provided, use the `err` metadata to determine the specific message wrapper to return.
if transport_details is not None and transport_details.grpc.metadata is not None: # type: ignore[misc]
err_cause: Optional[str] = transport_details.grpc.metadata.get("err") # type: ignore[misc]
if err_cause is not None and err_cause in LIMIT_EXCEEDED_ERROR_TO_MESSAGE_WRAPPER:
return LIMIT_EXCEEDED_ERROR_TO_MESSAGE_WRAPPER[err_cause]

# If `err` metadata is unavailable, try to use the error details field to return
# an appropriate message wrapper.
if transport_details is not None and transport_details.grpc.details is not None:
lower_cased_message = transport_details.grpc.details.lower()
if "subscribe" in lower_cased_message:
return LimitExceededMessageWrapper.TOPIC_SUBSCRIPTIONS_LIMIT_EXCEEDED.value
elif "operations" in lower_cased_message:
return LimitExceededMessageWrapper.OPERATIONS_RATE_LIMIT_EXCEEDED.value
elif "throughput" in lower_cased_message:
return LimitExceededMessageWrapper.THROUGHPUT_LIMIT_EXCEEDED.value
elif "request limit" in lower_cased_message:
return LimitExceededMessageWrapper.REQUEST_SIZE_LIMIT_EXCEEDED.value
elif "item size" in lower_cased_message:
return LimitExceededMessageWrapper.ITEM_SIZE_LIMIT_EXCEEDED.value
elif "element size" in lower_cased_message:
return LimitExceededMessageWrapper.ELEMENTS_SIZE_LIMIT_EXCEEDED.value

return LimitExceededMessageWrapper.UNKNOWN_LIMIT_EXCEEDED.value


class NotFoundException(SdkException):
"""Requested resource or the resource on which an operation was requested doesn't exist."""

Expand Down
Loading