Open
Description
Summary
When using openapi 3, the schema
inside the responses
should be inside a content
, then application/json
block (or other media type if not application/json
).
Openapi response object documentation is here.
☝🏻 If I've missed some configuration that allows to do this, my apologies!
Code to reproduce the issue
We define a route to list pets.
We configure the app to use openapi 3.
petsbp.py
:
from flask import Blueprint, jsonify
from flask_apispec import doc, marshal_with
from marshmallow import Schema, fields
bp = Blueprint(
"pets",
__name__,
url_prefix="/pets",
)
class PetSchema(Schema):
name = fields.Str(required=True)
@bp.route("/", methods=("GET",))
@doc(description="list pets")
@marshal_with(schema=PetSchema(many=True), code=200)
def list_pets():
return [{"name": "Fido"}]
server.py
:
def create_app():
app = Flask(__name__)
app.register_blueprint(petsbp.bp)
app.config.update(
{
"APISPEC_SPEC": APISpec(
title="PetsAPI",
version="v1",
openapi_version="3.0.0",
plugins=[MarshmallowPlugin()],
),
}
)
docs = FlaskApiSpec(
app,
document_options=False,
)
with app.app_context():
docs.register_existing_resources()
return app
Results
Expected behavior
Generated schema:
{
"components": {
"schemas": {
"Pet": {
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
}
},
"info": {
"title": "PetsAPI",
"version": "v1"
},
"openapi": "3.0.0",
"paths": {
"/pets/": {
"get": {
"description": "list pets",
"parameters": [],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/Pet"
},
"type": "array"
}
}
},
"description": "list pets"
}
}
}
}
}
}
Zoom in on the 200 response only:
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/Pet"
},
"type": "array"
}
}
},
"description": "list pets"
}
Note that the schema
appears inside a block application/json
, which is inside content
, which is inside 200
.
Swagger ui:
Note that we have an example value and schema.
Actual behavior:
Generated schema:
{
"components": {
"schemas": {
"Pet": {
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
}
},
"info": {
"title": "PetsAPI",
"version": "v1"
},
"openapi": "3.0.0",
"paths": {
"/pets/": {
"get": {
"description": "list pets",
"parameters": [],
"responses": {
"200": {
"description": "",
"schema": {
"items": {
"$ref": "#/components/schemas/Pet"
},
"type": "array"
}
}
}
}
}
}
}
Zoom in on the 200 response only:
"200": {
"description": "",
"schema": {
"items": {
"$ref": "#/components/schemas/Pet"
},
"type": "array"
}
}
Note that the schema
is a direct child of 200
.
Swagger UI:

Note there's no example value or schema.
Possible workarounds
Workaround 1 - specify the schema in @doc
, don't use @use_marshal
.
Use @doc
to specify the response. Don't use @use_marshal
:
@bp.route("/", methods=("GET",))
@doc(
description="list pets",
responses={
200: {
"content": {"application/json": {"schema": PetSchema(many=True)}},
"description": "list pets",
}
},
)
def list_pets():
return jsonify(PetSchema(many=True).dump([{"name": "Fido"}]))
Workaround 2 - use a custom converter
server.py
:
class MyViewConverter(ViewConverter):
def get_path(self, rule, target, **kwargs):
"""
In all responses, move the `schema` inside a `content`, `application/json` block.
TODO handle other mime types
"""
result = super().get_path(rule, target, **kwargs)
for operation in result["operations"].values():
for response in operation["responses"].values():
schema = response.pop("schema")
content = response.setdefault("content", {})
mimetype = content.setdefault("application/json", {})
mimetype["schema"] = schema
return result
def create_app():
app = Flask(__name__)
app.register_blueprint(petsbp.bp)
app.config.update(
{
"APISPEC_SPEC": APISpec(
title="PetsAPI",
version="v1",
openapi_version="3.0.0",
plugins=[MarshmallowPlugin()],
),
}
)
docs = FlaskApiSpec(
app,
document_options=False,
)
# NEW: use our custom view converter
docs.view_converter = MyViewConverter(docs.app, docs.spec, docs.document_options)
with app.app_context():
docs.register_existing_resources()
return app
Metadata
Metadata
Assignees
Labels
No labels