11"""Postmark API exceptions."""
22
3- from typing import Any , Dict , Optional
3+ from typing import Any , Optional
44
55
66class PostmarkException (Exception ):
@@ -15,11 +15,35 @@ def __init__(
1515 super ().__init__ (message )
1616 self .error_code = error_code
1717 self .http_status = http_status
18- self .message = message
18+
19+ @property
20+ def message (self ) -> str :
21+ """The error message. Alias for self.args[0]."""
22+ return self .args [0 ]
23+
24+
25+ class InvalidEmailPayloadException (PostmarkException ):
26+ """
27+ Raised when an email dict or model fails Pydantic validation
28+ before it is ever sent to the API.
29+
30+ Distinct from ValidationException, which maps to HTTP 422 responses
31+ from the Postmark API after a request was made.
32+ """
33+
34+ def __init__ (self , errors : list [dict [str , Any ]]):
35+ self .errors = errors
36+ summary = " | " .join (
37+ f"{ ' -> ' .join (str (loc ) for loc in e ['loc' ])} : { e ['msg' ]} " for e in errors
38+ )
39+ super ().__init__ (f"Invalid email payload: { summary } " )
1940
2041
2142class PostmarkAPIException (PostmarkException ):
22- """API errors with error codes from Postmark."""
43+ """
44+ Raised for errors returned by the Postmark API.
45+ Always carries both a Postmark error_code and an http_status.
46+ """
2347
2448 def __init__ (self , message : str , error_code : int , http_status : int ):
2549 super ().__init__ (message , error_code , http_status )
@@ -28,7 +52,6 @@ def __str__(self):
2852 return f"[{ self .error_code } ] { self .message } (HTTP { self .http_status } )"
2953
3054
31- # Specific exception types for common errors
3255class InvalidAPIKeyException (PostmarkAPIException ):
3356 """401 - Invalid or missing API key."""
3457
@@ -42,7 +65,7 @@ class InactiveRecipientException(PostmarkAPIException):
4265
4366
4467class ValidationException (PostmarkAPIException ):
45- """422 - Invalid request parameters."""
68+ """422 - Invalid request parameters (rejected by Postmark API) ."""
4669
4770 pass
4871
@@ -54,46 +77,47 @@ class RateLimitException(PostmarkAPIException):
5477
5578
5679class ServerException (PostmarkAPIException ):
57- """500/ 503 - Server errors ."""
80+ """500 / 503 - Server-side error ."""
5881
5982 pass
6083
6184
6285class TimeoutException (PostmarkException ):
63- """Request timeout ."""
86+ """Raised when a request to the Postmark API times out ."""
6487
6588 pass
6689
6790
68- # Error code mapping
69- ERROR_CODE_MAPPING = {
91+ # Postmark API error codes (from response body), not HTTP status codes.
92+ # See: https://postmarkapp.com/developer/api/overview#error-codes
93+ _ERROR_CODE_MAP : dict [int , type [PostmarkAPIException ]] = {
7094 10 : InvalidAPIKeyException ,
71- 406 : InactiveRecipientException ,
7295 300 : ValidationException ,
7396 405 : ValidationException , # Not allowed to send
97+ 406 : InactiveRecipientException ,
7498 701 : ValidationException , # Illegal attachment type
75- 429 : RateLimitException ,
76- 500 : ServerException ,
77- 503 : ServerException ,
7899}
79100
80101
81102def get_exception_class (
82103 error_code : int , http_status : int
83104) -> type [PostmarkAPIException ]:
84- """Get the appropriate exception class for an error code."""
85- # Check specific error codes first
86- if error_code in ERROR_CODE_MAPPING :
87- return ERROR_CODE_MAPPING [error_code ]
105+ """
106+ Resolve the most specific exception class for a failed API response.
107+
108+ Checks Postmark error codes first (more specific), then falls back
109+ to HTTP status codes, and finally to the generic PostmarkAPIException.
110+ """
111+ if error_code in _ERROR_CODE_MAP :
112+ return _ERROR_CODE_MAP [error_code ]
88113
89- # Fall back to HTTP status
90114 if http_status == 401 :
91115 return InvalidAPIKeyException
92- elif http_status == 422 :
116+ if http_status == 422 :
93117 return ValidationException
94- elif http_status == 429 :
118+ if http_status == 429 :
95119 return RateLimitException
96- elif http_status in (500 , 503 ):
120+ if http_status in (500 , 503 ):
97121 return ServerException
98122
99123 return PostmarkAPIException
0 commit comments