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

Raising Exceptions in sub-applications routes #1433

Closed
2 tasks done
mrkovalchuk opened this issue Jan 25, 2022 · 0 comments · Fixed by #1459
Closed
2 tasks done

Raising Exceptions in sub-applications routes #1433

mrkovalchuk opened this issue Jan 25, 2022 · 0 comments · Fixed by #1459
Labels
bug Something isn't working

Comments

@mrkovalchuk
Copy link

mrkovalchuk commented Jan 25, 2022

Checklist

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

Describe the bug

Let's start with this PR: #1262

It's about preventing raise anyio.ExceptionGroup in views under a BaseHTTPMiddleware. PR resolve that problem with nonlocal variable that stores our exception. But in the case of sub-applications, it does not work.

As I can see (fyi I am not good at asyncio), in the case below, we reach and read a response before we raise an exception and store it to our nonlocal variable:

fragment of BaseHTTPMiddleware.__call__

async def call_next(request: Request) -> Response:
    app_exc: typing.Optional[Exception] = None
    send_stream, recv_stream = anyio.create_memory_object_stream()

    async def coro() -> None:
        nonlocal app_exc

        async with send_stream:
            try:
                task = await self.app(scope, request.receive, send_stream.send)
            except Exception as exc:
                app_exc = exc

    task_group.start_soon(coro)

    try:
        message = await recv_stream.receive()
    except anyio.EndOfStream:
        if app_exc is not None:
            raise app_exc
        raise RuntimeError("No response returned.")
    
    ...
    response = StreamingResponse(
        status_code=message["status"], content=body_stream()
    )
    response.raw_headers = message["headers"]
    return response

in this moment: except anyio.EndOfStream: exception still no raised.

Steps to reproduce the bug

import httpx
import pytest
from fastapi import FastAPI, APIRouter
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import Route


class SomeError(Exception):
    pass


class SomeMiddleware(BaseHTTPMiddleware):
    async def dispatch(
        self, request: Request, call_next: RequestResponseEndpoint
    ) -> Response:
        return await call_next(request)

# Drop (or use not BaseHTTPMiddleware based) middleware and test works fine
app = FastAPI(middleware=[Middleware(SomeMiddleware), ])


async def simple_route(request: Request):
    raise SomeError


another_router = APIRouter(
    routes=[Route('/simple-route/', simple_route, methods=['GET'])]
)
sub_app = FastAPI()
sub_app.include_router(another_router)
app.router.mount(f'/api', sub_app)


@pytest.mark.asyncio
async def test_simple_route():
    async with httpx.AsyncClient(app=app) as client:
        with pytest.raises(SomeError):
            await client.get("http://testserver/api/simple-route/")

Expected behavior

An exception was raised and caught by pytest exception

Actual behavior

An exception wasn't raised

Debugging material

No response

Environment

macOS Monterey 12.0.1, starlette 0.17.1, Python 3.9.9

Additional context

No response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
2 participants