How to unmarshall incoming request payload as method/function arguments based on content type in Python flask or bottle frameworks

Question:

Is it possible to unmarshall incoming request payload as method/function arguments in bottle or flask? If so, how?

I would like to send say following as request payload of a POST/PUT request,

{ 'foo': [ 'bar1', 'bar2'], 'spam': 2 }

and have this used in pseudocode as,

@route('/cheeseshop/<id>', method='PUT')
def cheeseShop(foo, spam):
    pass

Can this be done automatically looking at the content type in either of these frameworks?

Asked By: opensourcegeek

||

Answers:

There are some caveats in doing so for code readability but a possible solution is as follow.

Define the serialization method. If you need to work with different clients I suggest JSON.

Create a decorator and put it between your function and the route

@route(...)
@expandargs
def foo(id, bar, baz):
    ...

In the decorator use request.json() (automatically decodes the payload if it’s JSON) to expand the args and then you’ll call the wrapped function with original args and the new, say, **expandedargs (note the double asterisks to explode the keywords).

Problems arise when mixing positional and keyword args.

Answered By: Paolo Casciello

This is of cause just sketching out Paolos answer, but to aid anyone else looking for this, here’s an example of an unmarshalling decorator that accomplishes the goal.

from functools import wraps
def unmarshal_payload(view):
    @wraps(view)
    def unmashalled_view(*args, **kwargs):
        return view(*args, **request.get_json(), **kwargs)
    return unmashalled_view 

Which would then be used as:

@app.route(f'/<int:id>/', methods=['PUT'])
@unmarshal_payload
def view(id, foo, bar):
    print(id, foo, bar)
    return 'Success'

Then depending on how you want to handle things like the payload containing {'id': 'something'} you can change it. As it is in this naive implimentation, Flask will return an Internal Server Error, as python throws a TypeError due to a function recieving multiple keyword arguments for the same keyword. Also if you give have parameters that are not named in the view, you get a Type Error with Unexpected keyword.

So a slightly more lenient definition would be:

from functools import wraps
def unmarshal_payload(view):
    @wraps(view)
    def unmashalled_view(*args, **kwargs):
        return view(*args, **kwargs, **{k:v for k,v in request.get_json().items() if k not in kwargs})
    return unmashalled_view 

@app.route(f'/<int:id>/', methods=['PUT'])
@unmarshal_payload
def view(id, foo, bar, **kwargs):
    print(id, foo, bar)
    return 'Success'
Answered By: Vincent
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.