improving-drf-endpoints

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Improving DRF Endpoints

优化DRF端点

Overview

概述

Serializer fields are the source of truth for PostHog's entire type pipeline:
text
Django serializer → drf-spectacular → OpenAPI JSON → Orval → Zod schemas → MCP tools
Every
help_text
, every field type, every
@extend_schema
annotation flows downstream. A missing
help_text
means an agent guessing at parameters. A bare
ListField()
means
z.unknown()
in the generated Zod schema. Getting the serializer right means every consumer — frontend types, MCP tools, API docs — gets correct types and descriptions automatically.
Serializer字段是PostHog整个类型流水线的唯一可信源:
text
Django serializer → drf-spectacular → OpenAPI JSON → Orval → Zod schemas → MCP tools
每个
help_text
、每个字段类型、每个
@extend_schema
注解都会向下流转。缺失
help_text
会导致Agent需要猜测参数含义;未指定类型的
ListField()
会在生成的Zod Schema中变成
z.unknown()
。确保Serializer的正确性,就能让所有下游消费者——前端类型、MCP工具、API文档——自动获得正确的类型与描述。

When to use

适用场景

  • Editing or reviewing any file that defines a
    Serializer
    or
    ViewSet
  • Fixing OpenAPI spec warnings or generated type issues
  • Preparing an endpoint for MCP tool exposure
  • Code review of API changes
  • 编辑或评审任何定义了
    Serializer
    ViewSet
    的文件
  • 修复OpenAPI规范警告或生成的类型问题
  • 为MCP工具暴露准备端点
  • API变更的代码评审

Audit checklist

审计检查清单

Triage: check the generated output first

优先级排序:先检查生成的输出结果

Before diving into Python, look at the committed generated types to see what's broken. Find the generated files for the endpoint's product:
  • Core API:
    frontend/src/generated/core/
  • Product APIs:
    products/<product>/frontend/generated/
Each has two files:
  • api.schemas.ts
    — TypeScript interfaces derived from serializers. Search for the serializer name and look for
    unknown
    types (bare
    ListField
    /
    JSONField
    ), missing JSDoc descriptions (missing
    help_text
    ), or overly generic
    Record<string, unknown>
    shapes.
  • api.ts
    — API client functions. Check if the endpoint's operation exists at all — if missing, the viewset method likely lacks
    @extend_schema
    .
This tells you exactly which fields and endpoints to prioritize.
在深入Python代码之前,先查看已提交的生成类型,确认存在哪些问题。找到对应产品端点的生成文件:
  • 核心API:
    frontend/src/generated/core/
  • 产品API:
    products/<product>/frontend/generated/
每个目录下有两个关键文件:
  • api.schemas.ts
    —— 从Serializer派生的TypeScript接口。搜索Serializer名称,查看是否存在
    unknown
    类型(来自未指定子类型的
    ListField
    /
    JSONField
    )、缺失JSDoc描述(对应缺失的
    help_text
    ),或过于宽泛的
    Record<string, unknown>
    结构。
  • api.ts
    —— API客户端函数。检查端点的操作是否存在——如果缺失,说明ViewSet方法可能未添加
    @extend_schema
    注解。
这能帮你明确需要优先处理的字段和端点。

Serializer fields

Serializer字段

Work through this list for every serializer and viewset you touch.
  1. Every field has
    help_text
    — describes purpose, format, constraints, valid values
  2. No bare
    ListField()
    or
    DictField()
    — always specify
    child=
    with a typed serializer or field
  3. No bare
    JSONField()
    — create a custom field class with
    @extend_schema_field(TypedSchema)
  4. SerializerMethodField
    has
    @extend_schema_field
    on its
    get_*
    method
  5. ChoiceField
    has explicit
    choices=
    with all valid values listed
  6. Read vs write serializers are separate when input shape differs from output
  7. Every success response is backed by a serializer — returning raw dicts or untyped lists means no generated types downstream
See serializer-fields.md for patterns and examples.
处理每个Serializer和ViewSet时,逐一核对以下清单:
  1. 所有字段都包含
    help_text
    —— 描述字段用途、格式、约束条件及有效值
  2. 禁止使用未指定类型的
    ListField()
    DictField()
    —— 必须通过
    child=
    指定类型化的Serializer或字段
  3. 禁止使用未指定类型的
    JSONField()
    —— 创建自定义字段类并添加
    @extend_schema_field(TypedSchema)
    注解
  4. SerializerMethodField
    get_*
    方法需添加
    @extend_schema_field
    注解
  5. ChoiceField
    需显式指定
    choices=
    参数,列出所有有效值
  6. 当输入与输出结构不同时,需将读、写Serializer分离
  7. 所有成功响应都需由Serializer提供支持 —— 返回原始字典或未类型化列表会导致下游无法生成类型
查看serializer-fields.md获取模式示例。

Viewset and action annotations

ViewSet与Action注解

  1. Every custom
    @action
    has
    @extend_schema
    or
    @validated_request
    — without it, drf-spectacular discovers zero parameters
  2. Plain
    ViewSet
    methods have schema annotations
    ModelViewSet
    with
    serializer_class
    is auto-discovered; plain
    ViewSet
    is not
  3. @extend_schema
    is on the actual method
    (
    get
    ,
    post
    ,
    create
    ,
    list
    ), not on a helper or the class itself
  4. Error responses are typed — use
    OpenApiResponse(response=ErrorSerializer)
    , not
    OpenApiTypes.OBJECT
  5. List endpoints declare pagination — reset with
    pagination_class=None
    on custom actions that don't paginate
  6. Prefer
    @validated_request
    over manual
    serializer.is_valid()
    +
    @extend_schema
    — it handles both in one decorator
  7. ViewSets outside
    products/
    need
    @extend_schema(tags=["<product>"])
    — ViewSets in
    products/<name>/backend/
    are auto-tagged via module path, but ViewSets in
    posthog/api/
    or
    ee/
    are not. Without the tag, the MCP scaffold and frontend type generator can't route the endpoint to the right product
Streaming endpoints: For SSE or streaming responses, use
@extend_schema(request=InputSerializer, responses={(200, "text/event-stream"): OpenApiTypes.STR})
to document the request schema even though the response can't be fully typed.
See viewset-annotations.md for patterns and examples.
  1. 所有自定义
    @action
    都需添加
    @extend_schema
    @validated_request
    注解
    —— 缺少注解的话,drf-spectacular无法发现任何参数
  2. 普通
    ViewSet
    方法需添加Schema注解
    —— 带有
    serializer_class
    ModelViewSet
    会被自动发现,但普通
    ViewSet
    不会
  3. @extend_schema
    注解需添加在实际方法上
    (如
    get
    post
    create
    list
    ),而非辅助方法或类本身
  4. 错误响应需指定类型 —— 使用
    OpenApiResponse(response=ErrorSerializer)
    ,而非
    OpenApiTypes.OBJECT
  5. 列表端点需声明分页配置 —— 对于不需要分页的自定义Action,需设置
    pagination_class=None
  6. 优先使用
    @validated_request
    而非手动调用
    serializer.is_valid()
    +
    @extend_schema
    —— 该注解可同时处理验证与Schema生成
  7. products/
    目录外的ViewSet需添加
    @extend_schema(tags=["<product>"])
    注解
    ——
    products/<name>/backend/
    下的ViewSet会通过模块路径自动打标签,但
    posthog/api/
    ee/
    下的ViewSet不会。缺少标签会导致MCP脚手架与前端类型生成器无法将端点路由到正确产品
流式端点: 对于SSE或流式响应,使用
@extend_schema(request=InputSerializer, responses={(200, "text/event-stream"): OpenApiTypes.STR})
来记录请求Schema,尽管响应无法完全类型化。
查看viewset-annotations.md获取模式示例。

Facade products (DataclassSerializer)

门面产品(DataclassSerializer)

For products using the facade pattern (e.g.,
visual_review
) with
DataclassSerializer
wrapping frozen dataclasses from
contracts.py
:
  • Field types are auto-derived from the dataclass — fewer typing issues by design
  • Focus on
    help_text
    (dataclass fields don't carry it; add it on the serializer field overrides)
  • @validated_request
    is already the standard pattern — verify response serializers are declared
  • @extend_schema
    tags and descriptions still need to be set on viewset methods
对于使用门面模式的产品(如
visual_review
),其
DataclassSerializer
包装了来自
contracts.py
的冻结数据类:
  • 字段类型会自动从数据类派生——设计上减少了类型问题
  • 重点关注**
    help_text
    **(数据类字段不携带该属性,需在Serializer字段重写时添加)
  • **
    @validated_request
    **已是标准模式——需验证响应Serializer是否已声明
  • @extend_schema
    标签与描述仍需添加在ViewSet方法上

Decision flowchart

决策流程图

dot
digraph audit {
    rankdir=TB
    node [shape=diamond fontsize=10]
    edge [fontsize=9]

    start [label="Serializer or\nViewSet file?" shape=box]
    is_model [label="ModelViewSet with\nserializer_class?"]
    is_plain [label="Plain ViewSet or\ncustom @action?"]
    is_facade [label="DataclassSerializer\n(facade product)?"]

    check_fields [label="Check fields:\nhelp_text, ListField,\nJSONField, ChoiceField" shape=box]
    add_schema [label="Add @validated_request\nor @extend_schema to\nevery method" shape=box]
    check_help [label="Focus on help_text\nand response declarations" shape=box]
    check_responses [label="Check response types,\npagination, error schemas" shape=box]

    start -> is_model
    is_model -> check_fields [label="yes"]
    is_model -> is_plain [label="no"]
    is_plain -> add_schema [label="yes"]
    is_plain -> is_facade [label="no"]
    is_facade -> check_help [label="yes"]
    check_fields -> check_responses
    add_schema -> check_fields
    check_help -> check_responses
}
dot
digraph audit {
    rankdir=TB
    node [shape=diamond fontsize=10]
    edge [fontsize=9]

    start [label="Serializer or\nViewSet file?" shape=box]
    is_model [label="ModelViewSet with\nserializer_class?"]
    is_plain [label="Plain ViewSet or\ncustom @action?"]
    is_facade [label="DataclassSerializer\n(facade product)?"]

    check_fields [label="Check fields:\nhelp_text, ListField,\nJSONField, ChoiceField" shape=box]
    add_schema [label="Add @validated_request\nor @extend_schema to\nevery method" shape=box]
    check_help [label="Focus on help_text\nand response declarations" shape=box]
    check_responses [label="Check response types,\npagination, error schemas" shape=box]

    start -> is_model
    is_model -> check_fields [label="yes"]
    is_model -> is_plain [label="no"]
    is_plain -> add_schema [label="yes"]
    is_plain -> is_facade [label="no"]
    is_facade -> check_help [label="yes"]
    check_fields -> check_responses
    add_schema -> check_fields
    check_help -> check_responses
}

Quick reference

快速参考

See quick-reference-table.md for a scannable "I see X, do Y" lookup.
See common-anti-patterns.md for before/after code pairs.
查看quick-reference-table.md获取可快速查阅的“遇到X,执行Y”对照表。
查看common-anti-patterns.md获取代码正反例对比。

Canonical examples in the codebase

代码库中的标准示例

  • JSONField + @extend_schema_field:
    posthog/api/alert.py
  • @validated_request:
    products/tasks/backend/api.py
  • help_text + typed responses:
    products/llm_analytics/backend/api/evaluation_summary.py
  • Facade product:
    products/visual_review/backend/presentation/views.py
  • JSONField + @extend_schema_field:
    posthog/api/alert.py
  • @validated_request:
    products/tasks/backend/api.py
  • help_text + 类型化响应:
    products/llm_analytics/backend/api/evaluation_summary.py
  • 门面产品:
    products/visual_review/backend/presentation/views.py

Related

相关资源

  • Downstream: After fixing serializers, use the
    implementing-mcp-tools
    skill to scaffold MCP tools
  • Pipeline docs:
    docs/published/handbook/engineering/type-system.md
  • Mixins:
    posthog/api/mixins.py
    (
    @validated_request
    source)
  • drf-spectacular config:
    posthog/settings/web.py
    (
    SPECTACULAR_SETTINGS
    )
  • 下游操作: 修复Serializer后,可使用
    implementing-mcp-tools
    技能搭建MCP工具
  • 流水线文档:
    docs/published/handbook/engineering/type-system.md
  • Mixin:
    posthog/api/mixins.py
    @validated_request
    的源码)
  • drf-spectacular配置:
    posthog/settings/web.py
    SPECTACULAR_SETTINGS