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"}
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.
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:
Most helpful comment
There is good explanation why this is working this way:
https://stackoverflow.com/a/57801848