1+1=10

扬长避短 vs 取长补短

Python FastAPI入门笔记(二)

内容小记:

  • 路径参数Path Parameter使用(类型验证与转换、包含/的文件路径、预定义值,Annotated使用与int范围限定)
  • 查询参数Query Parameter使用(默认值、可选参数、类型验证与转换、Annotated使用与str格式限定)

注:本文所有代码在python3.11下验证

路径参数(Path Parameter)

路径参数或变量 可以使用与Python格式化字符串相同的语法来声明:

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
async def item(item_id):
    return {"item_id": item_id} 

而后就可以通过 127.0.0.1/items/1+1=10 进行访问。浏览器显示内容如下:

{
    "item_id": "1+1=10"
}

类型验证与转换

上面的例子中,item_id内容比较随意。借助FastAPI底层使用的Pydantic,可以指定类型(比如int):

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
async def item(item_id: int):
    return {"item_id": item_id} 

此时用浏览器访问 127.0.0.1/items/1+1=10,将会遇到错误

{
    "detail": [
        {
            "type": "int_parsing",
            "loc": [
                "path",
                "item_id"
            ],
            "msg": "Input should be a valid integer, unable to parse string as an integer",
            "input": "1+1=10",
            "url": "https://errors.pydantic.dev/2.5/v/int_parsing"
        }
    ]
}

而 127.0.0.1/items/1 将会看到正确的结果

{
    "item_id": 1
}

同样,试图访问 127.0.0.1/items/1.2 也会将会看到错误的结果,要兼容int和float,需要改为

@app.get("/items/{item_id}")
async def item(item_id: float):
    return {"item_id": item_id} 

这些校验工作都是由Pydantic完成的。

顺序

使用路径操作函数时,如果由固定路径需要处理。要确保先声明固定路径:

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/1+1=10")
async def item0():
    return {"item_id": "1+1=10"} 

@app.get("/items/{item_id}")
async def item(item_id: float):
    return {"item_id": item_id} 

文件路径(路径转换符)

如果要提取 127.0.0.1:8000/files/aa/bb.txt 中的路径aa\bb.txt 怎么办?

这么写是不行的

from fastapi import FastAPI

app = FastAPI()

@app.get("/files/{fp}")
async def get_file(fp):
    return {"file": fp}

需要像下面这样,指定转换符path,提取变量才能包含/符号:

from fastapi import FastAPI

app = FastAPI()

@app.get("/files/{fp:path}")
async def get_file(fp):
    return {"file": fp}

限定字符串取值(预定值)

如何限定,只允许路径变量取几个特定值,比如部门,只能在hr、finance、rd中选择。

首先想到的是,这么尝试:

from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get("/departments/{department_name}")
async def deal_with_department(department_name):
    if department_name not in ["hr", "finance", "rd"]:
        raise HTTPException(status_code=404, detail="Department not found")
    return {"name": department_name}

工作起来没问题,不存在的部门直接报404异常。

不过,FastAPI中是这么用的:

from enum import Enum
from fastapi import FastAPI, HTTPException

app = FastAPI()

class DepartmentName(str, Enum):
    hr = "hr"
    finance = "finance"
    rd = "rd"

@app.get("/departments/{department_name}")
async def deal_with_department(department_name : DepartmentName):
    return {"name": department_name}

使用Enum的好处是,通过127.0.0.1:8000\docs访问文档时,更友好:

fastapi-docs-enum

限定路径参数值范围

如何要确保有效的路径参数值大小10~100之间,该怎么办?

  • https://fastapi.tiangolo.com/reference/parameters/

FastAPI利用的python中的Annotated功能,来给类型增加注解信息:

from fastapi import FastAPI, Path
from typing import Annotated

app = FastAPI()

@app.get("/items/{item_id}")
async def get_item(item_id: Annotated[int, Path(title="The ID of the item to get", ge=10, le=100)]):
    return {"item": item_id} 
  • 127.0.0.1/items/1 无效
  • 127.0.0.1/items/101 无效
  • 127.0.0.1/items/20 有效

注意:注解信息中用的Path,注意和下面查询参数中的Query区分

查询参数(Query Parameter)

查询参数是键值对,位于URL的?之后。不同的参数用&进行分隔。

FastAPI的处理逻辑是:路径处理函数中声明的参数,如果不是路径参数,将自动解释为查询参数。

获取查询参数

比如,要获取 127.0.0.1:8001/items/?item_id=111 中的查询参数,可以:

from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get("/items/")
async def get_item(item_id: int):
    return {"item": item_id}

默认值

查询参数可以指定默认值,比如item_id: int = 1

from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get("/items/")
async def get_item(item_id: int = 1):
    return {"item": item_id}

这样一来,下面两个写法等价:

  • 127.0.0.1:8001/items/
  • 127.0.0.1:8001/items/?item_id=1

可选参数

注意与默认值概念区分。将默认值设置为None来声明可选查询参数:

@app.get("/items/")
async def get_item(item_id: int | None = None):
    if item_id:
        return {"item": item_id} 
    return "items"

这样一来:

  • 127.0.0.1:8001/items/
  • 127.0.0.1:8001/items/?item_id=1

不再等价。

注意:|说明 item_id 可以是int,也可以是None类型。

类型校验与转换

比如,下面的例子:我们定义参数bool类型:

from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get("/items/{item_id}")
async def get_item(item_id, d: bool):
    item = {"item_id": item_id}
    if d:
        item.update({"description": "Demo from 1+1=10"})
    return item

下面的都是等价的:

  • 127.0.0.1:8001/items/111?d=1
  • 127.0.0.1:8001/items/111?d=on
  • 127.0.0.1:8001/items/111?d=yes
  • 127.0.0.1:8001/items/111?d=true
  • 127.0.0.1:8001/items/111?d=True
  • ...

注意:0 对应False,1对应True。其他数字不会转成bool。

字符串验证

如何要确保有效的查询参数的长度在3~10之间,该怎么办?

FastAPI利用的python中的Annotated功能,来给类型增加注解信息(注意,对于查询参数,用的Query):

from fastapi import FastAPI, Query
from typing import Annotated

app = FastAPI()

@app.get("/items/")
async def get_item(item_id: Annotated[str, Query(max_length=10, min_length=3)]):
    return {"item": item_id}
  • 127.0.0.1:8001/items/?item_id=1 无效
  • 127.0.0.1:8001/items/?item_id=11111111111111111111 无效
  • 127.0.0.1:8001/items/?item_id=1111 有效

使用Annotated注解时,依然可以将参数设置为可选:

@app.get("/items/")
async def get_item(item_id: Annotated[str | None, Query(max_length=10, min_length=3)]=None):
    if item_id:
        return {"item": item_id} 
    return "items"

另外,还可以使用正则表达式来限定查询参数的格式,比如:

from fastapi import FastAPI, Query
from typing import Annotated

app = FastAPI()

@app.get("/items/")
async def get_item(item_id: Annotated[str | None, Query(max_length=10, min_length=3, pattern=r'\w+\W+\w+')]=None):
    if item_id:
        return {"item": item_id} 
    return "items"
  • 127.0.0.1:8001/items/?item_id=11-11 有效
  • 127.0.0.1:8001/items/?item_id=1111 无效

参考

  • https://fastapi.tiangolo.com
  • https://asgi.readthedocs.io/en/latest/introduction.html
  • https://www.starlette.io
  • https://docs.pydantic.dev/latest/
  • https://fastapi.tiangolo.com/reference/parameters/

Python web

Comments