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.

Code after yield in the async dependecy executed before response has been sent to the client

See original GitHub issue

First Check

  • I added a very descriptive title to this issue.
  • I used the GitHub search to find a similar issue and didn’t find it.
  • I searched the FastAPI documentation, with the integrated search.
  • I already searched in Google “How to X in FastAPI” and didn’t find any information.
  • I already read and followed all the tutorial in the docs and didn’t find an answer.
  • I already checked if it is not related to FastAPI but to Pydantic.
  • I already checked if it is not related to FastAPI but to Swagger UI.
  • I already checked if it is not related to FastAPI but to ReDoc.

Commit to Help

  • I commit to help with one of those options 👆

Example Code

import asyncio
from typing import AsyncGenerator

import structlog
import uvicorn
from fastapi import FastAPI, Depends
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import Response

logger = structlog.getLogger(__name__)


app = FastAPI()


class BarMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        logger.info("BAR: Before call_next()")
        response = await call_next(request)
        logger.info("BAR: After call_next()")
        logger.info(f"BAR: Status code {response.status_code}")

        logger.info("BAR: Before sleep()")
        await asyncio.sleep(2)
        logger.info("BAR: After sleep()")

        return response


class ZxcMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        logger.info("ZXC: Before call_next()")
        response = await call_next(request)
        logger.info("ZXC: After call_next()")
        logger.info(f"ZXC: Status code {response.status_code}")

        logger.info("ZXC: Before sleep()")
        await asyncio.sleep(2)
        logger.info("ZXC: After sleep()")

        return response


async def get_foo() -> AsyncGenerator[str, None]:
    logger.info("BEFORE YIELD FOO")
    yield "foo"
    logger.info("AFTER YIELD FOO")


async def get_qwerty(foo: str = Depends(get_foo)) -> AsyncGenerator[str, None]:
    logger.info("BEFORE YIELD QWERTY")
    yield "qwerty"
    logger.info("AFTER YIELD QWERTY")


@app.get("/")
async def root(qwerty: str = Depends(get_qwerty)):
    return {"message": qwerty}


app.add_middleware(BarMiddleware)
app.add_middleware(ZxcMiddleware)


if __name__ == "__main__":
    uvicorn.run(
        "test:app",
        host="0.0.0.0",
        port=8080,
        reload=True,
    )

Description

Hi.

From the documentation:

The code following the yield statement is executed after the response has been delivered:

But this is not the case:

2022-06-23 09:21:29,932 uvicorn.error INFO: Finished server process [30197]
2022-06-23 09:21:30,309 uvicorn.error INFO: Started server process [30216]
2022-06-23 09:21:30,309 uvicorn.error INFO: Waiting for application startup.
2022-06-23 09:21:30,309 uvicorn.error INFO: Application startup complete.
2022-06-23 09:22.12 [info     ] ZXC: Before call_next()
2022-06-23 09:22.12 [info     ] BAR: Before call_next()
2022-06-23 09:22.12 [info     ] BEFORE YIELD FOO
2022-06-23 09:22.12 [info     ] BEFORE YIELD QWERTY
2022-06-23 09:22.12 [info     ] BAR: After call_next()
2022-06-23 09:22.12 [info     ] BAR: Status code 200
2022-06-23 09:22.12 [info     ] BAR: Before sleep()
2022-06-23 09:22.14 [info     ] BAR: After sleep()
2022-06-23 09:22.14 [info     ] ZXC: After call_next()
2022-06-23 09:22.14 [info     ] ZXC: Status code 200
2022-06-23 09:22.14 [info     ] ZXC: Before sleep()
2022-06-23 09:22.14 [info     ] AFTER YIELD QWERTY
2022-06-23 09:22.14 [info     ] AFTER YIELD FOO
2022-06-23 09:22.16 [info     ] ZXC: After sleep()
2022-06-23 09:22:16,494 uvicorn.access INFO: 127.0.0.1:55382 - "GET / HTTP/1.1" 200

Response has not been sent to the client yet, but we’re already in the “after yield” code. I can not reproduce it with a single middleware though.

Using native ASGI middlewares (instead of inheriting from BaseHTTPMiddleware) does not solve the issue.

It’s a problem for us, because we can not rely on the fact that transaction has been commited already (in the middleware), when we’re in the “after yield” code.

Either docs are misleading, or something works incorrectly.

Operating System

Linux

Operating System Details

No response

FastAPI Version

0.78.0

Python Version

3.8.10

Additional Context

No response

Issue Analytics

  • State:open
  • Created a year ago
  • Reactions:3
  • Comments:9 (6 by maintainers)

github_iconTop GitHub Comments

2reactions
JarroVGITcommented, Jun 25, 2022

BaseHTTPMiddleware will be deprecated (see this open issue on the Starlette project: Issue). So having the starting point where you see this behaviour with native ASGI middleware would help me reproduce this (and hopefully, help you fix it 😉)

That’s not for sure yet… There has been a lot of discussion about BaseHTTPMiddleware. Also, the limitations of BaseHTTPMiddleware are usually not a problem for most of the users.

That being said… They are still limitations, and if you are going to have them, for the time being, pure ASGI middlewares are recommended.

Fair point, I corrected my comment above 😇

0reactions
Kludexcommented, Jun 25, 2022

BaseHTTPMiddleware will be deprecated (see this open issue on the Starlette project: Issue). So having the starting point where you see this behaviour with native ASGI middleware would help me reproduce this (and hopefully, help you fix it 😉)

That’s not for sure yet… There has been a lot of discussion about BaseHTTPMiddleware. Also, the limitations of BaseHTTPMiddleware are usually not a problem for most of the users.

That being said… They are still limitations, and if you are going to have them, for the time being, pure ASGI middlewares are recommended.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Dependencies with yield - FastAPI
The code following the yield statement is executed after the response has been delivered: async def get_db(): db = DBSession() try: yield db...
Read more >
Durable Orchestrations - Azure Functions | Microsoft Learn
Orchestrator functions have the following characteristics: ... until the function code is finished or until it has scheduled new async work.
Read more >
Web Server Advanced — aiohttp 3.8.3 documentation
The attribute is a list of asynchronous generators, a code before yield is an initialization stage (called on startup), a code after yield...
Read more >
Event Loop — Python 3.11.1 documentation
Source code: Lib/asyncio/events.py, Lib/asyncio/base_events.py Preface The event loop is the core of every asyncio application. Event loops run asynchronous ...
Read more >
How to return error response when database failed to commit
But as i know, from 0.74.0 , dependencies with yield can catch HTTPException and custom exceptions before response was sent(i test 0.80.0 is...
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