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.

[QUESTION] SQLAlchemy Dependency vs. Middleware vs. scoped_session

See original GitHub issue

The SQLAlchemy library comes with a scoped_session object, which effectively allows for thread-safe database sessions. Is there a reason the docs recommend using the dependency method > middleware method > just a scoped_session global?

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:8
  • Comments:29 (11 by maintainers)

github_iconTop GitHub Comments

38reactions
sm-Fifteencommented, Nov 22, 2019

I’m not sure about the reasoning behind the doc’s reccomendations, but the reason why I’d personally advise against making scoped_session global is because it then forces you to also have a global engine to bind it to, and global variables in ASGI don’t have a well-defined lifetime (see #617). There’s also how using dependencies rather than globals mean that all the ressource needed by your route are passed as function parameters, which tends to make testing easier since you can just pass a fake DB session instead of the one you would normally use.

SQLAlchemy’s own doc says scoped_session are just one of many ways of handling sessions. From my understanding of how scoped_session and sessions work, thread-local sessions might not work that well with FastAPI since Python’s coroutines don’t have a 1:1 mapping with threads, so I suspect that this could cause some unexpected behavior (though I haven’t tried this, I might be wrong).

I personally prefer handling my connections this way:

from typing import Optional, AsyncIterable

from fastapi import Depends, FastAPI
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from sqlalchemy.engine import Engine as Database

app = FastAPI()

_db_conn: Optional[Database]

@app.on_event("startup")
def open_database_connection_pools():
    global _db_conn
    _db_conn = create_engine(...)

@app.on_event("shutdown")
def close_database_connection_pools():
    global _db_conn
    if _db_conn: _db_conn.dispose()

async def get_db_conn() -> Database:
    assert _db_conn is not None
    return _db_conn

# This is the part that replaces sessionmaker
async def get_db_sess(db_conn = Depends(get_db_conn)) -> AsyncIterable[Session]:
    sess = Session(bind=db_conn)

    try:
        yield sess
    finally:
        sess.close()

@app.get("/")
def foo(db_sess: Session = Depends(get_db_sess)):
    pass

The benefits of this approach are:

  • You can connect to as many databases as needed, which was a problem for me with the middleware approach, especially when using Flask
  • Your DB connections are released at application shutdown instead of garbage collection, which means you won’t run into issues if you use uvicorn --reload
  • Your DB sessions will automatically be closed when the route using as a dependency finishes, so any uncommited operations will be rolled back.
  • While testing you can just pass a different Session object so you can write to a test database instead
  • When calling that route as a regular function from a different thread (which is a niche case, but it happens), you can pass an existing database session object instead of having the route create a new one.
6reactions
bharathanrcommented, Jan 30, 2022

I’ve been trying to go through all the related issues #104 #290 etc., and come up with a summary of what the best production worthy steps might be as of 2022. It looks like, the following apply:

  1. Manage the lifetime of the engine/db connections explicitly on app startup/shutdown or somehow use Starlette’s lifespan (app.state and a global reference are both useful to implement this feature)
  2. Manage DB Sessions using FastAPI’s dependencies with yield as documented here.
  3. Do not explicitly commit in the context manager written as part of Point 2, rather commit within the API endpoint itself. Rollback is always done implicitly.
  4. Use async libraries if possible to avoid any gotchas with threads getting deadlocked once connections are exhausted as in #104 and #3205
  5. Do not keep any state in globals because they are not guaranteed to be cleaned up under ASGI

@sm-Fifteen , @dmontagu could you please weigh in with your thoughts? If this is a useful summary, it might be a good idea to write this in the docs.

Read more comments on GitHub >

github_iconTop Results From Across the Web

[QUESTION] SQLAlchemy Dependency vs. Middleware vs ...
The SQLAlchemy library comes with a scoped_session object, which effectively allows for thread-safe database sessions. Is there a reason the docs recommend ...
Read more >
SQLAlchemy Sessions - FastAPI Utilities
The get_db dependency makes use of a context-manager dependency, rather than a middleware-based approach. This means that any endpoints that don't make use...
Read more >
tiangolo/fastapi - Gitter
Is there a way to add support of application/json or any middleware to convert incoming json to www-form-urlencoded? I am in situation that...
Read more >
python - scoped_session(sessionmaker()) or plain ...
I am using SQlAlchemy in my web project. What should I use - scoped_session(sessionmaker()) or plain sessionmaker() - and why?
Read more >
SQL (Relational) Databases - FastAPI
Our dependency will create a new SQLAlchemy SessionLocal that will be used in a single request, and then close it once the request...
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