1

I have an api with 2 endpoints, one is a simple post receiving a json and the other is an endpoint which calls the 1st multiple times depending on the length of a list of jsons and save the return to a list.

First method

@app.route('/getAudience', methods=['POST', 'OPTIONS'])
def get_audience(audience_=None):
    try:
        if audience_:
            audience = audience_
        else:
            audience = request.get_json()
    except (BadRequest, ValueError):
        return make_response(jsonify(exception_response), 500)

    return get_audience_response(audience, exception_response)

Second method

@app.route('/getMultipleAudience', methods=['POST', 'OPTIONS'])
def get_multiple_audience():
    try:
        audiences = request.json
    except (BadRequest, ValueError):
        return make_response(jsonify(exception_response), 500)

    response = []
    for audience in audiences:
        new_resp = json.loads(get_audience(audience).data)
        response.append(new_resp)

    return make_response(jsonify(response))

I wanted to call the first method starting a thread per object in the list of the second method so I tried this:

def get_multiple_audience():
    with app.app_context():
        try:
            audiences = request.get_json()
        except (BadRequest, ValueError):
            return make_response(jsonify(exception_response), 500)

        for audience in audiences:
            thread = Thread(target=get_audience, args=audience)
            thread.start()

        thread.join()
        return make_response(jsonify(response))

And got this error:

Exception in thread Thread-6:
Traceback (most recent call last):
  File "C:\Python27\lib\threading.py", line 801, in __bootstrap_inner
    self.run()
  File "C:\Python27\lib\threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "C:\Python27\lib\site-packages\flask_cors\decorator.py", line 123, in wrapped_function
    options = get_cors_options(current_app, _options)
  File "C:\Python27\lib\site-packages\flask_cors\core.py", line 286, in get_cors_options
    options.update(get_app_kwarg_dict(appInstance))
  File "C:\Python27\lib\site-packages\flask_cors\core.py", line 299, in get_app_kwarg_dict
    app_config = getattr(app, 'config', {})
  File "C:\Python27\lib\site-packages\werkzeug\local.py", line 347, in __getattr__
    return getattr(self._get_current_object(), name)
  File "C:\Python27\lib\site-packages\werkzeug\local.py", line 306, in _get_current_object
    return self.__local()
  File "C:\Python27\lib\site-packages\flask\globals.py", line 51, in _find_app
    raise RuntimeError(_app_ctx_err_msg)
RuntimeError: Working outside of application context.

So then I tried to modify the first method like this:

@app.route('/getAudience', methods=['POST', 'OPTIONS'])
def get_audience(audience_=None):
    with app.app_context():
        try:
        ...

And got the same error. Can anyone give me a hint, advice, best practice or solution?

1
  • If you want to test your web service, the best way is to create a separate/independent application using Requests. Commented Aug 10, 2017 at 16:52

1 Answer 1

1

There are multiple problems here. Firstly, here:

for audience in audiences:
    thread = Thread(target=get_audience, args=audience)
    thread.start()

thread.join()

You are only waiting for the last thread to complete. You should have a list of all the threads, and wait for all of them to complete.

threads = []
for audience in audiences:
    thread = Thread(target=get_audience, args=audience)
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

Second problem is that you are returning a single response which isn't even set anywhere. But that's not how multi-threading works. You will have multiple results from all the threads and you will have to keep track of them. So you can create a results array to hold the answers for each thread's return value. Here I will make a simple function sum as an example.

results = []
threads = []

def sum(a, b):
    results.append(a + b)

@app.route("/test")
def test():
    with app.app_context():
        for i in range(5):
            t = Thread(target=sum, args=(1, 2))
            threads.append(t)
            t.start()

        for t in threads:
            t.join()

        return jsonify(results)

This will happily work, and it will return the result of all the calls to sum() function.

Now if I change sum to:

@app.route("/mysum/a/b")
def sum(a, b):
    results.append(a + b)
    return jsonify(a + b)

I will get a similar error as the one you were getting earlier: namely, RuntimeError: Working outside of request context., even thought the return value would still be correct: [3, 3, 3, 3, 3]. What's happening here is that your sum function is now trying to return a flask response, but it is residing inside its own temporary thread and doesn't have access to any of flask's internal contexts. What you should do is to never return a value inside a temporary worker thread, but have a pool to store them for future reference.

But this doesn't mean you can't have a /mysum route. Indeed, you can, but the logic has to be separated. To put it all together:

results = []
threads = []

def sum(a, b):
    return a + b

def sum_worker(a, b):
    results.append(sum(a, b))

@app.route("/mysum/a/b")
def mysum(a, b):
    return jsonify(sum(a, b))

@app.route("/test")
def test():
    with app.app_context():
        for i in range(5):
            t = Thread(target=sum_worker, args=(1, 2))
            threads.append(t)
            t.start()

        for t in threads:
            t.join()

        return jsonify(results)

Note that this code is very crude and is only for demonstration purposes. I can't recommend making global variables throughout your app, so some cleanup is required.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.