Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to POST multiple FILES using Flask test client?

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?

like image 277
user2390183 Avatar asked Dec 10 '13 16:12

user2390183


2 Answers

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'].)

like image 136
Lukáš Lalinský Avatar answered Oct 07 '22 23:10

Lukáš Lalinský


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
like image 43
user2390183 Avatar answered Oct 07 '22 23:10

user2390183