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?
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.
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'
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?
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.
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'