Skip to content
Open
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
32 changes: 25 additions & 7 deletions sdks/python/pmxt/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class PmxtError(Exception):

def __init__(self, message: str, code: str = "UNKNOWN_ERROR", retryable: bool = False, exchange: str | None = None) -> None:
super().__init__(message)
self.name = type(self).__name__
self.message = message
self.code = code
self.retryable = retryable
Expand All @@ -35,22 +36,30 @@ def _format_not_found_message(prefix: str, identifier: str) -> str:

class BadRequest(PmxtError):
"""400 Bad Request - The request was malformed or contains invalid parameters."""
pass
def __init__(self, message: str, **kwargs: Any) -> None:
kwargs.setdefault("code", "BAD_REQUEST")
super().__init__(message, **kwargs)


class AuthenticationError(PmxtError):
"""401 Unauthorized - Authentication credentials are missing or invalid."""
pass
def __init__(self, message: str, **kwargs: Any) -> None:
kwargs.setdefault("code", "AUTHENTICATION_ERROR")
super().__init__(message, **kwargs)


class PermissionDenied(PmxtError):
"""403 Forbidden - The authenticated user doesn't have permission."""
pass
def __init__(self, message: str, **kwargs: Any) -> None:
kwargs.setdefault("code", "PERMISSION_DENIED")
super().__init__(message, **kwargs)


class NotFoundError(PmxtError):
"""404 Not Found - The requested resource doesn't exist."""
pass
def __init__(self, message: str, **kwargs: Any) -> None:
kwargs.setdefault("code", "NOT_FOUND")
super().__init__(message, **kwargs)


class OrderNotFound(NotFoundError):
Expand Down Expand Up @@ -87,24 +96,31 @@ class RateLimitExceeded(PmxtError):
"""429 Too Many Requests - Rate limit exceeded."""

def __init__(self, message: str, retry_after: float | None = None, **kwargs) -> None:
kwargs.setdefault("code", "RATE_LIMIT_EXCEEDED")
kwargs.setdefault("retryable", True)
super().__init__(message, **kwargs)
self.retry_after = retry_after


class InvalidOrder(PmxtError):
"""400 Bad Request - The order parameters are invalid."""
pass
def __init__(self, message: str, **kwargs: Any) -> None:
kwargs.setdefault("code", "INVALID_ORDER")
super().__init__(message, **kwargs)


class InsufficientFunds(PmxtError):
"""400 Bad Request - Insufficient funds to complete the operation."""
pass
def __init__(self, message: str, **kwargs: Any) -> None:
kwargs.setdefault("code", "INSUFFICIENT_FUNDS")
super().__init__(message, **kwargs)


class ValidationError(PmxtError):
"""400 Bad Request - Input validation failed."""

def __init__(self, message: str, field: str | None = None, **kwargs) -> None:
kwargs.setdefault("code", "VALIDATION_ERROR")
super().__init__(message, **kwargs)
self.field = field

Expand Down Expand Up @@ -171,7 +187,9 @@ def from_server_error(error_data: Dict[str, Any]) -> PmxtError:

class NotSupported(PmxtError):
"""The requested operation is not supported."""
pass
def __init__(self, message: str, **kwargs: Any) -> None:
kwargs.setdefault("code", "NOT_SUPPORTED")
super().__init__(message, **kwargs)


_HOSTED_ERROR_EXPORTS = frozenset(
Expand Down
40 changes: 40 additions & 0 deletions sdks/python/tests/test_errors.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import pytest

from pmxt.errors import (
AuthenticationError,
BadRequest,
EventNotFound,
ExchangeNotAvailable,
InsufficientFunds,
InvalidOrder,
MarketNotFound,
NetworkError,
NotFoundError,
NotSupported,
OrderNotFound,
PermissionDenied,
PmxtError,
RateLimitExceeded,
ValidationError,
)


Expand All @@ -22,3 +33,32 @@ def test_retryable_errors_are_marked_retryable():
def test_rate_limit_retry_after_accepts_float_values():
err = RateLimitExceeded("slow down", retry_after=1.5)
assert err.retry_after == 1.5


@pytest.mark.parametrize(
("error_type", "expected_code"),
[
(BadRequest, "BAD_REQUEST"),
(AuthenticationError, "AUTHENTICATION_ERROR"),
(PermissionDenied, "PERMISSION_DENIED"),
(NotFoundError, "NOT_FOUND"),
(RateLimitExceeded, "RATE_LIMIT_EXCEEDED"),
(InvalidOrder, "INVALID_ORDER"),
(InsufficientFunds, "INSUFFICIENT_FUNDS"),
(ValidationError, "VALIDATION_ERROR"),
(NotSupported, "NOT_SUPPORTED"),
],
)
def test_directly_raised_errors_use_specific_default_codes(error_type, expected_code):
assert error_type("test").code == expected_code


@pytest.mark.parametrize("error_type", [PmxtError, BadRequest, OrderNotFound])
def test_errors_expose_their_concrete_class_name(error_type):
err = error_type("test")
assert err.name == error_type.__name__


def test_explicit_server_error_metadata_overrides_defaults():
err = ValidationError("test", code="CUSTOM", retryable=True, exchange="venue")
assert (err.code, err.retryable, err.exchange) == ("CUSTOM", True, "venue")
Loading