I am trying to pass comma separated query parameters to a Flask endpoint.
An example URI would be:
localhost:3031/someresource#?status=1001,1002,1003
Looking at the return of request.args or request.args.getlist('status') I see that I only get a string.
ipdb> pp request.args
ImmutableMultiDict([('status', '1001,1002,1003')])
ipdb> request.args.getlist('status')
['1001,1002,1003']
I know I can split the string by comma but that feels hacky. Is there a more idiomatic way to handle this in Flask? Or are my query params wrong format?
Solution
Since Flask does not directly support comma separated query params, I put this in in my base controller to support comma-separated or duplicate query params on all endpoints.
request_data = {}
params = request.args.getlist('status') or request.form.getlist('status')
if len(params) == 1 and ',' in params[0]:
request_data['status'] = comma_separated_params_to_list(params[0])})
else:
request_data['status'] = params
def comma_separated_params_to_list(param):
result = []
for val in param.split(','):
if val:
result.append(val)
return result
The flask variant getlist expects multiple keys:
from flask import Flask, request
app = Flask(__name__)
#app.route('/')
def status():
first_status = request.args.get("status")
statuses = request.args.getlist("status")
return "First Status: '{}'\nAll Statuses: '{}'".format(first_status, statuses)
❯ curl "http://localhost:5000?status=5&status=7"
First Status: '5'
All Statuses: '['5', '7']'
There's no standard for this, how multiple GET args are parsed/passed depends on which language/framework you're using; flask is built on werkzeug so it allows this style, but you'll have to look it up if you switch away from flask.
As an aside, Its not uncommon REST API design to have commas to pass multiple values for the same key - makes it easier for the user. You're parsing GET args anyway, parsing a resulting string is not that much more hacky. You can choose to raise a 400 HTTP error if their string with commas isn't well formatted.
Some other languages (notably PHP) support 'array' syntax, so that is used sometimes:
/request?status[]=1000&status[]=1001&status[]=1002
This is what you might want here:
request.args.to_dict(flat=False)
flat is True by default, so by setting it to False, you allow it to return a dict with values inside a list when there's more than one.
According to to_dict documentation:
to_dict(flat=True)
Return the contents as regular dict. If flat is True
the returned dict will only have the first item present, if flat is False
all values will be returned as lists.
Related
I have a list named linebline that contains around 99 lists within it (sublists). Within these sublists are dictionaries. I am interested in these dictionaries, as each sublist has a key named 'title' that I want a client/user to be able to query using a URL.
type(linebline) outputs list
type(linebline[0]) outputs dict
So far I have the following:
from flask import Flask, request, Response
app3 = Flask(__name__)
app3.route('/', methods = ['GET'])
def grab_title():
given_title = requests.args.get('title')
book_info = [obj for obj in linebline if obj['title'] == given_title] #.to_html(header="true", table_id="table")
return(book_info)
#return Response(city_inCom, mimetype = 'application/json')
app3.run(host = 'localhost', port = 5003)
An example of the query from a client would be:"http://localhost:5003/?title=New%20iPad%20Air%20may%20come%20with%20USB-C%20not%20Lightning%20Port%27"
I am currently getting:
Not Found
The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
I am not sure if I am addressing the 'title' key correctly OR am I not returning the right thing.
Is there a way to create an api with Flask that just returns a json, regardless of input params, the method should be get tho.
I want this because the api will be called with multiple headers but none interest me, i just want to return a certain json.
I take it you mean something like the following:
from flask import jsonify
#app.route('/send', methods=['GET'])
def returns_json():
data = {
'id': 1,
'name': 'Linus',
'admin': True
}
return jsonify(data)
The above endpoint only accepts GET methods and you could use Flask's jsonify method to serialize simple data types. If you have more complex data types, you might need to use something like marshmallow.
Just define your json and return it using the jsonify() function.
#app.route('/api')
def my_api():
# json_response can either be a list or dictionary
return jsonify(json_response)
I would know if it's possible to get request.args from different path levels.
Currently i can use request.args for only one level :
my_ip:my_port/modules/?name=git
or
my_ip:my_port/modules/?id=0
#module_blueprint.route('/', methods=['GET'])
def get_modules():
if len(request.args) == 0:
modules = Module.query.all()
return jsonify(modules=list_to_json(modules))
try:
module = Module.query.filter_by(**request.args.to_dict()).first()
except InvalidRequestError as i:
return json_response(400, "InvalidRequestError : {}".format(i.args))
if module is None:
return json_response(204, "")
return jsonify(module=module.serialize)
But in my Module model I have actions and I would like to get actions in the same way :
my_ip:my_port/modules/?name=git/actions/?id=0
But I don't know how to do this.
If I try with
#module_blueprint.route('/actions', methods=['GET'])
request.args just keep args after the action path
my_ip:my_port/modules/?name=git/actions
request.args is empty for this path
Thanks for helping.
Note: Request params are always appended at the end of the request url. Everything after a ? is considered URI Params. Thus, a url like
my_ip:my_port/modules/?name=git/actions/?id=0
is not possible since it violates the above mentioned guideline.
Solution if there is going to be only 1 param per level:
In this case, passing the info as params does not provide any advantage. Thus, the following would be a better way of doing things:
For fetching module by name:
#module_blueprint.route('/<module_name>', methods=['GET'])
For fetching actions for that module:
#module_blueprint.route('/<module_name>/action/<int:action_id>', methods=['GET'])
Solution if there are going to be more than 1 filter param per level:
In this case, it makes more sense to use request params.
For fetching module by name:
#module_blueprint.route('/', methods=['GET'])
For fetching actions for that module:
#module_blueprint.route('/actions', methods=['GET'])
I'm trying to do API-request and I need API-key to different view. I'm trying to use session variable, but the key seems to be in some other format than trying to use variable from Sqlite database. API requests work with the key from database, but not with session variable
How I get API-key from database and from session:
key_session = request.session['key']
key_db = APIkey.objects.values_list('key', flat=True).get(pk=2)
Both of these return same values, when I print them. Key example:
3h3asdh-asdasd:oisf87sdf87a5df76asdf83jhjhasgd8
I'm using base64.encodestring function when trying to do authentication to API-service with my key:
query = request.GET.get('query')
url = urllib2.Request('https://api.someapiwebsite.com',
None, headers={'Content-Type':'application/json'})
base64string = base64.encodestring('%s' % (key_session)).replace('\n', '')
If I print base64string with session variable (key_session), I get:
MmoihjsdasdoihhaG5tbjpuq9876eq9asd98a7Nmd3dWYzN2JmbWZ2aW1nMGVw==
If I print base64string with session variable (key_db), only difference is the two last characters == is now 'IC', and I think that's why the authentication to API service is failing:
MmoihjsdasdoihhaG5tbjpuq9876eq9asd98a7Nmd3dWYzN2JmbWZ2aW1nMGVwIC
What is making this difference in the base64 encoded string?
Edit:
I can see difference when using print repr():
print repr(key_db)
3h3asdh-asdasd:oisf87sdf87a5df76asdf83jhjhasgd8
print repr(key_session)
3h3asdh-asdasd:oisf87sdf87a5df76asdf83jhjhasgd8\x02\x02
One of the strings probably contains some trailing characters that print isn't showing. If you use repr then you should be able to see what the difference is.
print(repr(key_session))
print(repr(key_db))
You can then strip any characters as necessary before encoding the string, for example:
key_session = key_session.rstrip('\x02')
In order to test a Flask application, I got a flask test client POSTing request with files as attachment
def make_tst_client_service_call1(service_path, method, **kwargs):
_content_type = kwargs.get('content-type','multipart/form-data')
with app.test_client() as client:
return client.open(service_path, method=method,
content_type=_content_type, buffered=True,
follow_redirects=True,**kwargs)
def _publish_a_model(model_name, pom_env):
service_url = u'/publish/'
scc.data['modelname'] = model_name
scc.data['username'] = "BDD Script"
scc.data['instance'] = "BDD Stub Simulation"
scc.data['timestamp'] = datetime.now().strftime('%d-%m-%YT%H:%M')
scc.data['file'] = (open(file_path, 'rb'),file_name)
scc.response = make_tst_client_service_call1(service_url, method, data=scc.data)
Flask Server end point code which handles the above POST request is something like this
#app.route("/publish/", methods=['GET', 'POST'])
def publish():
if request.method == 'POST':
LOG.debug("Publish POST Service is called...")
upload_files = request.files.getlist("file[]")
print "Files :\n",request.files
print "Upload Files:\n",upload_files
return render_response_template()
I get this Output
Files:
ImmutableMultiDict([('file', <FileStorage: u'Single_XML.xml' ('application/xml')>)])
Upload Files:
[]
If I change
scc.data['file'] = (open(file_path, 'rb'),file_name)
into (thinking that it would handle multiple files)
scc.data['file'] = [(open(file_path, 'rb'),file_name),(open(file_path, 'rb'),file_name1)]
I still get similar Output:
Files:
ImmutableMultiDict([('file', <FileStorage: u'Single_XML.xml' ('application/xml')>), ('file', <FileStorage: u'Second_XML.xml' ('application/xml')>)])
Upload Files:
[]
Question:
Why request.files.getlist("file[]") is returning an empty list?
How can I post multiple files using flask test client, so that it can be retrieved using request.files.getlist("file[]") at flask server side ?
Note:
I would like to have flask client I dont want curl or any other client based solutions.
I dont want to post single file in multiple requests
Thanks
Referred these links already:
Flask and Werkzeug: Testing a post request with custom headers
Python - What type is flask.request.files.stream supposed to be?
You send the files as the parameter named file, so you can't look them up with the name file[]. If you want to get all the files named file as a list, you should use this:
upload_files = request.files.getlist("file")
On the other hand, if you really want to read them from file[], then you need to send them like that:
scc.data['file[]'] = # ...
(The file[] syntax is from PHP and it's used only on the client side. When you send the parameters named like that to the server, you still access them using $_FILES['file'].)
Lukas already addressed this,just providing these info as it may help someone
Werkzeug client is doing some clever stuff by storing requests data in MultiDict
#native_itermethods(['keys', 'values', 'items', 'lists', 'listvalues'])
class MultiDict(TypeConversionDict):
"""A :class:`MultiDict` is a dictionary subclass customized to deal with
multiple values for the same key which is for example used by the parsing
functions in the wrappers. This is necessary because some HTML form
elements pass multiple values for the same key.
:class:`MultiDict` implements all standard dictionary methods.
Internally, it saves all values for a key as a list, but the standard dict
access methods will only return the first value for a key. If you want to
gain access to the other values, too, you have to use the `list` methods as
explained below.
getList call looks for a given key in the "requests" dictionary. If the key doesn't exist, it returns empty list.
def getlist(self, key, type=None):
"""Return the list of items for a given key. If that key is not in the
`MultiDict`, the return value will be an empty list. Just as `get`
`getlist` accepts a `type` parameter. All items will be converted
with the callable defined there.
:param key: The key to be looked up.
:param type: A callable that is used to cast the value in the
:class:`MultiDict`. If a :exc:`ValueError` is raised
by this callable the value will be removed from the list.
:return: a :class:`list` of all the values for the key.
"""
try:
rv = dict.__getitem__(self, key)
except KeyError:
return []
if type is None:
return list(rv)
result = []
for item in rv:
try:
result.append(type(item))
except ValueError:
pass
return result