使用 Django Rest Framework 在 swagger 生成中重用选择定义
Reusing choice definition in swagger generation with Django Rest Framework
我有一个使用 drf-yasg
生成 swagger.json
文件的 Django(Django Rest Framework)网络服务。在模型中,我有几个 enums/choicefields 用于多个地方。默认情况下,drf-yasg
定义每次出现的内联字段:
Choices = serializers.ChoiceField(choices=['a', 'b', 'c'])
class SomeObject(serializers.Serializer):
field_1 = Choices
field_2 = Choices
在 swagger 文件中生成以下定义:
{
"definitions": {
"SomeObject": {
"required": [ "field_1", "field_2" ],
"type": "object",
"properties": {
"field_1": {
"title": "Field 1",
"type": "string",
"enum": [ "a", "b", "c" ]
},
"field_2": {
"title": "Field 1",
"type": "string",
"enum": [ "a", "b", "c" ]
}
}
}
}
}
这是一个小问题,因为它使客户端代码生成工具将每个枚举生成为自己的类型,而不是重复使用定义。所以我想像这样创建一个 swaggerfile:
{
"definitions": {
"Choices": {
"title": "Field 1",
"type": "string",
"enum": [ "a", "b", "c" ]
},
"SomeObject": {
"required": [ "field_1", "field_2" ],
"type": "object",
"properties": {
"field_1": {
"$ref": "#/definitions/Choices"
},
"field_2": {
"$ref": "#/definitions/Choices"
}
}
}
}
}
是否可以在 Django Rest Framework 中启用此行为?
以防万一有人看到并需要一些指示。我最终按如下方式实施:
创建 ChoiceField
的子类,用于指示应将枚举实现为引用,以及一些其他检查。检查 str
是为了确保序列化知道如何处理这些值:
from rest_framework import serializers
from enum import Enum
class ReferenceEnumField(serializers.ChoiceField):
def __init__(self, enum_type, **kwargs):
if not issubclass(enum_type, str):
raise TypeError("enum_type should inherit from str in order to be json-serializable.")
if not issubclass(enum_type, Enum):
raise TypeError("enum_type should be an Enum")
self.enum_name = enum_type.__name__
super().__init__(choices=[enum.name for enum in enum_type], **kwargs)
然后就是可以添加到装饰器中的inspector如下:
from drf_yasg.inspectors.base import NotHandled
from drf_yasg.inspectors.field import ReferencingSerializerInspector
from drf_yasg import openapi
from drf_yasg.errors import SwaggerGenerationError
from .ReferenceEnumfield import ReferenceEnumField
class EnumAsReferenceInspector(ReferencingSerializerInspector):
accepting_objects = True
@classmethod
def set_accepting_objects(cls, value):
cls.accepting_objects = value
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
if EnumAsReferenceInspector.accepting_objects and isinstance(field, ReferenceEnumField):
try:
# Avoid infinite recursion by setting the class to not accept objects to serialize.
EnumAsReferenceInspector.set_accepting_objects(False)
if swagger_object_type != openapi.Schema:
raise SwaggerGenerationError("cannot instantiate nested serializer as " + swagger_object_type.__name__)
ref_name = field.enum_name
def make_schema_definition(enum=field):
return self.probe_field_inspectors(enum, ChildSwaggerType, use_references)
if not ref_name or not use_references:
return make_schema_definition()
definitions = self.components.with_scope(openapi.SCHEMA_DEFINITIONS)
actual_schema = definitions.setdefault(ref_name, make_schema_definition)
actual_schema._remove_read_only()
return openapi.SchemaRef(definitions, ref_name)
finally:
EnumAsReferenceInspector.set_accepting_objects(True)
return NotHandled
它是从库中的一些其他代码拼凑而成的,所以我不确定是否有可以省略或以不同方式完成的行,但它确实有效。
我有一个使用 drf-yasg
生成 swagger.json
文件的 Django(Django Rest Framework)网络服务。在模型中,我有几个 enums/choicefields 用于多个地方。默认情况下,drf-yasg
定义每次出现的内联字段:
Choices = serializers.ChoiceField(choices=['a', 'b', 'c'])
class SomeObject(serializers.Serializer):
field_1 = Choices
field_2 = Choices
在 swagger 文件中生成以下定义:
{
"definitions": {
"SomeObject": {
"required": [ "field_1", "field_2" ],
"type": "object",
"properties": {
"field_1": {
"title": "Field 1",
"type": "string",
"enum": [ "a", "b", "c" ]
},
"field_2": {
"title": "Field 1",
"type": "string",
"enum": [ "a", "b", "c" ]
}
}
}
}
}
这是一个小问题,因为它使客户端代码生成工具将每个枚举生成为自己的类型,而不是重复使用定义。所以我想像这样创建一个 swaggerfile:
{
"definitions": {
"Choices": {
"title": "Field 1",
"type": "string",
"enum": [ "a", "b", "c" ]
},
"SomeObject": {
"required": [ "field_1", "field_2" ],
"type": "object",
"properties": {
"field_1": {
"$ref": "#/definitions/Choices"
},
"field_2": {
"$ref": "#/definitions/Choices"
}
}
}
}
}
是否可以在 Django Rest Framework 中启用此行为?
以防万一有人看到并需要一些指示。我最终按如下方式实施:
创建 ChoiceField
的子类,用于指示应将枚举实现为引用,以及一些其他检查。检查 str
是为了确保序列化知道如何处理这些值:
from rest_framework import serializers
from enum import Enum
class ReferenceEnumField(serializers.ChoiceField):
def __init__(self, enum_type, **kwargs):
if not issubclass(enum_type, str):
raise TypeError("enum_type should inherit from str in order to be json-serializable.")
if not issubclass(enum_type, Enum):
raise TypeError("enum_type should be an Enum")
self.enum_name = enum_type.__name__
super().__init__(choices=[enum.name for enum in enum_type], **kwargs)
然后就是可以添加到装饰器中的inspector如下:
from drf_yasg.inspectors.base import NotHandled
from drf_yasg.inspectors.field import ReferencingSerializerInspector
from drf_yasg import openapi
from drf_yasg.errors import SwaggerGenerationError
from .ReferenceEnumfield import ReferenceEnumField
class EnumAsReferenceInspector(ReferencingSerializerInspector):
accepting_objects = True
@classmethod
def set_accepting_objects(cls, value):
cls.accepting_objects = value
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
if EnumAsReferenceInspector.accepting_objects and isinstance(field, ReferenceEnumField):
try:
# Avoid infinite recursion by setting the class to not accept objects to serialize.
EnumAsReferenceInspector.set_accepting_objects(False)
if swagger_object_type != openapi.Schema:
raise SwaggerGenerationError("cannot instantiate nested serializer as " + swagger_object_type.__name__)
ref_name = field.enum_name
def make_schema_definition(enum=field):
return self.probe_field_inspectors(enum, ChildSwaggerType, use_references)
if not ref_name or not use_references:
return make_schema_definition()
definitions = self.components.with_scope(openapi.SCHEMA_DEFINITIONS)
actual_schema = definitions.setdefault(ref_name, make_schema_definition)
actual_schema._remove_read_only()
return openapi.SchemaRef(definitions, ref_name)
finally:
EnumAsReferenceInspector.set_accepting_objects(True)
return NotHandled
它是从库中的一些其他代码拼凑而成的,所以我不确定是否有可以省略或以不同方式完成的行,但它确实有效。