dispatchrun / dispatch-py

Python package to develop applications with Dispatch.
https://pypi.org/project/dispatch-py/
Apache License 2.0
56 stars 3 forks source link

function: propagate exception traceback #72

Closed achille-roussel closed 8 months ago

achille-roussel commented 8 months ago

This PR fixes error propagation through the call stack of durable coroutines.

I have added a dependency on the tblib package to deal with parsing stack traces and reconstructing traceback objects which is quite tricky to do since these values can only be constructed by the interpreter when raising exceptions (see).

Using this code example:

from fastapi import FastAPI
from dispatch.fastapi import Dispatch

app = FastAPI()
dispatch = Dispatch(app)

@dispatch.coroutine()
async def get(something):
    raise ValueError("This is an error")

@dispatch.coroutine()
async def publish():
    return await get.call("this")

@app.get("/")
def root():
    publish.dispatch()
    return "OK"

The execution produces these logs:

INFO:     Started server process [22270]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:52112 - "GET / HTTP/1.1" 200 OK
INFO:     127.0.0.1:52119 - "POST /dispatch.sdk.v1.FunctionService/Run HTTP/1.1" 200 OK
@dispatch.coroutine: 'get' raised an exception
Traceback (most recent call last):
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 252, in _run
    coroutine_yield = coroutine.run()
                      ^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 115, in run
    return self.coroutine.send(None)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/experimental/durable/function.py", line 218, in send
    return self.coroutine.send(send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/main.py", line 14, in get
    raise ValueError("This is an error")
ValueError: This is an error
INFO:     127.0.0.1:52119 - "POST /dispatch.sdk.v1.FunctionService/Run HTTP/1.1" 200 OK
@dispatch.coroutine: 'publish' raised an exception
Traceback (most recent call last):
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 252, in _run
    coroutine_yield = coroutine.run()
                      ^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 118, in run
    return self.coroutine.throw(error)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/experimental/durable/function.py", line 221, in throw
    return self.coroutine.throw(typ, val, tb)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/main.py", line 19, in publish
    return await get.call("this")
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/experimental/durable/function.py", line 285, in throw
    return self.generator.throw(typ, val, tb)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/function.py", line 92, in _call_async
    return await dispatch.coroutine.call(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.11/3.11.7_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/types.py", line 220, in throw
    return self.__wrapped.throw(tp, *rest)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/experimental/durable/function.py", line 285, in throw
    return self.generator.throw(typ, val, tb)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/coroutine.py", line 14, in call
    return (yield call)
            ^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 252, in _run
    coroutine_yield = coroutine.run()
    ^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 115, in run
    return self.coroutine.send(None)
    ^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/experimental/durable/function.py", line 218, in send
    return self.coroutine.send(send)
    ^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/main.py", line 14, in get
    raise ValueError("This is an error")
      ^^^^^^^^^^^^^^^^^
ValueError: This is an error
INFO:     127.0.0.1:52119 - "POST /dispatch.sdk.v1.FunctionService/Run HTTP/1.1" 200 OK

As we can see here, the original exception traceback is preserved and shown when re-raising the exception in the caller.

Please take a look and let me know if anything should be changed.

Fixes #68