Skip to content

Commit d604447

Browse files
committed
Preserve error type
1 parent 7bcb89d commit d604447

File tree

2 files changed

+41
-8
lines changed

2 files changed

+41
-8
lines changed

src/dispatch/proto.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import pickle
44
from dataclasses import dataclass
5+
from types import TracebackType
56
from typing import Any
67

78
import google.protobuf.any_pb2
@@ -280,7 +281,13 @@ class Error:
280281
Output.
281282
"""
282283

283-
def __init__(self, status: Status, type: str | None, message: str | None):
284+
def __init__(
285+
self,
286+
status: Status,
287+
type: str | None,
288+
message: str | None,
289+
value: Exception | None = None,
290+
):
284291
"""Create a new Error.
285292
286293
Args:
@@ -300,6 +307,7 @@ def __init__(self, status: Status, type: str | None, message: str | None):
300307
self.type = type
301308
self.message = message
302309
self.status = status
310+
self.value = value
303311

304312
@classmethod
305313
def from_exception(cls, ex: Exception, status: Status | None = None) -> Error:
@@ -313,18 +321,29 @@ def from_exception(cls, ex: Exception, status: Status | None = None) -> Error:
313321
if status is None:
314322
status = status_for_error(ex)
315323

316-
return Error(status, ex.__class__.__qualname__, str(ex))
324+
return Error(status, ex.__class__.__qualname__, str(ex), ex)
317325

318326
def to_exception(self) -> Exception:
319-
# TODO: use correct error type
320-
return RuntimeError(self.message)
327+
if self.value is not None:
328+
return self.value
329+
330+
g = globals()
331+
try:
332+
cls = g[self.type]
333+
assert issubclass(cls, Exception)
334+
except (KeyError, AssertionError):
335+
return RuntimeError(self.message)
336+
else:
337+
return cls(self.message)
321338

322339
@classmethod
323340
def _from_proto(cls, proto: error_pb.Error) -> Error:
324-
return cls(Status.UNSPECIFIED, proto.type, proto.message)
341+
value = pickle.loads(proto.value) if proto.value else None
342+
return cls(Status.UNSPECIFIED, proto.type, proto.message, value)
325343

326344
def _as_proto(self) -> error_pb.Error:
327-
return error_pb.Error(type=self.type, message=self.message)
345+
value = pickle.dumps(self.value) if self.value else None
346+
return error_pb.Error(type=self.type, message=self.message, value=value)
328347

329348

330349
def _any_unpickle(any: google.protobuf.any_pb2.Any) -> Any:

tests/dispatch/test_scheduler.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ async def call_sequentially(*functions):
3131
return results
3232

3333

34+
@durable
35+
async def raises_error():
36+
raise ValueError("oops")
37+
38+
3439
class TestOneShotScheduler(unittest.TestCase):
3540
def test_main_return(self):
3641
@durable
@@ -43,10 +48,10 @@ async def main():
4348
def test_main_raise(self):
4449
@durable
4550
async def main():
46-
raise RuntimeError("oops")
51+
raise ValueError("oops")
4752

4853
output = self.start(main)
49-
self.assert_exit_result_error(output, RuntimeError, "oops")
54+
self.assert_exit_result_error(output, ValueError, "oops")
5055

5156
def test_main_args(self):
5257
@durable
@@ -215,6 +220,14 @@ async def main():
215220

216221
self.assertEqual(len(correlation_ids), 8)
217222

223+
def test_raise_indirect(self):
224+
@durable
225+
async def main():
226+
return await gather(call_one("a"), raises_error())
227+
228+
output = self.start(main)
229+
self.assert_exit_result_error(output, ValueError, "oops")
230+
218231
def start(self, main: Callable, *args: Any, **kwargs: Any) -> Output:
219232
input = Input.from_input_arguments(main.__qualname__, *args, **kwargs)
220233
return OneShotScheduler(main).run(input)
@@ -258,6 +271,7 @@ def assert_exit_result_error(
258271
self.assertEqual(error.__class__, expect)
259272
if message is not None:
260273
self.assertEqual(str(error), message)
274+
return error
261275

262276
def assert_poll(self, output: Output) -> poll_pb.Poll:
263277
response = output._message

0 commit comments

Comments
 (0)