Connexion: supporting multiple file upload

Created on 4 Sep 2017  路  11Comments  路  Source: zalando/connexion

Description

When I define a swagger path like that one:

paths:
  /importer:
    post:
      operationId: api.importer.startImport
      description: |
        Start import process.
      consumes:
        - multipart/form-data
      parameters:
        - name: assets
          in: formData
          type: array
          items:
            type: string
            format: binary

I am not able to get the "array" of files defined under the name assets.
Only the first file which is send by the name assets...

def startImport(xml,importMeta,assets = None) -> object: print(assets)
Result is : <FileStorage: '25d' ('application/octet-stream')>

Expected behaviour

Array of Files should be accessible in the function.

Actual behaviour

Only one file is send to the function

Steps to reproduce

Sending 2 File with the key "assets"

Additional info:

Output of the commands:

Python 3.6.2
Connexion Version: 1.1.14

Most helpful comment

@nils250492 could you provide a full example for easier reproduction please?

All 11 comments

@nils250492 could you provide a full example for easier reproduction please?

I'm also seeing this issue. From werkzeug/datastructures.py (emphasis mine):

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.

In connexions/operations/abstract.py, we see this:

def _get_file_arguments(files, arguments, has_kwargs=False):
return {k: v for k, v in files.items() if k in arguments or has_kwargs}

which doesn't use the list methods, instead using the standard dict items() method. So this looks like a simple fix to implement.

I'm working around this problem for now by passing a list-of-lists as the file argument, but that is less than ideal; a more comprehensive solution would be appreciated.

I have the same issue as well.

requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                scannedImages:
                  type: array
                  items:
                    type: string
                    format: binary

And in my code when I try to access the files as below,

    for file in connexion.request.files['scannedImages']:
        file.save(os.path.join(os.getcwd(), 'data/%s' % file.name))

I get following error because neither the 'scannedImages` or it's items exists in the files object and it's empty

KeyError: 'scannedImages'

Appreciate any help on this.

@dinukadesilva what does your POSTed data look like? BTW this has nothing to do with the multi-file post issue above.

@skolpadda
In the post request I'm sending a list of files and it actually not coming.

Btw... Do you have a working example written with swagger 3 for multiple file upload.

@dinukadesilva specifically what does your POST look like? "sending a list of files" is too general, copy here the actual request along with all parameters you are using. Actually better still post your question to SO or similar, your question has nothing to do with this issue.

Hello everyone.

Could someone give me a full example of how to do multiple file uploads or verify what's wrong with the definition?

I've been trying to upload multiple images with a single request but didn't succeed so far. Any help would be extremely valuable. I'm attaching a piece of my code.

  /photos/{id}:
    post:
      tags:
        - "Photos"
      summary: "Add a new photo to land/property"
      description: "Link a new photo of the land and property"
      operationId: app.api.photos_api.upload_image
      responses:
        201:
          description: "OK"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Photo'
      parameters:
        - in: path
          name: id
          required: true
          description: "id of Land or Property that will be linked with the uploaded image"
          schema:
            type: integer
            maxItems: 1
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                fileName:
                  type: array
                  items:
                    type: string
                    format: binary

curl

curl -X POST "http://localhost:4000/api/photos" -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "fileName=[object File],[object File]" -F "id=31"

These [object File]'s are being transformed to string.

Any tip?

Thanks

I see that the issue still persists, and I am aware it is being worked on (and reverted and in work). If it helps here is how I face it:

swagger.yml

openapi: 3.0.2
info:
  title: Tasks Creater
  description: Quick sample for reproducing an issue with Connexion
  termsOfService: http://no/terms/
  contact:
    email: [email protected]
  license:
    name: Copy, change, use - this code is all yours
    url: http://this/code/is/all/yours
  version: 1.0.0
externalDocs:
  description: Got to get this fixed
  url: http://fix.please
servers:
- url: /api/v1
paths:
  /uploadfiles:
    post:
      tags:
      - uploadfiles
      summary: Upload multiple files
      operationId: controller.upload
      requestBody:
        description: Files that are uploaded
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                filename:
                  type: array
                  items:
                    type: string
                    format: binary
      responses:
        200:
          description: All OK

config.py

import connexion

app = connexion.FlaskApp(__name__, specification_dir='./')
app.add_api('swagger.yml')

app.py

from config import app

if __name__ == '__main__':
    app.run(port=8080)

controller.py

import os
from app import app
from flask import request, jsonify
from werkzeug.utils import secure_filename

ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])


def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


def upload():
    # check if the post request has the file part
    if 'file' not in request.files:
        resp = jsonify({'message': 'No file part in the request'})
        resp.status_code = 400
        return resp
    file = request.files['file']
    if file.filename == '':
        resp = jsonify({'message': 'No file selected for uploading'})
        resp.status_code = 400
        return resp
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
        resp = jsonify({'message': 'File successfully uploaded'})
        resp.status_code = 201
        return resp
    else:
        resp = jsonify({'message': 'Allowed file types are txt, pdf, png, jpg, jpeg, gif'})
        resp.status_code = 400
        return resp

My request was from http://0.0.0.0:8080/api/v1/ui, curl-equivalent of which turns to be. I picked 2 different pdf files to upload.

curl -X POST "http://0.0.0.0:8080/api/v1/uploadfiles" -H "accept: /" -H "Content-Type: multipart/form-data" -F "filename=[object File],[object File]"

Output

400 Undocumented | Error: BAD REQUEST
Response body
{
   "message": "No file part in the request" 
}
Response headers 
content-length: 42  
content-type: application/json  
date: Sat, 22 Feb 2020 10:49:55 GMT  
server: Werkzeug/1.0.0 Python/3.6.5

I've been trying to upload multiple images with a single request but didn't succeed so far. Any help would be extremely valuable. I'm attaching a piece of my code.

Same problem here.

I am having the same issue.... Looks almost exactly like the curl above

curl -X POST "http://0.0.0.0:8080/api/v1/uploadfiles" -H "accept: /" -H "Content-Type: multipart/form-data" -F "filename=[object File],[object File]"'
Was this page helpful?
0 / 5 - 0 ratings