使用 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

它是从库中的一些其他代码拼凑而成的,所以我不确定是否有可以省略或以不同方式完成的行,但它确实有效。