question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

"raise exc from None" interferes with exception chaining

See original GitHub issue

Checklist

  • The bug is reproducible against the latest release and/or master.
  • There are no similar issues or pull requests to fix it yet.

Describe the bug

Even though I use exception chaining (raise ... from) in my app, the original exception info gets deleted because Starlette handles exceptions with raise .. from None at the outer scope. Here is a MWE:

from starlette.applications import Starlette
from starlette.endpoints import HTTPEndpoint
from starlette.responses import HTMLResponse
from starlette.routing import Route


class TemplateRenderingError(Exception): pass


class Homepage(HTTPEndpoint):
    async def get(self, request):
        try:
            content = '{a}'.format()
        except KeyError as e:
            raise TemplateRenderingError from e
        return HTMLResponse(content)

app = Starlette(debug=True, routes=[
    Route('/', Homepage),
])

Run the server, load /, then look at the console output.

What I expect to get is a chained exception:

ERROR: Exception in ASGI application
Traceback (most recent call last):
  File ".\star_bug.py", line 13, in get
    content = '{a}'.format()
KeyError: 'a'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "c:\otree\ve-nodj\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 394, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "c:\otree\ve-nodj\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "c:\otree\ve-nodj\lib\site-packages\starlette\applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "c:\otree\ve-nodj\lib\site-packages\starlette\middleware\errors.py", line 184, in __call__
    raise exc
  File "c:\otree\ve-nodj\lib\site-packages\starlette\middleware\errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "c:\otree\ve-nodj\lib\site-packages\starlette\exceptions.py", line 84, in __call__
    raise exc
  File "c:\otree\ve-nodj\lib\site-packages\starlette\exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "c:\otree\ve-nodj\lib\site-packages\starlette\routing.py", line 582, in __call__
    await route.handle(scope, receive, send)
  File "c:\otree\ve-nodj\lib\site-packages\starlette\routing.py", line 243, in handle
    await self.app(scope, receive, send)
  File "c:\otree\ve-nodj\lib\site-packages\starlette\endpoints.py", line 30, in dispatch
    response = await handler(request)
  File ".\star_bug.py", line 15, in get
    raise TemplateRenderingError from e
star_bug.TemplateRenderingError

What I get is only the final exception:

ERROR: Exception in ASGI application
Traceback (most recent call last):
  File "c:\otree\ve-nodj\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 394, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "c:\otree\ve-nodj\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "c:\otree\ve-nodj\lib\site-packages\starlette\applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "c:\otree\ve-nodj\lib\site-packages\starlette\middleware\errors.py", line 184, in __call__
    raise exc
  File "c:\otree\ve-nodj\lib\site-packages\starlette\middleware\errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "c:\otree\ve-nodj\lib\site-packages\starlette\exceptions.py", line 83, in __call__
    raise exc from None
  File "c:\otree\ve-nodj\lib\site-packages\starlette\exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "c:\otree\ve-nodj\lib\site-packages\starlette\routing.py", line 582, in __call__
    await route.handle(scope, receive, send)
  File "c:\otree\ve-nodj\lib\site-packages\starlette\routing.py", line 243, in handle
    await self.app(scope, receive, send)
  File "c:\otree\ve-nodj\lib\site-packages\starlette\endpoints.py", line 30, in dispatch
    response = await handler(request)
  File ".\star_bug.py", line 15, in get
    raise TemplateRenderingError from e

Starlette uses raise exc from None in 4 places:

  • ExceptionMiddleware.__call__
  • ServerErrorMiddleware.__call__
  • WebSocketEndpoint.dispatch
  • _ASGIAdapter.send

It seems to me that in all 4 cases the from None could hide intentional context about users’ errors.

Having the chained traceback would be very valuable both for reading the console output and for programmatic use (I want to make a middleware that looks at the .__cause__ attribute of certain exceptions to deliver a more relevant error message). I’m happy to provide more details on my use case.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

3reactions
tomchristiecommented, Mar 25, 2021

It’s possible that there might be a good reason for one or more of those cases to be a raise exc from None, but in general I’d agree with you, yup. It’s probably worth a pull request, switching the behaviour of those. We can then review each case in that pull request, and check if any of them are “raise from None” for a good reason.

0reactions
oTree-orgcommented, Mar 17, 2021

Another fix for the issue would be to remove the from None in the following 4 places:

- ExceptionMiddleware.__call__
- ServerErrorMiddleware.__call__
- WebSocketEndpoint.dispatch
- ASGIAdapter.send
Read more comments on GitHub >

github_iconTop Results From Across the Web

PEP 3134 – Exception Chaining and Embedded Tracebacks
Implicit Exception Chaining​​ Calling compute(0, 0) causes a ZeroDivisionError . The compute() function catches this exception and calls log(exc) ...
Read more >
Python "raise from" usage
When raising from a exception handler where you don't want to show the context (don't want a during handling another exception happened message) ......
Read more >
10 Handling PL/SQL Errors
Handlers in the current block cannot catch the raised exception because an exception raised in a declaration propagates immediately to the enclosing block....
Read more >
30. Errors and Exception Handling | Python Tutorial
An exception is an error that happens during the execution of a program. Exceptions are known to non-programmers as instances that do not ......
Read more >
Exceptions - Manual
Normal execution (when no exception is thrown within the try block) will continue after that last catch block defined in sequence. Exceptions can...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found