jmcarp / flask-apispec

MIT License
655 stars 157 forks source link

File upload field declaration in Swagger #122

Open tiangolo opened 6 years ago

tiangolo commented 6 years ago

I recently needed a Swagger 2.0 API endpoint to receive a file upload, and Swagger UI wouldn't show the file input button until I declared it correctly.


Maybe this should go into the docs, but until I find the time to do it properly (and get the hang of .rst files again, as I always forget the syntax), I'll post my problem and how I solved it here. Just so that others having the same problem can find it in search engines, at least.

I'm cross-posting from this comment, as this is actually more relevant to Flask-apispec.


My project is based on https://github.com/tiangolo/full-stack-flask-couchdb (Flask-apispec).

I had to "create an apispec MarshmallowPlugin"

After creating the plugin, add it to the APISpec instance.

And then, AFTER that, create a custom FileField.

This is how the file where I set up Flask-apispec looks like.

./app/api/api_v1/api_docs.py :

# Import installed packages
from apispec import APISpec
from flask_apispec import FlaskApiSpec

# Import app code
from ...main import app
from ...core import config

from apispec.ext.marshmallow import MarshmallowPlugin
from marshmallow import fields

file_plugin = MarshmallowPlugin()

security_definitions = {
    "bearer": {
        "type": "oauth2",
        "flow": "password",
        "tokenUrl": f"{config.API_V1_STR}/login/access-token",
    }
}

app.config.update(
    {
        "APISPEC_SPEC": APISpec(
            title="My awesome API",
            version="v1",
            plugins=("apispec.ext.marshmallow", file_plugin),
            securityDefinitions=security_definitions,
        ),
        "APISPEC_SWAGGER_URL": f"{config.API_V1_STR}/swagger/",
    }
)

@file_plugin.map_to_openapi_type('file', None)
class FileField(fields.Raw):
    pass

docs = FlaskApiSpec(app)

security_params = [{"bearer": []}]

And then, in the endpoint that I want to declare as receiving a file:

# Import installed packages
import uuid
from flask import abort
from webargs import fields
from flask_apispec import doc, use_kwargs, marshal_with
from flask_jwt_extended import jwt_required, get_current_user

# Import app code
from app.main import app
from app.api.api_v1.api_docs import docs, security_params
from app.core import config
from app.utils.utils import get_message_from_image
from app.db.utils import check_if_user_is_superuser, check_if_user_is_active

from app.api.api_v1.api_docs import FileField

# For type hints
from werkzeug.datastructures import FileStorage

# Import Schemas
from app.schemas.msg import MsgSchema

@docs.register
@doc(description="Get file and to stuff", security=security_params, tags=["files"], consumes=['multipart/form-data'])
@app.route(f"{config.API_V1_STR}/upload/", methods=["POST"])
@use_kwargs({"image": FileField(required=True)}, locations=["files"])
@marshal_with(MsgSchema())
@jwt_required
def route_files_post(image: FileStorage):
    user = get_current_user()
    if not check_if_user_is_active(user):
        abort(400, "User not active")
    elif not check_if_user_is_superuser(user):
        abort(400, "User not a superuser")
    file_id = uuid.uuid4()
    file_path = f"/data/files/{file_id}"
    image.save(file_path)
    message = get_message_from_image(file_path)
    return ({"msg": message}, 200)

Notice that I'm using Python 3.6 type hints in the image: FileStorage. This is just to have code completion in the editor.

But also, notice that what it receives is not a bytes object, nor a file object, it's a werkzeug's FileStorage object, that includes a save method.

That's the complete example that did the trick for me.


Also, if you think this is something that would be worthwhile having integrated into Flask-apispec / apispec / Webargs / Marshmallow (actually I don't know where would it be), let me know and I'll do my best to submit a PR.

danieleades commented 5 years ago

This would be an amazing addition.

I desperately need to both upload a file to the API, and for that upload method to be self-documenting. I'm using the swagger-ui in lieu of a proper UI.

aprilahijriyan commented 3 years ago

@sloria any progress?