Describe the bug
when using OpenApiSerializerExtension that returns schema component with array element as the root, the component will not be included in the final open API schema json.
To Reproduce
extension implementation
from marshmallow import Schema
from drf_spectacular.extensions import OpenApiSerializerExtension
class MarshmallowSerializerExtension(OpenApiSerializerExtension):
target_class = Schema
match_subclasses = True
def get_name(self):
if inspect.isclass(self.target):
serializer_name = self.target.__name__
else:
serializer_name = type(self.target).__name__
return serializer_name
def map_serializer(self, auto_schema, direction):
return { # this will not be included in the final result (in components object)
"type": "array",
"items": {
"type": "object",
"properties": {
"e1": {
"type": "string",
"enum": ["a", "b", "c"]
},
},
"xml": {
"name": "item"
}
},
"xml": {
"wrapped": True,
"name": "arrayresponse"
}
}
ViewSet implementation
note: I'm using OpenApiViewExtension, but the issue same as if I use @extend_schema directly in the ViewSet class implementation
from rest_framework.viewsets import ViewSet
from rest_framework_xml.parsers import XMLParser
from rest_framework_xml.renderers import XMLRenderer
class MyViewSet(ViewSet):
parser_classes = (XMLParser,)
renderer_classes = (XMLRenderer,)
def post(self, request, format=None):
# ...
pass
ViewSet open api view extension implementation
from drf_spectacular.utils import extend_schema
from drf_spectacular.extensions import OpenApiViewExtension
class MyViewSetExtension(OpenApiViewExtension):
target_class = 'my_project.views.MyViewSet'
def view_replacement(self):
class Fixer(self.target_class):
@extend_schema(request=RequestSerializer) # RequestSerializer as a class that will be handled by MarshmallowSerializerExtension
def create(self, request):
pass
Generated schema
notice the components object is empty
{
"openapi": "3.0.3",
"info": {
"title": "My App",
"version": "1.0",
"description": "My App APIs documentation"
},
"paths": {
"/api/v1/my_view": {
"post": {
"operationId": "my_view_create",
"description": "The API description",
"summary": "The API summary",
"tags": [
"my_view"
],
"responses": {
"200": {
"description": "No response body"
}
}
}
}
},
"components": {}
}
Expected behavior
I expect the component to be included in the generated schema
expected schema
{
"openapi": "3.0.3",
"info": {
"title": "My App",
"version": "1.0",
"description": "My App APIs documentation"
},
"paths": {
"/api/v1/my_view": {
"post": {
"operationId": "my_view_create",
"description": "The API description",
"summary": "The API summary",
"tags": [
"my_view"
],
"requestBody": {
"content": {
"application/xml": {
"schema": {
"$ref": "#/components/schemas/RequestSchema"
}
}
}
},
"responses": {
"200": {
"description": "No response body"
}
}
}
}
},
"components": {
"schemas": {
"RequestSchema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"e1": {
"type": "string",
"enum": [
"a",
"b",
"c"
]
}
},
"xml": {
"name": "item"
}
},
"xml": {
"wrapped": true,
"name": "arrayresponse"
}
}
}
}
}
More details
- Python version: 3.7.5
- Installed Packages:
- Django==2.2.16
- marshmallow==3.3.0
- djangorestframework==3.10.3
- djangorestframework-xml==2.0.0
- drf-spectacular==0.15.1
I did some investigation in the library implementation and this part that causes the issue
# drf_spectacular/openapi.py
class AutoSchema(ViewInspector):
def resolve_serializer(self, serializer, direction) -> ResolvedComponent:
# ...
keep_component = (
or any(nest_tag in component.schema for nest_tag in ['oneOf', 'allOf', 'anyOf'])
or component.schema.get('properties', {})
)
if not keep_component:
del self.registry[component]
return ResolvedComponent(None, None) # sentinel
# ...
Describe the bug
when using
OpenApiSerializerExtensionthat returns schema component with array element as the root, the component will not be included in the final open API schema json.To Reproduce
extension implementation
ViewSet implementation
note: I'm using OpenApiViewExtension, but the issue same as if I use
@extend_schemadirectly in the ViewSet class implementationViewSet open api view extension implementation
Generated schema
notice the
componentsobject is empty{ "openapi": "3.0.3", "info": { "title": "My App", "version": "1.0", "description": "My App APIs documentation" }, "paths": { "/api/v1/my_view": { "post": { "operationId": "my_view_create", "description": "The API description", "summary": "The API summary", "tags": [ "my_view" ], "responses": { "200": { "description": "No response body" } } } } }, "components": {} }Expected behavior
I expect the component to be included in the generated schema
expected schema
{ "openapi": "3.0.3", "info": { "title": "My App", "version": "1.0", "description": "My App APIs documentation" }, "paths": { "/api/v1/my_view": { "post": { "operationId": "my_view_create", "description": "The API description", "summary": "The API summary", "tags": [ "my_view" ], "requestBody": { "content": { "application/xml": { "schema": { "$ref": "#/components/schemas/RequestSchema" } } } }, "responses": { "200": { "description": "No response body" } } } } }, "components": { "schemas": { "RequestSchema": { "type": "array", "items": { "type": "object", "properties": { "e1": { "type": "string", "enum": [ "a", "b", "c" ] } }, "xml": { "name": "item" } }, "xml": { "wrapped": true, "name": "arrayresponse" } } } } }More details
I did some investigation in the library implementation and this part that causes the issue