Skip to content

Commit 8024fec

Browse files
committed
feat: generalize error_code
* error_code in ResponseError * raise ... from * modernize typing Signed-off-by: Yves Bastide <yves@botify.com>
1 parent eec242a commit 8024fec

File tree

9 files changed

+71
-62
lines changed

9 files changed

+71
-62
lines changed

simpleflow/swf/mapper/actors/decider.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ def complete(
6161
raise DoesNotExistError(
6262
f"Unable to complete decision task with token={task_token}",
6363
message,
64-
)
65-
raise ResponseError(message)
64+
) from e
65+
raise ResponseError(message, error_code=error_code) from e
6666
finally:
6767
logging_context.reset()
6868

@@ -117,9 +117,9 @@ def poll(self, task_list: str | None = None, identity: str | None = None, **kwar
117117
raise DoesNotExistError(
118118
"Unable to poll decision task",
119119
message,
120-
)
120+
) from e
121121

122-
raise ResponseError(message)
122+
raise ResponseError(message, error_code=error_code) from e
123123

124124
token = task.get("taskToken")
125125
if not token:

simpleflow/swf/mapper/actors/worker.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ def cancel(self, task_token: str, details: str | None = None) -> dict[str, Any]
6464
raise DoesNotExistError(
6565
f"Unable to cancel activity task with token={task_token}",
6666
message,
67-
)
68-
raise ResponseError(message)
67+
) from e
68+
raise ResponseError(message, error_code=error_code) from e
6969
finally:
7070
logging_context.reset()
7171

@@ -87,9 +87,9 @@ def complete(self, task_token: str, result: Any = None) -> dict[str, Any] | None
8787
raise DoesNotExistError(
8888
f"Unable to complete activity task with token={task_token}",
8989
message,
90-
)
90+
) from e
9191

92-
raise ResponseError(message)
92+
raise ResponseError(message, error_code=error_code) from e
9393
except JumboTooLargeError as e:
9494
return self.respond_activity_task_failed(task_token, reason=format_exc(e))
9595

@@ -113,9 +113,9 @@ def fail(self, task_token: str, details: str | None = None, reason: str | None =
113113
raise DoesNotExistError(
114114
f"Unable to fail activity task with token={task_token}",
115115
message,
116-
)
116+
) from e
117117

118-
raise ResponseError(message)
118+
raise ResponseError(message, error_code=error_code) from e
119119
except JumboTooLargeError as e:
120120
return self.respond_activity_task_failed(task_token, reason=format_exc(e))
121121

@@ -137,15 +137,15 @@ def heartbeat(self, task_token: str, details: str | None = None) -> dict[str, An
137137
raise DoesNotExistError(
138138
f"Unable to send heartbeat with token={task_token}",
139139
message,
140-
)
140+
) from e
141141

142142
if error_code == "ThrottlingException":
143143
raise RateLimitExceededError(
144144
f"Rate exceeded when sending heartbeat with token={task_token}",
145145
message,
146-
)
146+
) from e
147147

148-
raise ResponseError(message)
148+
raise ResponseError(message, error_code=error_code) from e
149149

150150
def poll(self, task_list: str | None = None, identity: str | None = None) -> Response:
151151
"""Polls for an activity task to process from current
@@ -184,9 +184,9 @@ def poll(self, task_list: str | None = None, identity: str | None = None) -> Res
184184
raise DoesNotExistError(
185185
"Unable to poll activity task",
186186
message,
187-
)
187+
) from e
188188

189-
raise ResponseError(message)
189+
raise ResponseError(message, error_code=error_code) from e
190190

191191
if not task.get("taskToken"):
192192
raise PollTimeout("Activity Worker poll timed out")

simpleflow/swf/mapper/exceptions.py

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import re
44
from collections.abc import Sequence
55
from functools import partial, wraps
6-
from typing import Any, Callable
6+
from typing import Any, Callable, Pattern
77

88
from botocore.exceptions import ClientError
99

@@ -46,9 +46,16 @@ def __init__(self, message: str = "", raw_error: str = "", error_code: str = "",
4646
'kind'
4747
>>> error.details
4848
'details'
49+
>>> error = SWFError('message', error_code='FooFault')
50+
>>> error.message
51+
'message'
52+
>>> error.error_code
53+
'FooFault'
54+
>>> error.details
55+
''
4956
5057
"""
51-
Exception.__init__(self, message, *args)
58+
super().__init__(message, *args)
5259

5360
values = raw_error.split(":", 1)
5461

@@ -118,18 +125,15 @@ def ignore(*args, **kwargs):
118125
REGEX_NESTED_RESOURCE = re.compile(r"Unknown (?:type|execution)[:,]\s*([^ =]+)\s*=")
119126

120127

121-
def match_equals(regex, string, values):
128+
def match_equals(regex: Pattern, string: str | None, values: str | Sequence[str]) -> bool:
122129
"""
123130
Extract a value from a string with a regex and compare it.
124131
125132
:param regex: to extract the value to check.
126-
:type regex: _sre.SRE_Pattern (compiled regex)
127133
128134
:param string: that contains the value to extract.
129-
:type string: str
130135
131136
:param values: to compare with.
132-
:type values: [str]
133137
134138
"""
135139
if string is None:
@@ -144,12 +148,11 @@ def match_equals(regex, string, values):
144148
return matched[0] in values
145149

146150

147-
def is_unknown_resource_raised(error, *args, **kwargs):
151+
def is_unknown_resource_raised(error: Exception, *args, **kwargs) -> bool:
148152
"""
149153
Handler that checks if *error* is an unknown resource fault.
150154
151155
:param error: is the exception to check.
152-
:type error: Exception
153156
154157
"""
155158
if not isinstance(error, ClientError):
@@ -158,7 +161,7 @@ def is_unknown_resource_raised(error, *args, **kwargs):
158161
return extract_error_code(error) == "UnknownResourceFault"
159162

160163

161-
def is_unknown(resource: str | Sequence[str]):
164+
def is_unknown(resource: str | Sequence[str]) -> Callable:
162165
"""
163166
Return a function that checks if *error* is an unknown *resource* fault.
164167
@@ -190,7 +193,7 @@ def wrapped(error, *args, **kwargs):
190193
return wrapped
191194

192195

193-
def always(value):
196+
def always(value: Any) -> Callable:
194197
"""
195198
Always return *value* whatever arguments it got.
196199
@@ -217,7 +220,7 @@ def wrapped(*args, **kwargs):
217220
return wrapped
218221

219222

220-
def generate_resource_not_found_message(error):
223+
def generate_resource_not_found_message(error: Exception) -> str:
221224
error_code = extract_error_code(error)
222225
if error_code != "UnknownResourceFault":
223226
raise ValueError(f"cannot extract resource from {error}")
@@ -227,37 +230,43 @@ def generate_resource_not_found_message(error):
227230
return f"Resource {resource[0] if resource else 'unknown'} does not exist"
228231

229232

230-
def raises(exception, when, extract: Callable[[Any], str] = str):
233+
def raises(
234+
exception: type[Exception] | type[SWFError],
235+
when: Callable[[Exception, tuple, dict], bool],
236+
extract: Callable[[Any], str] = str,
237+
):
231238
"""
232239
:param exception: to raise when the predicate is True.
233-
:type exception: type(Exception)
234-
235240
:param when: predicate to apply.
236-
:type when: (error, *args, **kwargs) -> bool
241+
:param extract: function to extract the value from the exception.
237242
"""
238243

239244
@wraps(raises)
240245
def raises_closure(error, *args, **kwargs):
241246
if when(error, *args, **kwargs) is True:
242-
raise exception(extract(error)) from None
243-
raise error
247+
if isinstance(getattr(error, "response", None), dict) and issubclass(exception, SWFError):
248+
raise exception(extract_message(error), error_code=extract_error_code(error)) from error
249+
250+
raise exception(extract(error)) from error
251+
raise error from None
244252

245253
return raises_closure
246254

247255

248-
def catch(exceptions, handle_with=None, log=False):
256+
def catch(
257+
exceptions: type[Exception] | Sequence[type[Exception]] | tuple[type[Exception]],
258+
handle_with: Callable[[Exception, tuple, dict], Any] | None = None,
259+
log: bool = False,
260+
):
249261
"""
250262
Catch *exceptions*, then eventually handle and log them.
251263
252264
:param exceptions: sequence of exceptions to catch.
253-
:type exceptions: Exception | (Exception, )
254265
255266
:param handle_with: handle the exceptions (if handle_with is not None) or
256267
raise them again.
257-
:type handle_with: function(err, *args, **kwargs)
258268
259269
:param log: the exception with default logger.
260-
:type log: bool
261270
262271
Examples:
263272
@@ -312,10 +321,10 @@ def translate(exceptions, to):
312321
313322
"""
314323

315-
def throw(err, *args, **kwargs):
324+
def throw(err: Exception, *args, **kwargs):
316325
if isinstance(getattr(err, "response", None), dict) and issubclass(to, SWFError):
317-
raise to(extract_message(err), error_code=extract_error_code(err)) from None
318-
raise to(extract_message(err))
326+
raise to(extract_message(err), error_code=extract_error_code(err)) from err
327+
raise to(extract_message(err)) from err
319328

320329
return catch(exceptions, handle_with=throw)
321330

simpleflow/swf/mapper/models/activity.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,9 @@ def _diff(self, ignore_fields: list[str] | None = None) -> ModelDiff:
121121
error_code = extract_error_code(e)
122122
message = extract_message(e)
123123
if error_code == "UnknownResourceFault":
124-
raise DoesNotExistError("Remote ActivityType does not exist")
124+
raise DoesNotExistError("Remote ActivityType does not exist") from e
125125

126-
raise ResponseError(message)
126+
raise ResponseError(message, error_code=error_code) from e
127127

128128
info = description["typeInfo"]
129129
config = description["configuration"]
@@ -192,9 +192,9 @@ def save(self):
192192
error_code = extract_error_code(e)
193193
message = extract_message(e)
194194
if error_code == "TypeAlreadyExistsFault":
195-
raise AlreadyExistsError(f"{self} already exists")
195+
raise AlreadyExistsError(f"{self} already exists") from e
196196
if error_code in ("UnknownResourceFault", "TypeDeprecatedFault"):
197-
raise DoesNotExistError(f"{error_code}: {message}")
197+
raise DoesNotExistError(f"{error_code}: {message}") from e
198198
raise
199199

200200
@exceptions.catch(

simpleflow/swf/mapper/models/domain.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ def _diff(self, ignore_fields: list[str] | None = None) -> ModelDiff:
8686
except ClientError as e:
8787
error_code = e.response["Error"]["Code"]
8888
if error_code == "UnknownResourceFault":
89-
raise DoesNotExistError("Remote Domain does not exist")
89+
raise DoesNotExistError("Remote Domain does not exist") from e
9090

91-
raise ResponseError(e.args[0])
91+
raise ResponseError(e.args[0], error_code=error_code) from e
9292

9393
domain_info = description["domainInfo"]
9494
domain_config = description["configuration"]
@@ -137,8 +137,8 @@ def save(self) -> None:
137137
error_code = extract_error_code(e)
138138
message = extract_message(e)
139139
if error_code == "DomainAlreadyExistsFault":
140-
raise AlreadyExistsError(f"Domain {self.name} already exists amazon-side")
141-
raise ResponseError(message)
140+
raise AlreadyExistsError(f"Domain {self.name} already exists amazon-side") from e
141+
raise ResponseError(message, error_code=error_code) from e
142142

143143
@exceptions.translate(ClientError, to=ResponseError)
144144
@exceptions.catch(

simpleflow/swf/mapper/models/workflow.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ def _diff(self, ignore_fields: list[str] | None = None) -> ModelDiff:
133133
error_code = extract_error_code(e)
134134
message = extract_message(e)
135135
if error_code == "UnknownResourceFault":
136-
raise DoesNotExistError("Remote Domain does not exist")
136+
raise DoesNotExistError("Remote Domain does not exist") from e
137137

138-
raise ResponseError(message)
138+
raise ResponseError(message, error_code=error_code) from e
139139

140140
workflow_info = description["typeInfo"]
141141
workflow_config = description["configuration"]
@@ -199,9 +199,9 @@ def save(self) -> None:
199199
error_code = extract_error_code(e)
200200
message = extract_message(e)
201201
if error_code == "TypeAlreadyExistsFault":
202-
raise AlreadyExistsError(f"Workflow type {self.name} already exists amazon-side")
202+
raise AlreadyExistsError(f"Workflow type {self.name} already exists amazon-side") from e
203203
if error_code == "UnknownResourceFault":
204-
raise DoesNotExistError(message)
204+
raise DoesNotExistError(message) from e
205205
raise
206206

207207
def delete(self) -> None:
@@ -212,7 +212,7 @@ def delete(self) -> None:
212212
error_code = extract_error_code(e)
213213
message = extract_message(e)
214214
if error_code in ["UnknownResourceFault", "TypeDeprecatedFault"]:
215-
raise DoesNotExistError(message)
215+
raise DoesNotExistError(message) from e
216216

217217
def upstream(self) -> WorkflowType:
218218
from simpleflow.swf.mapper.querysets.workflow import WorkflowTypeQuerySet
@@ -400,9 +400,9 @@ def _diff(self, ignore_fields: list[str] | None = None) -> ModelDiff:
400400
error_code = extract_error_code(e)
401401
message = extract_message(e)
402402
if error_code == "UnknownResourceFault":
403-
raise DoesNotExistError("Remote Domain does not exist")
403+
raise DoesNotExistError("Remote Domain does not exist") from e
404404

405-
raise ResponseError(message)
405+
raise ResponseError(message, error_code=error_code) from e
406406

407407
execution_info = description["executionInfo"]
408408
execution_config = description["executionConfiguration"]

simpleflow/swf/mapper/querysets/activity.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ def get(self, name: str, version: str, *args, **kwargs) -> ActivityType:
101101
error_code = extract_error_code(e)
102102
message = extract_message(e)
103103
if error_code == "UnknownResourceFault":
104-
raise DoesNotExistError(message)
104+
raise DoesNotExistError(message) from e
105105

106-
raise ResponseError(message)
106+
raise ResponseError(message, error_code=error_code) from e
107107

108108
activity_info = response[self._infos]
109109
activity_config = response["configuration"]

simpleflow/swf/mapper/querysets/domain.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ def get(self, name: str, *args, **kwargs) -> Domain:
3939
except ClientError as e:
4040
error_code = e.response["Error"]["Code"]
4141
if error_code == "UnknownResourceFault":
42-
raise DoesNotExistError(f"No such domain: {name}")
42+
raise DoesNotExistError(f"No such domain: {name}") from e
4343
# Any other errors should raise
44-
raise ResponseError(e.args[0])
44+
raise ResponseError(e.args[0], error_code=error_code) from e
4545

4646
domain_info = response["domainInfo"]
4747
domain_config = response["configuration"]

simpleflow/swf/mapper/querysets/workflow.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ def get(self, name, version, *args, **kwargs):
120120
error_code = extract_error_code(e)
121121
message = extract_message(e)
122122
if error_code == "UnknownResourceFault":
123-
raise DoesNotExistError(message)
123+
raise DoesNotExistError(message) from e
124124

125-
raise ResponseError(message)
125+
raise ResponseError(message, error_code=error_code) from e
126126

127127
wt_info = response[self._infos]
128128
wt_config = response["configuration"]
@@ -475,9 +475,9 @@ def get(self, workflow_id, run_id, *args, **kwargs):
475475
error_code = extract_error_code(e)
476476
message = extract_message(e)
477477
if error_code == "UnknownResourceFault":
478-
raise DoesNotExistError(message)
478+
raise DoesNotExistError(message) from e
479479

480-
raise ResponseError(message)
480+
raise ResponseError(message, error_code=error_code) from e
481481

482482
execution_info = response[self._infos]
483483
execution_config = response["executionConfiguration"]

0 commit comments

Comments
 (0)