Mahmoud Bassyouni
Mahmoud Bassyouni

Reputation: 31

How to make local variable inside Flask app to be controlled from another function?

I've been reading about this for several days now, from Flask documentation, old stack overflow posts, and google. I found many topics talking about flask.g and app.context(). I failed to understand and I feel completely ignorant and gave up understanding what is going on. so I just need the simplest way to make a local variable inside flask app function to be controlled and accessible from another flask function.

briefly, I have two functions in my flask app, one to process data (long processing), and the other to kill the processing by terminating the object. killing function is invoked by url "mywebsite/kill". I tried making object global by adding "global" to variable x ( global x ) and it worked fine except that object became shared between all Flask requests and that broke up multi threading feature of Flask.

So I need to make x accessible to the kill() function and to avoid the use of normal global at the same time.

Thanks to help me with pseudo code bellow, and don't copy paste to the famous Hieroglyphs flask documentation.

pseudo example:

app = Flask(__name__)

@app.route("/process")
def process():
    x= process_obj()    #Long Processing
    return render_template('result.html')

@app.route("/kill")
def kill():
    x.close()

Upvotes: 0

Views: 2168

Answers (1)

Arunmozhi
Arunmozhi

Reputation: 964

Before getting into Flask and sharing, let us see what you want to do from a purely programming side of things. You want to share a variable between two functions.

There are multiple ways to do that. One of them is to use globals, which you have tried and discovered is not the solution. Some other ways that come to my mind are:

  1. Encapsulation of the related functions using a class
  2. Passing the shared information as an argument to the other function
  3. Maintaining a registrar to store and retrieve at will

1. Encapsulation using classes

As Flask is designed to be simple and uses functions as first class citizens to create views, encapsulation using classes would defeat the purpose. The right alternative is to use Flask Blueprints to group the functions that would share the common data. This would mean using globals in the blueprint module and we would face the same issue as with globals

2. Passing the value as an argument

This is a straight forward way to share values in a regular situation. In this case, with each function bound to a URL as a view, this might or might not work depending on the traceability of your process object x. Suppose that you have a function get_process(process_id) then you could do something like this:

@app.route("/process")
def process():
    x = process_obj()
    return render_template('result.html', x=x)

In you template render the link as:

<a href="{% url_for('kill', id=x.id) %}">Kill Process</a>

Then get the value in your kill view like this:

@app.route("/kill")
def kill():
    id = request.args.get("id", None)
    if not id:
        abort(400)
    x = get_process(id)
    x.close()

Note: If this process you are talking about in the pseudo code is managed by Task manager like Celery or Python-RQ, then the above solution should work as they come with ways to retrieve jobs based on their ID.

3. Maintaining a Registrar

In case you are not using a task manager and doing something like multiprocessing or some form of aysnc background process yourself, then that system needs to have a registrar that will allow you to trace and retrieve the processes you are starting.

I would suggest not doing this and opt for a well tested library like Celery, Python RQ, Huey..etc., to do that for you.

Update: To create a registrar of the resources, create a class with a singleton pattern that can be accessed across the application. Since you are using Flask, I would suggest that this is created as a Flask extension, so you can share it along with the application and use the application context when required.

The following psuedo code should get you going:

# import your necessary ssh library objects if required
from flask import current_app, _app_ctx_stack


class MyObjectRegistrar(object):
    def __init__(self, app=None):
        self.app = app
        self.my_objects = {}
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        # load any config into your registrar from the app config if necessary

    def add(self, id, obj):
        self.my_objects[id] = obj

    def get(self, id):
        return self.my_objects.get(id, None)

    def remove(self, id):
        del self.my_objects[id]

With the above registrar loaded as a Flask extension:

from flask import Flask
from my_registrar import MyObjectRegistrar

app = Flask(__name__)
registrar = MyObjectRegistrar(app)

This will give you the registrar object that you can use in your views like:

@app.route("/process")
def process():
    obj = process_obj()
    registrar.add("some-unique-id", obj)
    return render_template("results.html", id="some-unique-id")

@app.route("/kill")
def kill():
    id = request.args.get("id")
    x = registrar.get(id)
    x.close()
    registrar.remove(id)
    return render_template("killed.html")

Note: It is not mandatory that it has to be a Flask extension. If you see no use in having the Flask App object around, then you can just do a singleton class in a regular Python module.

Upvotes: 2

Related Questions