Fastapi: bind db engine and redis client to app for global usage

Created on 20 Jul 2020  路  5Comments  路  Source: tiangolo/fastapi

I've been using flask and tornado for some time, and I used to create a DB engine and Redis client and something like the fileserver, then bind them to app.config in the main app.py file.

# in the main app,py file
from sqlalchemy import create_engine
from redis import Redis

dbe = create_engine(...)
app.config['dbe'] = dbe

redis_cli = Redis(...)
app.config['redis_cli'] = redis_cli

then I can from flask import current_app as app and use the app.config['dbe'] to execute sqls.

# in the app view file
from flask import current_app as app
app.config['dbe'].execute(sql, **kwargs)
app.config['redis_cli'].get(...)

With this method, I don't need to initialize the DB engine and Redis client every time, and all the pre-initialized DB engine can be used in the app views(i just treat them as handlers or routers)

I'm wondering is there an equivalent method in fastAPI? Or, I need to create them every time?

question

Most helpful comment

It is possible to do this and very useful.

Although currently it means making a change to Starlette. Which (according to my needs) incorrectly overwrites the 'app' parameter set in the ASGI context. See https://github.com/encode/starlette/issues/977

In my code (which has only been tested in development! ) I'm using app.extra['redis'] to store a redis connection pool.

I have something along the lines of ...

@app.on_event("startup")
async def handle_startup():
    try:
        pool = await aioredis.create_redis_pool(
            (REDIS_HOST, REDIS_PORT), encoding='utf-8')
        logger.info(f"Connected to Redis on {REDIS_HOST} {REDIS_PORT}")
        app.extra['redis'] = pool
    except ConnectionRefusedError as e:
        logger.info(f"cannot connect to redis on {REDIS_HOST} {REDIS_PORT}")
        return

Then in my endpoint handlers i'll have something like...

@router.put("/blah/de/blah/") 
        async def update(request: Request)
            redis = request.app.extra['redis']

It has worked for me so far in my limited testing.... I'd be interested if others can see any problems with this approach?

I tried Contextvars but didnt work for me seems to only make sense to for long lived coroutines for example a websocket connection.

All 5 comments

1028 I suggest to read the last comment of tigalo

thanks for the reply, it seems that tiangolo thinks And thread locals are a tool that only works with simple threaded applications, it doesn't work in async contexts. , and he mentioned In those cases (and all the cases in modern Python) you need instead context vars., but how could I create context vars in fastapi?

It is possible to do this and very useful.

Although currently it means making a change to Starlette. Which (according to my needs) incorrectly overwrites the 'app' parameter set in the ASGI context. See https://github.com/encode/starlette/issues/977

In my code (which has only been tested in development! ) I'm using app.extra['redis'] to store a redis connection pool.

I have something along the lines of ...

@app.on_event("startup")
async def handle_startup():
    try:
        pool = await aioredis.create_redis_pool(
            (REDIS_HOST, REDIS_PORT), encoding='utf-8')
        logger.info(f"Connected to Redis on {REDIS_HOST} {REDIS_PORT}")
        app.extra['redis'] = pool
    except ConnectionRefusedError as e:
        logger.info(f"cannot connect to redis on {REDIS_HOST} {REDIS_PORT}")
        return

Then in my endpoint handlers i'll have something like...

@router.put("/blah/de/blah/") 
        async def update(request: Request)
            redis = request.app.extra['redis']

It has worked for me so far in my limited testing.... I'd be interested if others can see any problems with this approach?

I tried Contextvars but didnt work for me seems to only make sense to for long lived coroutines for example a websocket connection.

Works the same way for websockets, like:

@router.websocket_route("/ws")
class WSEndpoint(WebSocketEndpoint):
    async def on_connect(self, websocket: WebSocket) -> None:
        await websocket.app.extra['redis'].info()

I just find that nsidnev made a solution in https://github.com/nsidnev/fastapi-realworld-example-app and the way he/she did it is storing DB connection in app.state.pool, which is actually the same solution in #1028 provided by mikelibg as well as what a137x did above, also leonh used the same solution. But storing DB connection this way might not be recommended, since we didn't get a confirmation in #1028 from tigalo, there might be some profound reasons. Hope someone could explain this in a more detailed way. Thanks in advance.

Was this page helpful?
0 / 5 - 0 ratings