drf-yasg 自动文档原理详解

一、背景:什么是 drf-yasg?

drf-yasg(Yet Another Swagger Generator)是一个为 Django REST Framework(DRF)自动生成 OpenAPI / Swagger 文档的库。

它的工作流程是:

HTTP 请求 /swagger.json
       ↓
drf-yasg 遍历项目所有 URL → 找到每个 ViewSet / APIView
       ↓
对每个视图,实例化一个 "AutoSchema" 对象
       ↓
调用 AutoSchema 的各个方法,收集:tags、summary、请求体、响应体……
       ↓
拼装出完整的 OpenAPI JSON 文档

二、核心概念:AutoSchema

SwaggerAutoSchema 是 drf-yasg 的核心类,负责从一个视图中提取文档信息

每次生成文档时,drf-yasg 会为每个接口(每个 HTTP method)实例化一个 SwaggerAutoSchema 对象,然后调用它的方法:

方法作用
get_tags()返回该接口属于哪个 tag(分组)
get_summary_and_description()返回接口的标题和描述
get_request_body_parameters()返回请求体参数
get_response_schemas()返回响应体结构

AutoSchema 实例持有 self.view,即当前被解析的 ViewSet 实例,可以从中读取任意类属性。


三、ViewSet 如何告诉 drf-yasg 用哪个 AutoSchema?

DRF 的每个 View / ViewSet 都有一个类属性:

swagger_schema = None  # 默认值,使用全局配置的 AutoSchema

当 drf-yasg 解析这个 ViewSet 时,会检查这个属性,如果不为 None,就用它来代替全局默认的 AutoSchema

所以:

class LLMViewSet(ModelViewSet):
    swagger_schema = ResponseSwaggerAutoSchema  # ← 告诉 drf-yasg 用我们自定义的

四、旧写法的问题:@swagger_auto_schema 装饰器

旧写法需要在每个 action 方法上手动打装饰器:

@swagger_auto_schema(tags=["大语言模型管理"], operation_summary="获取大语言模型列表")
def list(self, request, *args, **kwargs):
    return super().list(request, *args, **kwargs)

@swagger_auto_schema 的本质是:把文档参数保存到方法的 _swagger_auto_schema 属性上,等 drf-yasg 解析时读取这个属性来覆盖 AutoSchema 的默认行为。

# drf-yasg 内部简化逻辑
overrides = getattr(view.action_handler, '_swagger_auto_schema', {})

问题是:这要求你必须显式定义这些方法,哪怕方法体只是 return super().xxx(),纯粹是样板代码。


五、新方案的原理

5.1 直接重写 AutoSchema 方法

既然 drf-yasg 是调用 AutoSchema 的方法来获取信息,我们直接重写这些方法就好:

class ResponseSwaggerAutoSchema(SwaggerAutoSchema):
 
    def get_tags(self, operation_keys=None):
        # self.view 就是当前 ViewSet 实例
        tags = getattr(self.view, "swagger_tags", None)
        if tags:
            return list(tags)
        return super().get_tags(operation_keys)  # 降级到默认行为
 
    def get_summary_and_description(self):
        name = getattr(self.view, "swagger_name", None)
        if name:
            action = getattr(self.view, "action", None)  # 当前动作名,如 "list"
            summaries = {**_ACTION_SUMMARIES, ...}
            tpl = summaries.get(action)       # 取模板,如 "获取{name}列表"
            if tpl:
                return tpl.format(name=name), ""   # → "获取大语言模型列表", ""
        return super().get_summary_and_description()

5.2 self.view.action 是什么?

DRF 的 ViewSet 在接收请求时,会把当前执行的动作名写入 self.action

GET    /llms/      → action = "list"
POST   /llms/      → action = "create"
GET    /llms/{id}/ → action = "retrieve"
PUT    /llms/{id}/ → action = "update"
PATCH  /llms/{id}/ → action = "partial_update"
DELETE /llms/{id}/ → action = "destroy"

drf-yasg 生成文档时也会模拟这个过程,所以 self.view.action 在文档生成阶段是可读的。

5.3 ViewSet 只需声明类属性

class LLMViewSet(ModelViewSet):
    swagger_schema = ResponseSwaggerAutoSchema   # 使用自定义 AutoSchema
    swagger_name   = "大语言模型"                 # AutoSchema.get_summary_and_description() 读取
    swagger_tags   = ["大语言模型管理"]            # AutoSchema.get_tags() 读取

drf-yasg 解析文档时:

发现 LLMViewSet.swagger_schema = ResponseSwaggerAutoSchema
→ 实例化 ResponseSwaggerAutoSchema(view=llm_viewset_instance, ...)
→ 调用 get_tags()
    → getattr(self.view, "swagger_tags") = ["大语言模型管理"] ✓
→ 调用 get_summary_and_description()
    → name = "大语言模型", action = "list"
    → 返回 "获取大语言模型列表"  ✓

六、对比总结

旧方案:装饰器注入(外部覆盖)
─────────────────────────────────────
@swagger_auto_schema(tags=..., operation_summary=...)
def list(self, ...):          ← 必须显式定义方法
    return super().list(...)  ← 无实际业务逻辑


新方案:AutoSchema 内部读取(原生集成)
─────────────────────────────────────
swagger_schema = ResponseSwaggerAutoSchema  ← 声明用哪个 Schema
swagger_name   = "大语言模型"               ← Schema 内部自动读取
swagger_tags   = ["大语言模型管理"]          ← Schema 内部自动读取
# 无需定义任何 action 方法

新方案走的是 drf-yasg 的正规扩展路径,ResponseSwaggerAutoSchema 同时也保留了原有的自定义响应体结构逻辑(get_response_schemas),两者合并在同一个类里,不再需要额外的装饰器。