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),两者合并在同一个类里,不再需要额外的装饰器。