spectree
spectree copied to clipboard
[BUG] There is no way to create swagger file upload
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?
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.
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
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?
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: https://github.com/0b01001001/spectree/commit/53936d41463c0a7020dc2174b06b6b6187c3028d
Let me know if you have any ideas.
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:
Is there anything wrong that I am doing?
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
).
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"
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?
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:
- #185
This PR doesn't have activities for a long time. Hope we can work together to improve it.
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.
closing, solved via #225