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

1"""Exceptions for the Durable Executions Testing Library. 

2 

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. 

6 

7## AWS-Compliant Error Format 

8 

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: 

12 

13### Standard Format (most exceptions): 

14```json 

15{ 

16 "Type": "ExceptionName", 

17 "message": "Error message" // or "Message" depending on Smithy definition 

18} 

19``` 

20 

21### Special Cases: 

22- `ExecutionAlreadyStartedException`: No "Type" field, includes "DurableExecutionArn" 

23```json 

24{ 

25 "message": "Error message", 

26 "DurableExecutionArn": "arn:aws:states:..." 

27} 

28``` 

29 

30## Field Name Conventions 

31 

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" 

38 

39## HTTP Status Codes 

40 

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) 

47 

48## Usage Examples 

49 

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"} 

55 

56# HTTP response creation 

57from .web.models import HTTPResponse 

58 

59response = HTTPResponse.create_error_from_exception(exception) 

60# Creates HTTP 400 response with AWS-compliant JSON body 

61``` 

62 

63## Boto3 Compatibility 

64 

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 

70 

71Avoid any non-stdlib references in this module, it is at the bottom of the dependency chain. 

72""" 

73 

74from __future__ import annotations 

75 

76from typing import Any 

77 

78 

79class DurableFunctionsLocalRunnerError(Exception): 

80 """Base class for Durable Executions exceptions""" 

81 

82 

83class UnknownRouteError(DurableFunctionsLocalRunnerError): 

84 """No route matches the requested path pattern.""" 

85 

86 def __init__(self, method: str, path: str) -> None: 

87 """Initialize UnknownRouteError with method and path. 

88 

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) 

97 

98 

99class SerializationError(DurableFunctionsLocalRunnerError): 

100 """Exception for serialization errors.""" 

101 

102 

103class DurableFunctionsTestError(Exception): 

104 """Base class for testing errors.""" 

105 

106 

107class AwsApiException(DurableFunctionsLocalRunnerError): # noqa: N818 

108 """Base class for AWS API-style exceptions that can be serialized to AWS format.""" 

109 

110 http_status_code: int = 500 # Default to server error 

111 

112 def to_dict(self) -> dict[str, Any]: 

113 """Serialize to AWS-compliant JSON structure.""" 

114 raise NotImplementedError 

115 

116 

117# Smithy-Mapped Exceptions (defined in Smithy models) 

118class InvalidParameterValueException(AwsApiException): 

119 """Exception for invalid parameter values.""" 

120 

121 http_status_code = 400 

122 

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) 

127 

128 def to_dict(self) -> dict[str, Any]: 

129 """Serialize to AWS-compliant JSON structure.""" 

130 return {"Type": "InvalidParameterValueException", "message": self.message} 

131 

132 

133class ResourceNotFoundException(AwsApiException): 

134 """Exception for resource not found errors.""" 

135 

136 http_status_code = 404 

137 

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) 

145 

146 def to_dict(self) -> dict[str, Any]: 

147 """Serialize to AWS-compliant JSON structure.""" 

148 return {"Type": "ResourceNotFoundException", "Message": self.Message} 

149 

150 

151class ServiceException(AwsApiException): 

152 """Exception for general service errors.""" 

153 

154 http_status_code = 500 

155 

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) 

163 

164 def to_dict(self) -> dict[str, Any]: 

165 """Serialize to AWS-compliant JSON structure.""" 

166 return {"Type": "ServiceException", "Message": self.Message} 

167 

168 

169class ExecutionAlreadyStartedException(AwsApiException): 

170 """Exception for execution already started errors.""" 

171 

172 http_status_code = 409 

173 

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) 

179 

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 } 

186 

187 

188class ExecutionConflictException(AwsApiException): 

189 """Exception for execution conflict errors.""" 

190 

191 http_status_code = 409 

192 

193 def __init__(self, message: str) -> None: 

194 """Initialize with message field.""" 

195 self.message = message 

196 super().__init__(message) 

197 

198 def to_dict(self) -> dict[str, Any]: 

199 """Serialize to AWS-compliant JSON structure.""" 

200 return {"Type": "ExecutionConflictException", "message": self.message} 

201 

202 

203class CallbackTimeoutException(AwsApiException): 

204 """Exception for callback timeout errors.""" 

205 

206 http_status_code = 408 

207 

208 def __init__(self, message: str) -> None: 

209 """Initialize with message field (lowercase per Smithy definition).""" 

210 self.message = message 

211 super().__init__(message) 

212 

213 def to_dict(self) -> dict[str, Any]: 

214 """Serialize to AWS-compliant JSON structure.""" 

215 return {"Type": "CallbackTimeoutException", "message": self.message} 

216 

217 

218class TooManyRequestsException(AwsApiException): 

219 """Exception for too many requests errors.""" 

220 

221 http_status_code = 429 

222 

223 def __init__(self, message: str) -> None: 

224 """Initialize with message field (lowercase per Smithy definition).""" 

225 self.message = message 

226 super().__init__(message) 

227 

228 def to_dict(self) -> dict[str, Any]: 

229 """Serialize to AWS-compliant JSON structure.""" 

230 return {"Type": "TooManyRequestsException", "message": self.message} 

231 

232 

233# Unmapped Exceptions (thrown by services but not in Smithy) 

234class IllegalStateException(AwsApiException): 

235 """IllegalStateException.""" 

236 

237 http_status_code = 500 

238 

239 def __init__(self, message: str) -> None: 

240 """Initialize with message field.""" 

241 self.message = message 

242 super().__init__(message) 

243 

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} 

247 

248 

249class RuntimeException(AwsApiException): 

250 """RuntimeException.""" 

251 

252 http_status_code = 500 

253 

254 def __init__(self, message: str) -> None: 

255 """Initialize with message field.""" 

256 self.message = message 

257 super().__init__(message) 

258 

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} 

262 

263 

264class IllegalArgumentException(AwsApiException): 

265 """IllegalArgumentException.""" 

266 

267 http_status_code = 400 

268 

269 def __init__(self, message: str) -> None: 

270 """Initialize with message field.""" 

271 self.message = message 

272 super().__init__(message) 

273 

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}