Fastapi: Yield dependency not cleaning up immediately after the request

Created on 15 Jul 2020  路  4Comments  路  Source: tiangolo/fastapi

First check

  • [x] I added a very descriptive title to this issue.
  • [x] I used the GitHub search to find a similar issue and didn't find it.
  • [x] I searched the FastAPI documentation, with the integrated search.
  • [x] I already searched in Google "How to X in FastAPI" and didn't find any information.
  • [x] I already read and followed all the tutorial in the docs and didn't find an answer.
  • [x] I already checked if it is not related to FastAPI but to Pydantic.
  • [x] I already checked if it is not related to FastAPI but to Swagger UI.
  • [x] I already checked if it is not related to FastAPI but to ReDoc.
  • [x] After submitting this, I commit to one of:

    • Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.

    • I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.

    • Implement a Pull Request for a confirmed bug.

Example

Here's a self-contained, minimal, reproducible, example with my use case:

from fastapi import FastAPI, Depends
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session

engine = create_engine('postgresql://goals:goals@db:5432/goals')
SessionLocal = sessionmaker(autocommit=False, bind=engine)

def get_db():
    session = SessionLocal()
    try:
        print('opening')
        yield session
    except:
        print('rollback')
        session.rollback()
    finally:
        print('closing')
        session.close()

app = FastAPI()

@app.get('/test')
def test_route(db = Depends(get_db)):
    return {"Hello": "world"}

Description

I'm trying to use yield dependencies to insert my database session into routes. I wanted to check that the session was being closed at the end of the request, however I've found that it doesn't close (or rollback during an exception) until the next request. It seems that everything after yield session isn't being executed after the request.

Using the example, I can curl localhost:8000/test, and the log will show:

backend_1  | INFO:     Waiting for application startup.
backend_1  | INFO:     Application startup complete.
backend_1  | opening
backend_1  | INFO:     172.22.0.1:52200 - "GET /test HTTP/1.1" 200 OK

If I then hit the endpoint again, I get

backend_1  | closing
backend_1  | opening
backend_1  | INFO:     172.22.0.1:52208 - "GET /test HTTP/1.1" 200 OK

Strangely, if I force a restart after a request, the session will then close.

backend_1  | opening
backend_1  | INFO:     172.22.0.1:52208 - "GET /test HTTP/1.1" 200 OK
backend_1  | WARNING:  Detected file change in 'main.py'. Reloading...
backend_1  | INFO:     Shutting down
backend_1  | INFO:     Waiting for application shutdown.
backend_1  | INFO:     Application shutdown complete.
backend_1  | INFO:     Finished server process [156]
backend_1  | closing
backend_1  | INFO:     Started server process [159]
backend_1  | INFO:     Waiting for application startup.
backend_1  | INFO:     Application startup complete.

Environment

  • OS: [e.g. Linux / Windows / macOS]: Linux, using FastAPI Docker image
  • FastAPI Version [e.g. 0.3.0]: 0.59 (also tried with 0.58)
  • Python version: 3.8.2 (the version from the docker image)
question

Most helpful comment

There is good explanation why this is working this way:
https://stackoverflow.com/a/57801848

All 4 comments

I believe the problem is how stdout works in docker image, you can try same example using writing file or raising exception to see immediate teardown of dependency, i.e.:

from fastapi import FastAPI, Depends


def get_db():
    try:
        print('opening')
        with open('file', 'a') as f:
            f.write('opening')
        yield
    except:
        with open('file', 'a') as f:
            f.write('closing')
        print('rollback')
    finally:
        with open('file', 'a') as f:
            f.write('closing')
        print('closing')


app = FastAPI()


@app.get('/test')
def test_route(db=Depends(get_db)):
    return {"Hello": "world"}

There is good explanation why this is working this way:
https://stackoverflow.com/a/57801848

Yep, that's it! Never would've found that myself, thank you, and thank you for the link explaining why. :)

Thanks for the help here @SirTelemak ! That link was. :ok_hand: I didn't know that either. :shrug:

Thanks for reporting back and closing the issue @GabrielCarpr :+1:

Was this page helpful?
0 / 5 - 0 ratings