91

I am trying to save a cache dictionary in my flask application.

As far as I understand it, the Application Context, in particular the flask.g object should be used for this.

Setup:

import flask as f

app = f.Flask(__name__)

Now if I do:

with app.app_context():
    f.g.foo = "bar"
    print f.g.foo

It prints bar.

Continuing with the following:

with app.app_context():
    print f.g.foo

AttributeError: '_AppCtxGlobals' object has no attribute 'foo'

I don’t understand it and the docs are not helping at all. If I read them correctly the state should have been preserved.

Another idea I had was to simply use module-wide variables:

cache = {}

def some_function():
    cache['foo'] = "bar"

But it seems like these get reset with every request.

How to do this correctly?

Edit: Flask 10.1

Carcigenicate
  • 43,494
  • 9
  • 68
  • 117
Profpatsch
  • 4,918
  • 5
  • 27
  • 32
  • 13
    No, thanks for the link, but it’s such a simple task that I’d like to keep the dependenies low atm. – Profpatsch Oct 09 '13 at 16:24
  • I think I went with a browser cookie in the end. But you are of course welcome to post a solution once you find one. – Profpatsch Jan 03 '14 at 08:23
  • Session might be solution, but session is kept per user so it has it's limitations. Other option is memcache. It's simple key->value storage that is easy to configure and is shared among all threads and processes. Sometimes however (like in my case) it's a bit too much complication, so I am thinking to just use dictionary in global scope. Risk is data integrity (not a factor for me) but same dict will be shared between some processes. – Drachenfels Mar 30 '16 at 12:49

3 Answers3

85

Based on your question, I think you're confused about the definition of "global".

In a stock Flask setup, you have a Flask server with multiple threads and potentially multiple processes handling requests. Suppose you had a stock global variable like "itemlist = []", and you wanted to keep adding to it in every request - say, every time someone made a POST request to an endpoint. This is totally possible in theory and practice. It's also a really bad idea.

The problem is that you can't easily control which threads and processes "win" - the list could up in a really wonky order, or get corrupted entirely. So now you need to talk about locks, mutexs, and other primitives. This is hard and annoying.

You should keep the webserver itself as stateless as possible. Each request should be totally independent and not share any state in the server. Instead, use a database or caching layer which will handle the state for you. This seems more complicated but is actually simpler in practice. Check out SQLite for example ; it's pretty simple.

To address the 'flask.g' object, that is a global object on a per request basis.

http://flask.pocoo.org/docs/api/#flask.g

It's "wiped clean" between requests and cannot be used to share state between them.

mallyvai
  • 1,698
  • 14
  • 20
  • 1
    I understand the database approach, but not familiar with caching layers. Could you recommend some caching layer libraries/frameworks? – max Sep 15 '16 at 01:26
  • It seems that in Flask 0.11 `flask.g` is tied to application context. Does it changes anything? – omikron Dec 14 '16 at 11:04
  • 1
    I love SQlite, but i don't think that you want to use it for concurrent usage , in apps that have to make writes. Writes locks the db. I really think that somewhat like redis-server will make the job, specially for listings. Another (maybe stupid) approach could be to write data separately into a folder. So, each thread will write it's own file, but when u retrieve data u will read all files. This way you always get all data while non blocking new writes. – m3nda Apr 03 '17 at 15:18
  • 5
    This is no longer true. Starting with flask.10 flask.g is tied to the application context and not the request context. http://flask.pocoo.org/docs/0.12/api/#flask.g – minhaz1 Dec 06 '17 at 23:10
  • 67
    Had to -1 this answer. There are instances when you want global objects that are not refreshed between the request, for example, I want to load path of some files in memory and **not** modify it at all. If I do want to modify it I will restart the server. This answer doesn't help. – Amit Tripathi Jan 13 '18 at 15:52
  • Design your application to not use threads, only processes. You will be happier. – mvallebr Aug 22 '18 at 13:44
  • 29
    Yeah, i'm looking at loading a large pre-trained model for inference and the load time is significant so can't be stateless. – CpILL Jan 01 '19 at 01:53
  • 2
    @CpILL Any luck yet? Same use case (unless your model is dynamic?). Documentation pretty incoherent and most SO questions on the topic get shut down as being dups.. – jtlz2 Apr 01 '19 at 18:36
  • @jtlz2 I'm thinking JohanGov's solution will work as I'm not writing to anything so its OK to share a global variable – CpILL Apr 29 '19 at 02:48
  • I think you should highlight **Instead, use a database or caching layer which will handle the state for you.** – Martin Thoma Jun 11 '19 at 13:52
22

I've done something similar to your "module-wide variables" idea that I use in a flask server that I use to integrate two pieces of software where I know I will only ever have one simultaneous "user" (being the sender software).

My app.py looks like this:

from flask import Flask
from flask.json import jsonify
app = Flask(__name__)

cache = {}

@app.route("/create")
def create():
    cache['foo'] = 0
    return jsonify(cache['foo'])

@app.route("/increment")
def increment():
    cache['foo'] = cache['foo'] + 1
    return jsonify(cache['foo'])

@app.route("/read")
def read():
    return jsonify(cache['foo'])

if __name__ == '__main__':
    app.run()

You can test it like this:

import requests

print(requests.get('http://127.0.0.1:5000/create').json())
print(requests.get('http://127.0.0.1:5000/increment').json())
print(requests.get('http://127.0.0.1:5000/increment').json())
print(requests.get('http://127.0.0.1:5000/read').json())
print(requests.get('http://127.0.0.1:5000/increment').json())
print(requests.get('http://127.0.0.1:5000/create').json())
print(requests.get('http://127.0.0.1:5000/read').json())

Outputs:

0
1
2
2
3
0
0

Use with caution as I expect this to not behave in a proper multi user web server environment.

Johan Gov
  • 1,262
  • 1
  • 13
  • 26
  • 21
    This will work in situations where you have a SINGLE uwsgi worker globablly. You also have to worry about multiple access to the variable, so you may have to implement locks or similar depending on the use. It is another tool in the bag though. – Paul Becotte Sep 27 '16 at 00:59
  • 7
    Watch out for this type of test... The test Flask server is single-threaded and it's a single process, you will not be able to find multi-threading-related issues with this test. – Konrad Feb 11 '19 at 18:51
  • I'm new to Flask, is there a reason why this dictionary method persists between requests but creating a variable such as an INT doesn't? – Major Major Feb 05 '20 at 13:45
  • @MajorMajor there is no difference to declaring an int and a dictionary in regard to its persistence. If they persist or not depends on where they are declared. If you've got further questions regarding this I encourage you to ask a new question. – Johan Gov Feb 05 '20 at 19:39
  • I found that `str` don't persist. Be aware!! – Mattia Ducci Mar 05 '20 at 16:06
  • Ya this is only for SINGLE UWSGI worker. What's the option when we have multiple workers? – RaiBnod May 06 '21 at 08:01
12

This line

with app.app_context():
    f.g.foo = "bar"

Since you are using the "with" keyword, once this loop is executed, it calls the __exit__ method of the AppContext class. See this. So the 'foo' is popped out once done. Thats why you don't have it available again. You can instead try:

ctx = app.app_context()
f.g.foo = 'bar'
ctx.push()

Until you call the following, g.foo should be available

ctx.pop()

I am howver not sure if you want to use this for the purpose of caching.

codegeek
  • 32,236
  • 12
  • 63
  • 63
  • 6
    What should I use then? I thought this was the means to store global values in an app? – Profpatsch Oct 10 '13 at 18:40
  • 3
    The application context is created and destroyed as necessary. It never moves between threads and it will not be shared between requests. (http://flask.pocoo.org/docs/appcontext/) – Shankar ARUL Aug 16 '14 at 00:46