- 后端
- API设计
【免费下载链接】django-ninja
💨 Fast, Async-ready, Openapi, type hints based framework for building APIs
本篇指南聚焦 Django Ninja 中GET 查询参数(query parameters)的声明、类型转换、校验与文档化机制。你将学会如何让函数签名中的普通参数自动成为查询参数,掌握必填/可选参数的声明方式、bool/date/int等类型的转换规则,以及如何使用Query[...]+ Schema 对复杂过滤条件进行结构化封装。读完即可在自己的 API 中写出类型安全、自动生成 OpenAPI 文档、可被编辑器与测试框架完整感知的查询参数层。
查询参数的本质:函数签名即参数声明
在 Django Ninja 中,路由处理函数除了第一个request参数外,所有不属于路径参数(path parameters)的函数参数,都会被自动解释为查询参数。这与 FastAPI 的设计一脉相承:你不需要显式声明"这是一个 query 参数",类型注解和默认值本身就承载了全部声明信息。
以 docs/src/tutorial/query/code01.py 为例:
weapons = ["Ninjato", "Shuriken", "Katana", "Kama", "Kunai", "Naginata", "Yari"] @api.get("/weapons") def list_weapons(request, limit: int = 10, offset: int = 0): return weapons[offset: offset + limit]访问如下 URL:
http://localhost:8000/api/weapons?offset=0&limit=10框架会从查询串中取出offset与limit,按注解转换为int,经校验后传入函数。
这一自动推断机制的核心实现位于 ninja/signature/details.py 的_get_param_type方法。其判定优先级非常清晰:
- 参数类型是
Param子类(如Query(...)、Path(...)),直接用该定义; - 参数名出现在路径模板中,则归为路径参数
Path(...); - 参数是集合类型或 Pydantic 模型,归为
Body(...); - 其余所有情况一律归为
Query(...)——这正是"未标注即查询参数"规则的源码依据。
从源码结构可以推断,这一优先级设计使得路径参数、查询参数、请求体三类数据源能够共存于同一函数签名,互不冲突。
为什么值得使用查询参数注解
原文档指出,查询参数与路径参数享受同样的四重收益:
- 编辑器支持(Editor support):IDE 能基于函数签名给出参数提示、类型补全与重构支持,杜绝魔法字符串;
- 数据解析(Data "parsing"):框架自动把 URL 查询串中的字符串解析为注解声明的 Python 类型;
- 数据校验(Data validation):类型不匹配或缺失必填项时自动返回 422 校验错误;
- 自动文档(Automatic documentation):参数会被自动录入 Swagger UI / ReDoc 的 OpenAPI schema,前端与调用方可直接查看。
一个关键默认行为必须牢记:默认情况下,GET 参数在 HTTP 层全部是字符串;只有当你为函数参数加上类型注解时,Django Ninja 才会将其转换为对应类型并执行校验。若不加注解,参数将按str处理:
@api.get("/weapons") def list_weapons(request, limit, offset): # type(limit) == str # type(offset) == str这一行为的底层逻辑同样在_get_param_type中:当注解缺失时(annotation == self.signature.empty),框架会回退为str(见 ninja/signature/details.py)。
默认值:让查询参数"可省略"
查询参数不属于路径的固定组成部分,因此天然是可选的,可以设置默认值:
@api.get("/weapons") def list_weapons(request, limit: int = 10, offset: int = 0): return weapons[offset : offset + limit]这里默认offset=0、limit=10。于是访问:
http://localhost:8000/api/weapons等价于访问:
http://localhost:8000/api/weapons?offset=0&limit=10而访问:
http://localhost:8000/api/weapons?offset=20时,函数内部拿到的参数值是:
offset=20(URL 显式设置的值)limit=10(默认值兜底)
测试目录 tests/main.py 中/query/int/default路由即验证了该行为:get_query_type_optional_10(request, query: int = 10)在请求/query/int/default时返回"foo bar 10",在请求/query/int/default?query=50时返回"foo bar 50"(见 tests/test_query.py)。
必填与可选参数:遵循 Python 函数参数语义
声明查询参数必填还是可选,与声明普通 Python 函数参数完全一致——没有默认值的参数就是必填的:
weapons = ["Ninjato", "Shuriken", "Katana", "Kama", "Kunai", "Naginata", "Yari"] @api.get("/weapons/search") def search_weapons(request, q: str, offset: int = 0): results = [w for w in weapons if q in w.lower()] return results[offset : offset + 10]在上述例子中,Django Ninja 会始终校验 GET 请求必须携带q参数,而offset是可选整数(缺省为 0)。
源码中,必填/可选是通过Query(...)与Query(default)区分的:...(Ellipsis)表示必填,具体值表示默认值(见 ninja/signature/details.py)。测试用例 tests/test_query.py 给出了完整的行为矩阵:
| 请求路径 | 状态码 | 说明 |
|---|---|---|
/query | 422 | 缺少必填的query参数,返回missing校验错误 |
/query?query=baz | 200 | 正常返回 |
/query?not_declared=baz | 422 | 未声明的参数也会触发缺失校验(声明了query但没传) |
/query/optional | 200 | 可选参数可缺省 |
/query/int?query=42.5 | 422 | int注解拒绝浮点字符串,返回int_parsing错误 |
/query/int?query=foo | 422 | 非数字字符串同样被拒绝 |
错误响应体采用标准格式,例如:
{ "detail": [ { "type": "missing", "loc": ["query", "query"], "msg": "Field required" } ] }这印证了"类型注解即校验规则"的设计:q: str缺失即报missing,query: int收到无法解析的字符串即报int_parsing。
GET 参数类型转换规则
声明多个不同类型的参数时,转换规则各不相同:
from datetime import date @api.get("/example") def example(request, s: str = None, b: bool = None, d: date = None, i: int = None): return [s, b, d, i]str类型:原样透传,不做任何转换;int/float:解析为对应数值类型,无法解析时返回 422(如/query/int?query=42.5对int注解报错);bool类型:下面列出的任意写法(大小写变体同样有效),函数收到的b均为布尔值True;其余写法一律视为False:
http://localhost:8000/api/example?b=1 http://localhost:8000/api/example?b=True http://localhost:8000/api/example?b=true http://localhost:8000/api/example?b=on http://localhost:8000/api/example?b=yesdate类型:既支持标准日期字符串,也支持 unix 时间戳整数:
http://localhost:8000/api/example?d=1577836800 # same as 2020-01-01 http://localhost:8000/api/example?d=2020-01-01上述转换发生在 Pydantic 校验层。Django Ninja 在请求进入时把查询串交给Parser,其中parse_querydict负责把MultiValueDict(Django 的查询字典)转换为普通字典(见 ninja/parser.py),随后由动态构建的QueryModel通过 Pydanticmodel_validate完成类型转换与校验(见 ninja/params/models.py)。整个过程对开发者透明:你只声明类型,转换、校验、报错全由框架完成。
列表型查询参数:重复键的聚合
查询串中可以携带重复键,例如?query=a&query=b&query=c。Django 的QueryDict天然支持多值,Django Ninja 的Parser.parse_querydict会通过data.getlist(key)聚合这些值(见 ninja/parser.py)。配合List注解即可直接接收列表:
from typing import List from ninja import Query @router.get("/query/list") def get_query_list(request, query: List[str] = Query(...)): return ",".join(query)访问/query/list?query=a&query=b&query=c将得到"a,b,c"。还可以声明可空列表:
@router.get("/query/list-optional") def get_query_optional_list(request, query: Optional[List[str]] = Query(None)): if query: return ",".join(query) return query测试矩阵覆盖了列表参数的必填/可选行为(见 tests/test_query.py)。在源码层面,detect_collection_fields会识别注解中的集合类型并标记为"列表字段",parse_querydict据此决定使用getlist还是单值提取(见 ninja/signature/details.py)。
使用 Schema 封装查询参数
当查询参数增多时,逐个声明函数参数会让签名臃肿。Django Ninja 支持把查询参数封装进一个 Pydantic Schema,用Query[...]泛型标记:
import datetime from typing import List from pydantic import Field from ninja import Query, Schema class Filters(Schema): limit: int = 100 offset: int = None query: str = None category__in: List[str] = Field(None, alias="categories") @api.get("/filter") def events(request, filters: Query[Filters]): return {"filters": filters.dict()}关键点说明:
Query[Filters]表示"从查询串解析出Filters实例",函数内部通过filters.limit、filters.offset等属性访问各字段;alias映射外部参数名:URL 中使用categories(如/filter?categories=a&categories=b),Schema 字段名却是category__in——这在调用方参数名与内部实现解耦时非常有用,例如对接前端固定命名或 Django ORM 的__in查找语法;- Schema 内同样遵循默认值/必填语义:
limit: int = 100可选且默认 100,offset: int = None可选且默认 None; - 从源码看,Schema 型查询参数通过
_args_flatten_map的扁平化映射把 Schema 字段展开为查询键,并在QueryModel.get_request_data中经parse_querydict收集数据后交给 Pydantic 校验(见 ninja/signature/details.py 与 ninja/params/models.py)。
嵌套的 Pydantic 模型同样支持扁平化展开,字段会映射为扁平键名(FLATTEN_PATH_SEP分隔,见 ninja/signature/details.py),这为组织超多参数的复杂查询提供了可扩展方案。
参数级约束与别名
除了 Schema 封装,还可以用Query()函数在单个参数上施加更细的约束(见 ninja/params/functions.py 与 ninja/params/models.py):
@api.get("/search") def search( request, q: str = Query(..., min_length=3, max_length=50, description="搜索关键词"), page: int = Query(1, ge=1, description="页码,从 1 开始"), size: int = Query(10, ge=1, le=100, description="每页条数"), sort: str = Query("name", pattern="^(name|date|rating)$"), categories: List[str] = Query(None, alias="cat"), ): ...可用约束包括:gt/ge/lt/le(数值范围)、min_length/max_length(字符串长度)、pattern(正则)、alias(参数别名)、title/description/example/examples(OpenAPI 文档展示)、deprecated(标记废弃)以及include_in_schema(是否显示在文档中)。这些约束既作用于运行时校验,也会同步写入自动生成的 OpenAPI schema,让文档与校验规则永远一致。
自动化文档与复杂过滤
由于查询参数声明完全基于类型注解,Swagger UI / ReDoc 会自动为每个查询参数生成参数说明、类型、默认值、必填标记与校验约束,无需手写任何文档代码。这正是原文档强调的"Automatic documentation"收益在 OpenAPI 层的落地。
对于更复杂的过滤场景(如组合多个条件、支持__in等 Django ORM 风格查找),推荐进一步阅读 过滤指南(Filtering);查询参数配合过滤功能,可构建出既类型安全又文档完备的检索 API。
小结
| 场景 | 写法 | 行为 |
|---|---|---|
| 基础查询参数 | def f(request, limit: int = 10) | 自动识别为查询参数,缺省用默认值 |
| 必填参数 | def f(request, q: str) | 缺失返回 422 |
| 无注解参数 | def f(request, x) | 按str处理 |
| 布尔参数 | b: bool | 1/True/true/on/yes(含大小写变体)→True |
| 日期参数 | d: date | 支持2020-01-01或 unix 时间戳 |
| 列表参数 | q: List[str] | 重复键自动聚合 |
| Schema 封装 | filters: Query[Filters] | 结构化访问 + alias 映射外部名 |
| 单参数约束 | q: str = Query(..., min_length=3) | 运行时校验 + OpenAPI 文档同步 |
Django Ninja 的查询参数体系完全由类型注解驱动:解析入口是 ninja/parser.py 的parse_querydict,参数分类逻辑在 ninja/signature/details.py 的_get_param_type,数据模型在 ninja/params/models.py 的QueryModel,而完整的必填/可选/类型/列表行为矩阵可在 tests/test_query.py 中查阅验证。掌握这套机制,你就能以最小的样板代码写出校验严格、文档自动生成、前后端契约清晰的查询参数层。
- 后端
- API设计
【免费下载链接】django-ninja
💨 Fast, Async-ready, Openapi, type hints based framework for building APIs
相关推荐
VSS SDR/Envoy路由解析:视频流与代理之间如何优雅地传递请求
VSS SDR/Envoy路由解析:视频流与代理之间如何优雅地传递请求 在 VSS(Video Search and Summarization,视频搜索与摘要
人工智能大模型AI AgentRAG计算机视觉视频后端FastAPI 查询参数(Query Parameters)完全指南:声明、类型转换与必填校验
FastAPI 查询参数 Query Parameters 完全指南:声明、类型转换与必填校验 在 FastAPI 中,只要在路径操作函数里声明的参数不是路径参
后端Web框架API设计FastAPI 查询参数(Query Parameters)完全指南:自动解析、类型转换与必填校验
FastAPI 查询参数(Query Parameters)完全指南:自动解析、类型转换与必填校验 导读 本文基于 FastAPI 官方文档的韩文教程 docs
后端Web框架API设计
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考