From 49de67654946d978de73148d3a2af940ee03aa48 Mon Sep 17 00:00:00 2001 From: Samuel Tinnerholm Date: Sat, 11 Jul 2026 06:49:22 +0000 Subject: [PATCH] fix(python-sdk): align typed error metadata --- sdks/python/pmxt/errors.py | 32 +++++++++++++++++++------ sdks/python/tests/test_errors.py | 40 ++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/sdks/python/pmxt/errors.py b/sdks/python/pmxt/errors.py index 4219aa64..b09d0cb2 100644 --- a/sdks/python/pmxt/errors.py +++ b/sdks/python/pmxt/errors.py @@ -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 @@ -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): @@ -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 @@ -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( diff --git a/sdks/python/tests/test_errors.py b/sdks/python/tests/test_errors.py index 19974a77..1f39386f 100644 --- a/sdks/python/tests/test_errors.py +++ b/sdks/python/tests/test_errors.py @@ -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, ) @@ -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")