Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What is the best way to store globally accessible "heavy" objects? #374

Closed
devforfu opened this issue Feb 1, 2019 · 12 comments
Closed

What is the best way to store globally accessible "heavy" objects? #374

devforfu opened this issue Feb 1, 2019 · 12 comments

Comments

@devforfu
Copy link

devforfu commented Feb 1, 2019

In Flask we have a g global context to keep the global variables of the request. A kind of canonical example from the docs:

from flask import g

def get_db():
    if 'db' not in g:
        g.db = connect_to_database()

    return g.db

@app.teardown_appcontext
def teardown_db():
    db = g.pop('db', None)

    if db is not None:
        db.close()

So I wonder if there is something similar in starlette? I have a couple of very weighty objects which I don't want to initialize too often. These objects are "read-only" and are not modified during app's lifetime. What is the best way to handle cases like this?

@tomchristie
Copy link
Member

These objects are "read-only" and are not modified during app's lifetime.

Depends on if you're asking about "during app's lifetime" or "during the request/response cycle".

(Flask's g object is for storing data associated with the current request/response cycle.)

For stuff that has a request/response lifespan, you can store arbitrary stuff on the request "scope". request["foo"] = whatever().

For lifespan, use globals, with @app.on_event('startup')/@app.on_event('shutdown') to setup and teardown any network connections or other resources.

@tomchristie
Copy link
Member

Happy to dig further into any specific questions around this too. (And consider if we end up wanting any helpers for task-local context)

@devforfu
Copy link
Author

devforfu commented Feb 1, 2019

Makes sense! Yeah, it seems that I've merged two different questions into a single one :)

@tweenietomatoes
Copy link

everyone loves tom

@chris-erickson
Copy link

(Asking this here for the benefit of the community)

How would one achieve the "during the request/response cycle" variant, in a middleware? We have a weird configuration where our calling client POST's a weird json body that we can't change. I flag this by using a custom qs parameter that signals to our app that it's one of these weird ones and needs to restructure it to something a normal person would recognize (a typical request with sane json format passes through unscathed). In a flask app, I use a route decorator function to check all this, then set this transformed value in something like g.transformed_json. If that property exists, we use it instead of the normal body in the route.

It would be wonderful if I could modify the incoming request conditionally, in a middleware in Starlette. I haven't been able to locate the ideal way to do this, and request["foo"] = whatever() in my middleware throws TypeError: 'Request' object does not support item assignment. Perhaps I'm just confused and going about this entirely wrong.

@tomchristie
Copy link
Member

tomchristie commented Feb 8, 2019

@chris-erickson Okay - a hack for this right now would be accessing the private request._scope, so request._scope["foo"] = whatever()

I think we should probably expose a public interface for storing per-request information on the ASGI scope. I'll open a ticket for that.

@chris-erickson
Copy link

Great, that works as I would expect, and should suffice for now. A per-function decorator was something else I considered based on something else I saw. Would you say that way is not preferred? It's slightly more "obvious" in that you know exactly which routes will get this behavior but in my case, it's not really important.

@tomchristie
Copy link
Member

A per-function decorator was something else I considered

That'd work absolutely fine too, yup. Just depends if it's beahvior that you want to apply to all incoming requests, or to particular endpoints.

@ohmeow
Copy link

ohmeow commented Nov 8, 2019

For lifespan, use globals, with @app.on_event('startup')/@app.on_event('shutdown') to setup and teardown any network connections or other resources.

How do we do this?

I've tried to set a global variable outside of any functions in my main.py file that creates the starlette application and then tried to set it equal to something inside a startup function. But it still come back as None

@haydenflinner
Copy link

@ohmeow I know it's a little late, but the solution is to write

global myvariable
myvariable = 123

within the function.

@mrlevitas
Copy link

how would I use that global myvariable in a separate file?
I declare it in

@app.on_event('startup')
async def startup():
    global myvariable
    myvariable = 123

but I get: NameError: name 'myvariable' is not defined in separate file.

Do I need to import myvariable somehow?

@mrlevitas
Copy link

mrlevitas commented Sep 30, 2020

I believe I got it working using 'settings'.

settings.py

FEATURES_READY = False

main.py

from . import settings

def startup():
    settings.FEATURES_READY = True

app = Starlette(
    on_startup=[startup],
)

another_file.py

from . import settings

async def an_endpoint(request: Request) -> PlainTextResponse:
    if settings.FEATURES_READY:
        return PlainTextResponse("OK")

edit:

or better, set the variable via a function in the same module:
db.py

features_ready = None

def set_features():
    global features_ready
    features_ready = True

main.py

from .db import set_features

app = Starlette(
    on_startup=[set_features],
)

routes/another_file.py

from .. import db

async def an_endpoint(request: Request) -> PlainTextResponse:
    if db.features_ready:
        return PlainTextResponse("OK")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants