Batch API requests with flask

I am creating a REST API with a flask and one thing I would like to include is the ability to batch request resources, similar to how the Graph Graph API works:

curl \
    -F 'access_token=…' \
    -F 'batch=[{"method":"GET", "relative_url":"me"},{"method":"GET", "relative_url":"me/friends?limit=50"}]' \
    https://graph.facebook.com

      

Which then returns an array with each request resolved by its status code and result:

[
    { "code": 200, 
      "headers":[
          { "name": "Content-Type", 
            "value": "text/javascript; charset=UTF-8" }
      ],
      "body": "{\"id\":\"…\"}"},
    { "code": 200,
      "headers":[
          { "name":"Content-Type", 
            "value":"text/javascript; charset=UTF-8"}
      ],
      "body":"{\"data\": [{…}]}}
]

      

I was able to replicate this in flask by just looping over requests and calling urlopen against my own application. It seems really inefficient and I must think there is a better way. Is there an easier and / or better way to make requests against my own application from a request handler?

+3


source to share


2 answers


Since you need to return headers, my recommendation is that you send these batch requests to yourself (i.e., send requests to localhost

) so that the responses match those you get when making individual calls.

Note that when your API receives a batch request, you will need at least one idle worker to accept these indirect requests, while the first worker is blocking and waiting. Therefore, you will need to have at least two workers. Even so, you run the risk of deadlocking if two batch requests appear at the same time and take two of your workers. So you actually need to have as many workers as the batch requests you expect to receive at the same time plus at least one more to handle the indirect requests.



Looking at it the other way, you'll want to run as many of these indirect queries in parallel as possible, because if they end up running one after the other, the advantage of using batch queries is lost. So you also need to have enough workers for parallelism.

To be honest, I don't see this as a great opportunity. In most client-side languages, it is quite easy to execute multiple requests in parallel, so you don't need to provide this server-side function. Especially easy if you use Javascript, but also easy in Python, Ruby, etc.

+2


source


You can only use Flask to make the individual requests provided in the package as follows.

Batch request

[
    {
        "method" : <string:method>,
        "path"   : <string:path>,
        "body"   : <string:body>
    },
    {
        "method" : <string:method>,
        "path"   : <string:path>,
        "body"   : <string:body>
    }
]

      

Burst response



[
    {
        "status"   : <int:status_code>,
        "response" : <string:response>
    },
    {
        "status"   : <int:status_code>,
        "response" : <string:response>
    }
]

      

Sample code

def _read_response(response):
    output = StringIO.StringIO()
    try:
        for line in response.response:
            output.write(line)

        return output.getvalue()

    finally:
        output.close()

@app.route('/batch', methods=['POST'])
def batch(username):
    """
    Execute multiple requests, submitted as a batch.

    :statuscode 207: Multi status
    """
    try:
        requests = json.loads(request.data)
    except ValueError as e:
        abort(400)

    responses = []

    for index, req in enumerate(requests):
        method = req['method']
        path = req['path']
        body = req.get('body', None)

        with app.app_context():
            with app.test_request_context(path, method=method, data=body):
                try:
                    # Can modify flask.g here without affecting flask.g of the root request for the batch

                    # Pre process Request
                    rv = app.preprocess_request()

                    if rv is None:
                        # Main Dispatch
                        rv = app.dispatch_request()

                except Exception as e:
                    rv = app.handle_user_exception(e)

                response = app.make_response(rv)

                # Post process Request
                response = app.process_response(response)

        # Response is a Flask response object.
        # _read_response(response) reads response.response and returns a string. If your endpoints return JSON object,
        # this string would be the response as a JSON string.
        responses.append({
            "status": response.status_code,
            "response": _read_response(response)
        })

    return make_response(json.dumps(responses), 207, HEADERS)

      

+1


source







All Articles