Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move exception handling logic to endpoints #2020

Closed
wants to merge 7 commits into from

Conversation

adriangb
Copy link
Member

@adriangb adriangb commented Jan 27, 2023

Fixes #493, partially resolves #1692 (comment), resolves #1649 (comment)

By moving handling of exceptions to the Route level we can use the same Request/WebSocket object for the endpoint and exception handlers.

I wrote this PR so that it is backwards compatible (and it also needs a lot of polish). There's a lot of duplicate logic between ExceptionMiddleware and routing.py. I see two options:

  • Clean up the implementation to minimize duplicate code but keep ExceptionMiddleware around. This is less likely to break things like FastAPI that poke around into the internals of Starlette. It also means that ASIG middleware e.g. in an ASGI app mounted via Mount can continue to raise HTTPExceptions which will get caught by ExceptionMiddleware (I'd argue that anyone that is doing this should not be doing this but it's technically possible to do). (see note 1)
  • Remove ExceptionMiddleware. We'd just need to move a bit of logic (separating the exception and status handlers and putting them into the scope for every request) into Starlette. This would eliminate all of the duplicate code.

Implementation aside, to me this makes sense conceptually: exception handlers operate on requests/webscokets not at the ASGI level. So they should live within the sphere of Request/WebSocket, not alongside generic ASGI middleware. I think this is the same principle that applies to FastAPI's dependencies and why they don't have the problems that BaseHTTPMiddleware has.

Notes:

  1. Note that this does not apply to normal middleware installed via Starlette(..., middleware=...) since those middleware have never had any guarantee of HTTPExceptions they raise being caught.

@adriangb adriangb requested review from Kludex and a team January 27, 2023 22:21
@adriangb
Copy link
Member Author

Tests aare failing for coverage reasons. I like how the diff shows very clearly what the real change is now so I opened a PR against this branch to show what a "cleaned up" version with no coverage issues would look like, but the diff is bigger: master...adriangb:starlette:cleanup-move-exc

@alex-oleshkevich
Copy link
Member

Will it work if we just wrap self.handle in ExceptionMiddleware?

# starlette/routing.py
class BaseRoute:
    async def __call__(self, scope, receive, send):
        ...
        handler = ExceptionMiddleware(self.handle, handlers=scope['starlette.exception_handlers'], debug=scope['app'].debug),
        await handler(scope, receive, send)

If possible, this significantly reduces the code duplication.

@adriangb
Copy link
Member Author

adriangb commented Jan 30, 2023

There’d still be two request instances. Reducing code duplication can be done easily with some refactoring (there is no duplicate code in master...adriangb:starlette:cleanup-move-exc)

@adriangb
Copy link
Member Author

adriangb commented Feb 4, 2023

@tiangolo would you mind taking a look at this with respect to how it might impact FastAPI? You vendor request_response and some other bits but I think they could be ported over right?

@Kludex Kludex added this to the Version 0.26.0 milestone Feb 6, 2023
@tiangolo
Copy link
Sponsor Member

tiangolo commented Feb 6, 2023

I like it! 🚀 I agree it could/would be ported over to FastAPI.

Thanks @adriangb for pinging me (and @Kludex for DMing about the ping, I tend to lose these important pings among too many GitHub email notifications 😬 ).


A bit more from my point of view, probably taking this even further (in the future):

I want to be able to support exception handlers at the router level, to have some routers that handle some specific exceptions that might be relevant only to some portions of the code related to that router, instead of having to put all the exceptions together at the global app.

In FastAPI, this will also mean that I will have to refactor how app.include_router() works to not recreate routes but to actually store and re-use sub-routers. That's a larger refactor in FastAPI, but anyway, that's what I would like to target in the future, and I think this logic, moving exception handling away from middleware will bring us closer to that.

@adriangb
Copy link
Member Author

adriangb commented Feb 6, 2023

I like it! 🚀 I agree it could/would be ported over to FastAPI.

Good to hear! I think if FastAPI is willing to adapt I don't see any blockers for moving forward with this; it's a backwards compatible change for pure-Starlette projects.

I want to be able to support exception handlers at the router level, to have some routers that handle some specific exceptions that might be relevant only to some portions of the code related to that router, instead of having to put all the exceptions together at the global app.

Interesting, this is not even something I had thought of! But yes of course this change would make it pretty straightforward to have per-route exception handlers that we could merge with the application or router level exception handlers using a ChainMap sort of thing. Great point!

@adriangb
Copy link
Member Author

adriangb commented Feb 6, 2023

Closing this in favor of #2026 (see #2020 (comment))

@adriangb adriangb closed this Feb 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Question about Request.Body()
4 participants