设置查询参数是否为必填

使用Annotated装饰查询参数

不设置default值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@app.get("/validation")
async def async_root(str_param: Annotated[str | None, Query(min_length = 3,
                                                            max_length = 20,
                                                            pattern = '^[a-zA-Z0-9]+$',
                                                            description = 'This is a string parameter')]):
    """
    如果不设置 str_param = None, 则 str_param 为必填
    """
    ret = {"str_list": str_param}
    if str_param is not None:
        h = hashlib.sha3_512(str_param.encode('utf-8')).hexdigest()
        ret.update({"sha3_512": h})
    else:
        ret.update({"uuid": uuid.uuid4().hex})
    return ret

img

不设置默认值

img

生成的文档中参数为必填

设置default值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@app.get("/validation1")
async def async_root1(str_param: Annotated[str | None, Query(min_length = 3,
                                                             max_length = 20,
                                                             pattern = '^[a-zA-Z0-9]+$',
                                                             description = 'This is a string parameter')] = None):
    """
    设置 str_param = None 表明允许 str_param 为空(非必填)
    """
    ret = {"str_list": str_param}
    if str_param is not None:
        h = hashlib.sha3_512(str_param.encode('utf-8')).hexdigest()
        ret.update({"sha3_512": h})
    else:
        ret.update({"uuid": uuid.uuid4().hex})
    return ret

img

设置 default 值

img

生成的文档中参数为非必填

string 类型的默认值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@app.get("/validation2")
async def async_root2(str_param: Annotated[str, Query(min_length = 3,
                                                      max_length = 20,
                                                      pattern = '^[a-zA-Z0-9]+$',
                                                      description = 'This is a string parameter')] = 'default_value'):
    """
    此时 str_list 有默认值,非必填
    """
    ret = {"str_list": str_param}
    if str_param is not None:
        h = hashlib.sha3_512(str_param.encode('utf-8')).hexdigest()
        ret.update({"sha3_512": h})
    else:
        ret.update({"uuid": uuid.uuid4().hex})
    return ret

img

默认值为 string 类型

使用ellipsis作为默认值

错误写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@app.get("/validation3")
async def async_root3(str_param: Annotated[str, Query(min_length = 3,
                                                      max_length = 20,
                                                      pattern = '^[a-zA-Z0-9]+$',
                                                      description = 'This is a string parameter')] = ...):
    """
    此时 str_list = ... 生成的文档中,str_list 并非为必填
    并且当发送请求:
        curl -X 'GET' 'http://127.0.0.1:18081/validation3' -H 'accept: application/json'
    后台会报错:
        ValueError: [TypeError("'ellipsis' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')]
    """
    ret = {}
    if str_param is not ...:
        ret = {"str_list": str_param}
        h = hashlib.sha3_512(str_param.encode('utf-8')).hexdigest()
        ret.update({"sha3_512": h})
    else:
        ret.update({"uuid": uuid.uuid4().hex, 'error': 'str_list is ...'})
    return ret


@app.get("/validation4")
async def async_root4(str_param: Annotated[str | None, Query(min_length = 3,
                                                             max_length = 20,
                                                             pattern = '^[a-zA-Z0-9]+$',
                                                             description = 'This is a string parameter')] = ...):
    """
    发送:curl -X 'GET' 'http://127.0.0.1:18081/validation4' -H 'accept: application/json'时
    后台会报错:ValueError: [TypeError("'ellipsis' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')]
    """
    ret = {}
    if str_param is not ...:
        ret = {"str_list": str_param}
        h = hashlib.sha3_512(str_param.encode('utf-8')).hexdigest()
        ret.update({"sha3_512": h})
    else:
        ret.update({"uuid": uuid.uuid4().hex, 'error': 'str_list is ...'})
    return ret

使用 Query(xxxx) = …的写法均会引起错误,此种写法来源于 FastAPI 官网的文档:

使用ellipsis设置必填参数

img

错误的文档示例

关于此错误写法笔者已经给 FastAPI 提了 Issue:https://github.com/fastapi/fastapi/issues/12313

img

相关开发者回复

正确写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@app.get("/validation5")
async def async_root5(str_param: Annotated[str, Query(default = ...,
                                                      min_length = 3,
                                                      max_length = 20,
                                                      pattern = '^[a-zA-Z0-9]+$',
                                                      description = 'This is a string parameter')]):
    """
    使用 Query(default = ...) 的方式设置 str_list 为必填参数
    不使用 default 时参数也会默认为必填
    Remember that in most of the cases, when something is required,
    you can simply omit the default, so you normally don't have to use ...
    """
    ret = {"str_list": str_param}
    h = hashlib.sha3_512(str_param.encode('utf-8')).hexdigest()
    ret.update({"sha3_512": h})
    ret.update({"uuid": uuid.uuid4().hex})
    return ret

img

使用ellipsis作为默认值来设定必填属性

官网文档中关于必填参数可以为None的错误描述

官网文档描述链接

img

如果一个参数为必填同时还可以为 None 的错误描述

关于这个问题也提了 Issue 给到 FastAPI 团队:Question about “Required, can be None”

img

问题得到了 FastAPI 开发者的确认

关于这个问题的讨论帖:Discussion,感兴趣的可以自行查看。

Annotated搭配 Query(default=xxxx)带来的问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@app.get("/validation1_1")
async def async_root1_1(str_param: Annotated[str | None, Query(default = 'test',
                                                               min_length = 3,
                                                               max_length = 20,
                                                               pattern = '^[a-zA-Z0-9]+$',
                                                               description = 'This is a string parameter')]):
    ret = {"str_list": str_param}
    if str_param is not None:
        h = hashlib.sha3_512(str_param.encode('utf-8')).hexdigest()
        ret.update({"sha3_512": h})
    else:
        ret.update({"uuid": uuid.uuid4().hex})
    return ret

img

使用 Query 的 default 参数设置默认值

运行时会报错:

1
AssertionError: `Query` default value cannot be set in `Annotated` for 'str_param'. Set the default value with `=` instead.

img

Amazon Q AI 给出的答复

总之在使用Annotated搭配 Query 时需要注意编码细节。

列表查询参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@app.get("/validation7")
async def async_root7(str_list: Annotated[List[str] | None, Query(min_length = 1)]):
    """
    列表查询参数
    curl -X 'GET' 'http://127.0.0.1:18081/validation7?str_list=aaa&str_list=bbb&str_list=ccc' -H 'accept: application/json'
    """
    if str_list is not None:
        return {'items_count': len(str_list), 'items': str_list}
    else:
        return {'message': 'no items'}

img

运行效果

路径参数使用alias别名的注意点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@app.get('/{item_id}')
async def async_root(item_id: Annotated[int, Path(title = 'Item ID', alias = 'xid')]):
    """
    GithubIssue: https://github.com/fastapi/fastapi/issues/12323
    当带有 alias 时,接口无法正常工作:
        curl http://127.0.0.1:18081/123
        {"detail":[{"type":"missing","loc":["path","xid"],"msg":"Field required","input":null}]}
    """
    return {'item_id': item_id}

@app.get('/{xid_2}')
async def async_root2(item_id: Annotated[int, Path(title = 'Item ID', alias = 'xid_2')]):
    """
    The title parameter you're referring to in the Path function is part of FastAPI's path parameter customization. While it doesn't directly appear in the Swagger UI documentation, it serves an important purpose in the OpenAPI schema.
    Here's what the title parameter does:
        OpenAPI Schema: The title is included in the OpenAPI (formerly Swagger) schema that FastAPI generates.
                        This schema is a machine-readable description of your API, which can be used by various tools and clients.
        JSON Schema: In the context of JSON Schema (which OpenAPI uses), the title provides a human-readable name for the parameter.
                     It's meant to be a brief description of the parameter.
        API Documentation: While not directly visible in the Swagger UI,
                           the title can be used by other documentation tools that consume the OpenAPI schema to provide more descriptive names for parameters.
        Code Generation: Tools that generate client code from OpenAPI schemas might use the title to create more meaningful variable or property names.
    """
    return {'item_id': item_id}

相关 issue 讨论帖:Should the alias parameter in the Path method be removed?

img