Starlette: accessing app state on startup/shutdown handlers

Created on 29 Dec 2019  路  5Comments  路  Source: encode/starlette

i have a starlette app that uses @app.[route|on_event] decorators for routing but would like to switch to the recommended Starlette(routes=..., on_startup=...).

one thing i'm confusing about: how can i access app state via startup/shutdown handlers? currently i have the following:

from my_middleware import GetDatabaseConnectionMiddleware

app = Starlette()
# some middleware that gets a connection from app.state.connection_pool
# and adds it to the request's scope
app.add_middleware(GetDatabaseConnectionMiddleware)

@app.on_event("startup")
async def startup():
    app.state.connection_pool = await asyncpg.create_pool(
        dsn=DATABASE_URL, min_size=2, max_size=2
    )

i think this is a bit messy but seemed like the most obvious way to set app state in the startup handler. i'm not sure how i'd do the same when app is created after the functions are defined, though. any ideas on how to do this?

All 5 comments

Did databases library goods for you ? @zhammer

# https://www.starlette.io/database
import databases
database = databases.Database(DATABASE_URL)
...
app = Starlette(
    routes=routes,
    on_startup=[database.connect],
    on_shutdown=[database.disconnect]
)

thanks for the suggestion @elisong ! unfortunately i'm not using sqlalchemy (so the starlette.databases package wouldn't work for me). also i only provided the db connection pool setup example in my issue description, but there are a few other pieces of state i'd like to add to app in startup!

i'm not sure how i'd do the same when app is created after the functions are defined, though. any ideas on how to do this?

You can define the startup function before the application instantation. That's fine, since it won't run until the app is in scope.

async def startup():
    app.state.connection_pool = await asyncpg.create_pool(
        dsn=DATABASE_URL, min_size=2, max_size=2
    )

app = Starlette(routes=..., middleware=..., on_startup=[startup])

Sorry to piggyback on this, I didn't want to create what looked like a duplicate ticket.
Would it make sense for the startup/shutdown event handlers to get passed the app instance?
Unless I've missed something, the example @tomchristie gives above would not work if, say, a 3rd party library needed to connect bits onto the app.

My use case is for my rate limiting plugin where I would like to store the limiter on app.state, but also add an event handler to return a custom response when a rate limit is hit (to be able to provide details about which limit was hit to the user, for instance).
At the moment, I do it like:

    limiter = Limiter(key_func=get_remote_address)
    app = Starlette()
    app.state.limiter = limiter
    app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

But that means a bunch of imports from the innards of the plugin, which I don't find very clean, especially if these internals change.
If the app instance was passed to the startup handler, I could do the initialization where the app instance is out of scope, something like:

    limiter = Limiter(key_func=get_remote_address)
    app = Starlette(on_startup=[limiter.startup_handler])

and not have to worry about how the plugin works (to the extent that it doesn't clash with other ones of course).
Does this make sense? I guess there might be a better way to do it, but I couldn't think of it.

@laurentS Yup I think that's reasonable. I've got an outstanding PR for a context-managed. lifespan alternative to startup/shudown, and yes, that happens to include the app instance... https://github.com/encode/starlette/pull/799

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tomchristie picture tomchristie  路  3Comments

PeriGK picture PeriGK  路  5Comments

Serkan-devel picture Serkan-devel  路  5Comments

dgrahn picture dgrahn  路  4Comments

tomchristie picture tomchristie  路  5Comments