Coverage for async-durable-execution/src/async_durable_execution/runner/exceptions.py: 100%
86 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 16:54 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 16:54 +0000
1"""Exceptions for the Durable Executions Testing Library.
3This module provides AWS-compliant exceptions that serialize to the exact JSON format
4expected by boto3 and AWS services. All exceptions follow Smithy model definitions
5for field names and structure.
7## AWS-Compliant Error Format
9All AWS API exceptions inherit from `AwsApiException` and implement the `to_dict()` method
10to serialize to AWS-compliant JSON format. The format varies by exception type based on
11their Smithy model definitions:
13### Standard Format (most exceptions):
14```json
15{
16 "Type": "ExceptionName",
17 "message": "Error message" // or "Message" depending on Smithy definition
18}
19```
21### Special Cases:
22- `ExecutionAlreadyStartedException`: No "Type" field, includes "DurableExecutionArn"
23```json
24{
25 "message": "Error message",
26 "DurableExecutionArn": "arn:aws:states:..."
27}
28```
30## Field Name Conventions
32Field names follow the exact Smithy model definitions:
33- `InvalidParameterValueException`: uses lowercase "message"
34- `CallbackTimeoutException`: uses lowercase "message"
35- `ResourceNotFoundException`: uses capital "Message"
36- `ServiceException`: uses capital "Message"
37- `ExecutionAlreadyStartedException`: uses lowercase "message" + "DurableExecutionArn"
39## HTTP Status Codes
41Each exception maps to appropriate HTTP status codes:
42- 400: InvalidParameterValueException (Bad Request)
43- 404: ResourceNotFoundException (Not Found)
44- 408: CallbackTimeoutException (Request Timeout)
45- 409: ExecutionAlreadyStartedException (Conflict)
46- 500: ServiceException (Internal Server Error)
48## Usage Examples
50```python
51# Create and serialize an exception
52exception = InvalidParameterValueException("Invalid parameter value")
53json_dict = exception.to_dict()
54# Result: {"Type": "InvalidParameterValueException", "message": "Invalid parameter value"}
56# HTTP response creation
57from .web.models import HTTPResponse
59response = HTTPResponse.create_error_from_exception(exception)
60# Creates HTTP 400 response with AWS-compliant JSON body
61```
63## Boto3 Compatibility
65All exceptions are designed to be compatible with boto3's error handling:
66- JSON structure matches boto3 expectations
67- Field names match Smithy model definitions
68- Type field values match exception class names
69- Can be deserialized by boto3's error factory
71Avoid any non-stdlib references in this module, it is at the bottom of the dependency chain.
72"""
74from __future__ import annotations
76from typing import Any
79class DurableFunctionsLocalRunnerError(Exception):
80 """Base class for Durable Executions exceptions"""
83class UnknownRouteError(DurableFunctionsLocalRunnerError):
84 """No route matches the requested path pattern."""
86 def __init__(self, method: str, path: str) -> None:
87 """Initialize UnknownRouteError with method and path.
89 Args:
90 method: HTTP method (GET, POST, etc.)
91 path: Request path that couldn't be matched
92 """
93 self.method = method
94 self.path = path
95 message = f"Unknown path pattern: {method} {path}"
96 super().__init__(message)
99class SerializationError(DurableFunctionsLocalRunnerError):
100 """Exception for serialization errors."""
103class DurableFunctionsTestError(Exception):
104 """Base class for testing errors."""
107class AwsApiException(DurableFunctionsLocalRunnerError): # noqa: N818
108 """Base class for AWS API-style exceptions that can be serialized to AWS format."""
110 http_status_code: int = 500 # Default to server error
112 def to_dict(self) -> dict[str, Any]:
113 """Serialize to AWS-compliant JSON structure."""
114 raise NotImplementedError
117# Smithy-Mapped Exceptions (defined in Smithy models)
118class InvalidParameterValueException(AwsApiException):
119 """Exception for invalid parameter values."""
121 http_status_code = 400
123 def __init__(self, message: str | None) -> None:
124 """Initialize with message field (lowercase per Smithy definition)."""
125 self.message = message
126 super().__init__(message)
128 def to_dict(self) -> dict[str, Any]:
129 """Serialize to AWS-compliant JSON structure."""
130 return {"Type": "InvalidParameterValueException", "message": self.message}
133class ResourceNotFoundException(AwsApiException):
134 """Exception for resource not found errors."""
136 http_status_code = 404
138 def __init__(
139 self,
140 Message: str, # noqa: N803
141 ) -> None: # Capital M per Smithy definition
142 """Initialize with Message field (capital M per Smithy definition)."""
143 self.Message = Message
144 super().__init__(Message)
146 def to_dict(self) -> dict[str, Any]:
147 """Serialize to AWS-compliant JSON structure."""
148 return {"Type": "ResourceNotFoundException", "Message": self.Message}
151class ServiceException(AwsApiException):
152 """Exception for general service errors."""
154 http_status_code = 500
156 def __init__(
157 self,
158 Message: str, # noqa: N803
159 ) -> None: # Capital M per Smithy definition
160 """Initialize with Message field (capital M per Smithy definition)."""
161 self.Message = Message
162 super().__init__(Message)
164 def to_dict(self) -> dict[str, Any]:
165 """Serialize to AWS-compliant JSON structure."""
166 return {"Type": "ServiceException", "Message": self.Message}
169class ExecutionAlreadyStartedException(AwsApiException):
170 """Exception for execution already started errors."""
172 http_status_code = 409
174 def __init__(self, message: str, DurableExecutionArn: str) -> None: # noqa: N803
175 """Initialize with message and DurableExecutionArn fields."""
176 self.message = message
177 self.DurableExecutionArn = DurableExecutionArn
178 super().__init__(message)
180 def to_dict(self) -> dict[str, Any]:
181 """Serialize to AWS-compliant JSON structure (no Type field per Smithy definition)."""
182 return {
183 "message": self.message,
184 "DurableExecutionArn": self.DurableExecutionArn,
185 }
188class ExecutionConflictException(AwsApiException):
189 """Exception for execution conflict errors."""
191 http_status_code = 409
193 def __init__(self, message: str) -> None:
194 """Initialize with message field."""
195 self.message = message
196 super().__init__(message)
198 def to_dict(self) -> dict[str, Any]:
199 """Serialize to AWS-compliant JSON structure."""
200 return {"Type": "ExecutionConflictException", "message": self.message}
203class CallbackTimeoutException(AwsApiException):
204 """Exception for callback timeout errors."""
206 http_status_code = 408
208 def __init__(self, message: str) -> None:
209 """Initialize with message field (lowercase per Smithy definition)."""
210 self.message = message
211 super().__init__(message)
213 def to_dict(self) -> dict[str, Any]:
214 """Serialize to AWS-compliant JSON structure."""
215 return {"Type": "CallbackTimeoutException", "message": self.message}
218class TooManyRequestsException(AwsApiException):
219 """Exception for too many requests errors."""
221 http_status_code = 429
223 def __init__(self, message: str) -> None:
224 """Initialize with message field (lowercase per Smithy definition)."""
225 self.message = message
226 super().__init__(message)
228 def to_dict(self) -> dict[str, Any]:
229 """Serialize to AWS-compliant JSON structure."""
230 return {"Type": "TooManyRequestsException", "message": self.message}
233# Unmapped Exceptions (thrown by services but not in Smithy)
234class IllegalStateException(AwsApiException):
235 """IllegalStateException."""
237 http_status_code = 500
239 def __init__(self, message: str) -> None:
240 """Initialize with message field."""
241 self.message = message
242 super().__init__(message)
244 def to_dict(self) -> dict[str, Any]:
245 """Serialize to AWS-compliant JSON structure (maps to ServiceException)."""
246 return {"Type": "ServiceException", "Message": self.message}
249class RuntimeException(AwsApiException):
250 """RuntimeException."""
252 http_status_code = 500
254 def __init__(self, message: str) -> None:
255 """Initialize with message field."""
256 self.message = message
257 super().__init__(message)
259 def to_dict(self) -> dict[str, Any]:
260 """Serialize to AWS-compliant JSON structure (maps to ServiceException)."""
261 return {"Type": "ServiceException", "Message": self.message}
264class IllegalArgumentException(AwsApiException):
265 """IllegalArgumentException."""
267 http_status_code = 400
269 def __init__(self, message: str) -> None:
270 """Initialize with message field."""
271 self.message = message
272 super().__init__(message)
274 def to_dict(self) -> dict[str, Any]:
275 """Serialize to AWS-compliant JSON structure (maps to InvalidParameterValueException)."""
276 return {"Type": "InvalidParameterValueException", "message": self.message}