Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] There is no way to create swagger file upload #85

Closed
kemara opened this issue Nov 23, 2020 · 11 comments
Closed

[BUG] There is no way to create swagger file upload #85

kemara opened this issue Nov 23, 2020 · 11 comments

Comments

@kemara
Copy link

kemara commented Nov 23, 2020

Describe the bug
As json takes a pynatic model and there is no Pyndatic field with type file I could not find a way to upload a file.

Does anybody know of a way to create a swagger file upload in Flask?

@kemingy
Copy link
Member

kemingy commented Nov 23, 2020

Thanks for your feedback.

Pydantic doesn't have a file type. Actually, flask will create a werkzeug.datastructures.FileStorage object. To support this, you need to provide a File type.

from werkzeug.datastructures import FileStorage
from pydantic import BaseModel


class File:
    file: FileStorage
    class Config:
        arbitrary_types_allowed = True

Let's say if you have multiple items in the form, then you may need to create the schema like this:

from werkzeug.datastructures import FileStorage
from pydantic import BaseModel


class Form:
    file: FileStorage
    user: str
    addr: str

    class Config:
        arbitrary_types_allowed = True

I'll create a PR to support this.

@kemingy
Copy link
Member

kemingy commented Nov 24, 2020

Pydantic doesn't have a file type. Actually, flask will create a werkzeug.datastructures.FileStorage object. To support this, you need to provide a File type.

The problem is that cannot generate the schema because FileStorage is not a pydantic model.

I guess we need to create another model like this:

class FlaskFile(BaseModel):
    filename: str
    name: str
    content_length: int
    content_type: str
    mimetype: str
    stream: bytes

@kemara
Copy link
Author

kemara commented Nov 25, 2020

Thank you for your help but I was still unable to create a functioning swagger doc that can upload a file.

When I try to add it to a spectree.validate as json or header an error accurs:
Value not declarable with JSON Schema, field: name='file' type=FileStorage required=True

As I understood you we need to add FlaskFile into spectree so it can handle it. Is there any way I could help with that?

@kemingy
Copy link
Member

kemingy commented Nov 25, 2020

I found a solution. It may not be elegant.

from werkzeug.datastructures import FileStorage
from pydantic import BaseModel


FlaskFile = FileStorage


def file_modify_schame(cls, field_schema):
    field_schema.update(format='file-storage')


FlaskFile.__modify_schema__ = classmethod(file_modify_schame)


class Form(BaseModel):
    user: str
    file: FlaskFile

    class Config:
        arbitrary_types_allowed = True

The code above will work with this commit: 53936d4

Let me know if you have any ideas.

@kemara
Copy link
Author

kemara commented Dec 9, 2020

Thank you again for your help but I was still not able to make it work.
Here is the complete flask application where I tried to make it work:

from flask import Flask
from spectree import SpecTree
from werkzeug.datastructures import FileStorage
from pydantic import BaseModel

FlaskFile = FileStorage

def file_modify_schame(cls, field_schema):
    field_schema.update(format='file-storage')

FlaskFile.__modify_schema__ = classmethod(file_modify_schame)

class Form(BaseModel):
    user: str
    file: FlaskFile

    class Config:
        arbitrary_types_allowed = True

app = Flask(__name__)
api = SpecTree('flask')

@app.route('/', methods=['POST'])
@api.validate(json=Form)
def hello_world():
    return 'Hello World!'

api.register(app)
if __name__ == '__main__':
    app.run()

The swagger looks like:
Screenshot 2020-12-09 at 10 57 54

Is there anything wrong that I am doing?

@kemingy
Copy link
Member

kemingy commented Dec 9, 2020

Hi, I think your code is correct. But sadly swagger doesn't support the customized type of object. I think you can find the definition in swagger schemas below or redoc page (/apidoc/redoc).

@gomeslucasm
Copy link

gomeslucasm commented May 19, 2022

The problem is that spectree only parse request body for json on docs generation.

if hasattr(func, "json"):
        data = {"content": {"application/json": {"schema": {"$ref": f"#/components/schemas/{func.json}"}}}}

In the request context, spectree convert both request.form and request.json in the same request.context.json.

I'm start to use spectree in my work and I made some modifications to put @api.validate(json=JsonSchema) or @api.validate(form=FormSchema), using that, I modificate the request body parser for docs, to diferentiate the two types

if hasattr(func, "json"):
        data = {"content": {"application/json": {"schema": {"$ref": f"#/components/schemas/{func.json}"}}}}

if hasattr(func, "form"):
        data = {"content": {"multipart/form-data": {"schema": {"$ref": f"#/components/schemas/{func.form}"}}}}

After do that, you will be able to create a pydantic custom schema for file that has a customized schema that returns the openapi spec for file upload.

class UploadedFile:
    @classmethod
    def __get_validators__(cls):
        yield cls.validate

    @classmethod
    def validate(cls, v):
        return v
    @classmethod
    def __modify_schema__(cls, field_schema):
        field_schema["type"] = "file"
        field_schema["format"] = "binary"

@gomeslucasm
Copy link

gomeslucasm commented May 19, 2022

Are there some reasons for choosing to add both request.form or request.json in the same @api.validate(json=Schema)? Instead of declaring the two different schemas?

@kemingy
Copy link
Member

kemingy commented May 19, 2022

Are there some reasons for choosing to add both request.form or request.json in the same @api.validate(json=Schema)? Instead of declaring the two different schemas?

The request body can be either JSON data (maybe other serialized type) or a file.

There is a PR related to this:

This PR doesn't have activities for a long time. Hope we can work together to improve it.

@gomeslucasm
Copy link

Interesting, maybe I can help you, I don't have knowledge about starlette or falcon, but I can give you some ideas about the modifications that I made in the flask plugin.

@yedpodtrzitko
Copy link
Collaborator

closing, solved via #225

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants