Skip to content

[Bug]: Unable to send files through multipart/form in Flask when integrated with Openapi-Core #630

Closed
@rohan-97

Description

@rohan-97

Actual Behavior

Hello,

I am trying to Implement an API,
The API Takes a file as input in a REST API using multipart/form

I am using Flask to implement the REST API and following is the flask script

#!/usr/bin/python3
"""Test server."""

import os
import random
from flask import Flask, request, jsonify
from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator
from openapi_core import Spec

SPEC = "file_reader.yaml"
openapi = FlaskOpenAPIViewDecorator.from_spec(Spec.from_file_path(SPEC))

app = Flask(__name__)

def __save_file_locally(file_object:object) -> str:
    if file_object.filename:
        filepath = os.path.join("/tmp/", f"{file_object.filename}.{random.randint(1,100)}")
        if os.path.exists(filepath):
            os.unlink(filepath)
        file_object.save(filepath)
        return filepath
    return None

@app.route("/test", methods=["POST"])
@openapi
def read_permission():
    """Test function"""
    # print(f"request OpenAPI dir : {request.openapi.body.items()}")
    __save_file_locally(request.files['inputFile'])
    return jsonify({"key": "Value"})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=567, debug=True)

    # curl -X POST -H  "Content-type: application/json" --data '{"flag":"ttF"}' http://localhost:567/test

and following is the file_reader.yaml file

openapi: '3.0.2'
info:
  title: Test Title
  version: '1.0'
servers:
  - url: http://localhost:567/
paths:
  /test:
    post:
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                inputFile:
                  type: string 
                  format: binary
      responses:
        200:
          description: Sample response
          content:
            application/json:
              schema:
                type: object
                properties:
                  key:
                    type: string
                    minLength: 6
                    maxLength: 20

# curl -X POST -H  "Content-type: multipart/form-data" -F "file=@file_reader.py" http://localhost:567/test

However when I hit the API I get following validation error

root@ip-10-31-1-221:~/openapi_core_POC# curl -X POST -H  "Content-type: multipart/form-data" -F "file=@file_reader.py" http://localhost:567/test | python3 -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2884  100  1588  100  1296   258k   210k --:--:-- --:--:-- --:--:--  469k
{
    "errors": [
        {
            "class": "<class 'openapi_core.deserializing.media_types.exceptions.MediaTypeDeserializeError'>",
            "status": 400,
            "title": "Failed to deserialize value with multipart/form-data mimetype: --------------------------2c4bf22387dcbcbc\r\nContent-Disposition: form-data; name=\"file\"; filename=\"file_reader.py\"\r\nContent-Type: application/octet-stream\r\n\r\n#!/usr/bin/python3\n\"\"\"Test server.\"\"\"\n\nimport os\nimport random\nfrom flask import Flask, request, jsonify\nfrom openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator\nfrom openapi_core import Spec\n\nSPEC = \"file_reader.yaml\"\nopenapi = FlaskOpenAPIViewDecorator.from_spec(Spec.from_file_path(SPEC))\n\napp = Flask(__name__)\n\ndef __save_file_locally(file_object:object) -> str:\n    if file_object.filename:\n        filepath = os.path.join(\"/tmp/\", f\"{file_object.filename}.{random.randint(1,100)}\")\n        if os.path.exists(filepath):\n            os.unlink(filepath)\n        file_object.save(filepath)\n        return filepath\n    return None\n\n@app.route(\"/test\", methods=[\"POST\"])\n@openapi\ndef read_permission():\n    \"\"\"Test function\"\"\"\n    # print(f\"request OpenAPI dir : {request.openapi.body.items()}\")\n    __save_file_locally(request.files['inputFile'])\n    return jsonify({\"key\": \"Value\"})\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=567, debug=True)\n\n    # curl -X POST -H  \"Content-type: application/json\" --data '{\"flag\":\"ttF\"}' http://localhost:567/test\n\r\n--------------------------2c4bf22387dcbcbc--\r\n"
        }
    ]
}

Expected Behavior

It is expected that there should be no validation errors as we have specified mime type as multipart/form.

Also as per OpenAPI docs, OpenAPI 3.0 does not support type:file field,
and as per docs, while taking input we should specify type:string and format:binary

so just wanted to confirm whether the request body is correct or not

      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                inputFile:
                  type: string 
                  format: binary

Steps to Reproduce

Write the provided python3 file as file_reader.py and provided yaml file as file_reader.yaml

Execute file_reader.py file
and use following CURL command to make a request

curl -X POST -H  "Content-type: multipart/form-data" -F "file=@file_reader.py" http://localhost:567/test

OpenAPI Core Version

0.17.1

OpenAPI Core Integration

Flask

Affected Area(s)

Deserializing

References

No response

Anything else we need to know?

No response

Would you like to implement a fix?

Yes

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/bugIndicates an issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions