Skip to content

Fastapi

简介

FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 3.6+ 并基于标准的 Python 类型提示。 关键特性: 快速:可与 NodeJS 和 Go 比肩的极高性能(归功于 Starlette 和 Pydantic)。最快的 Python web 框架之一。 高效编码:提高功能开发速度约 200% 至 300%。 更少 bug:减少约 40% 的人为(开发者)导致错误。 智能:极佳的编辑器支持。处处皆可自动补全,减少调试时间。 简单:设计的易于使用和学习,阅读文档的时间更短。 简短:使代码重复最小化。通过不同的参数声明实现丰富功能。bug 更少。 健壮:生产可用级别的代码。还有自动生成的交互式文档。 标准化:基于(并完全兼容)API 的相关开放标准:OpenAPI (以前被称为 Swagger) 和 JSON Schema。

swagger

基于开放标准 用于创建 API 的 OpenAPI 包含了路径操作,请求参数,请求体,安全性等的声明。 使用 JSON Schema (因为 OpenAPI 本身就是基于 JSON Schema 的)自动生成数据模型文档。 经过了缜密的研究后围绕这些标准而设计。并非狗尾续貂。 这也允许了在很多语言中自动生成客户端代码 访问 /docs

相关配置

debug: bool = False
docs_url: str = "/docs"
openapi_prefix: str = ""
openapi_url: str = "/openapi.json"
redoc_url: str = "/redoc"
title: str = "FastAPI"
version: str = "0.1.0"
disable_docs: bool = False

将docs_url,redoc_url,openapi_url均设为None的情况下,disable_docs为true才会生效,也就是不能访问docs。

依赖注入

在很多的时候,不同的方法有公共的参数,这时候就可以使用Depends设置公共参数并且将其注入到请求中。通过这样做到复用以及代码的简化,并且其中还能对数据进行一些校验。一个方法中也可以使用多个依赖,灵活组合。只能在包含在查询参数GET里面。其中有一个use_cache的参数,其作用为 多个依赖有共同的子依赖的时候,多个request访问,则会将这个子依赖放入缓存中

# @app.get("/")
# async def read_items(commons: dict = Depends(XXX))
# XXX可以 方法,类等。


# 依赖于方法       
async def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}

@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
    commons.update({'小钟': '同学'})
    return commons

#依赖类       
class Data:
    def __init__(self, q: str , skip: int, limit: int):
        # if q == "shepi":
        #     raise HTTPException(status_code=400, detail="X-Token header invalid")
        self.q = q
        self.skip = skip
        self.limit = limit
@app01.post("/")
def get(commons: Data = Depends(Data)):
    print(commons.q)
    return {"id": "嘤嘤嘤"}

# 多重嵌套
def query_extractor(q: str = None):
    return q

def query_or_cookie_extractor(
        q: str = Depends(query_extractor), last_query: str = Cookie(None)
):
    if not q:
        return last_query
    return q

        @app.get("/items/")
        async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)):
            return {"q_or_cookie": query_or_default}
# 多依赖
async def verify_token(x_token: str = Header(...)):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")

async def verify_key(x_key: str = Header(...)):
    if x_key != "fake-super-secret-key":
        raise HTTPException(status_code=400, detail="X-Key header invalid")
    return x_key

@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
    return [{"item": "Foo"}, {"item": "Bar"}]

写法

commons=Depends(CommonQueryParams) 
commons: CommonQueryParams = Depends(CommonQueryParams)
commons: CommonQueryParams = Depends()
commons = Depends(CommonQueryParams)

依赖

app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])

# 分api
from fastapi import ApiRouter
router = ApiRouter(dependencies=[])

请求相关

header/cookie/query

大多数标准的headers用 "连字符" 分隔,也称为 "减号" (-)。 但是像 user-agent 这样的变量在Python中是无效的。 因此, 默认情况下, Header 将把参数名称的字符从下划线 (_) 转换为连字符 (-) 来提取并记录 headers. 同时,HTTP headers 是大小写不敏感的,因此,因此可以使用标准Python样式(也称为 "snake_case")声明它们。 因此,您可以像通常在Python代码中那样使用 user_agent ,而不需要将首字母大写为 User_Agent 或类似的东西。 如果出于某些原因,你需要禁用下划线到连字符的自动转换,设置Header的参数 convert_underscores 为 False:

from typing import Optional

from fastapi import FastAPI, Header

app = FastAPI()


@app.get("/items/")
async def read_items(user_agent: Optional[str] = Header(None)):
    return {"User-Agent": user_agent}

@app.get("/items/")
async def read_items(ads_id: Optional[str] = Cookie(None)):
    return {"ads_id": ads_id}

路径参数

@app.delete("/{id}")
async def get(id: int):
    print(id)
return {"mes": "delete"}

PS: 如果有一个方法 路径为 /name 另一个是 /{name},访问/name写在前面的那个方法将会被访问。

GET参数

可以设置请求参数中必需与不必需的参数,以及设置其默认值。 如果未表明 参数名:optional[类型] = 默认值,那么这个参数就是必须的。

from typing import Optional
@app.get("/xx")
def read_item(page: int = 0, limit: Optional[int] = 10):

PS: 对于bool类型数据,1,yes,on,true ,以及其中字母任意变换大小写均为True。no,false同理

Json

POST或者其他方式的请求参数,使用pydantic可以将json数据直接序列化为对象,对其中数据也可以设置默认值以及可选值.可以多个类深层次嵌套.

from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

@app.post("/")
async def get( item:Item,page: bool):
    print(item.name)
    print(page)
return {"mes": "get"}

form

要使用表单,需预先安装 python-multipart。 例如,pip install python-multipart。 声明表单体要显式使用 Form ,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。

from fastapi import FastAPI, Form

app = FastAPI()

@app.post("/login/")
async def login(username: str = Form(...), password: str = Form(...)):
    return {"username": username}

数据类型

常见的有str,int,bool,float UUID:

  • 一种标准的 "通用唯一标识符" ,在许多数据库和系统中用作ID。
  • 在请求和响应中将以 str 表示。

datetime.datetime:

  • 一个 Python datetime.datetime.
  • 在请求和响应中将表示为 ISO 8601 格式的 str ,比如: 2008-09-15T15:53:00+05:00.

datetime.date:

  • Python datetime.date.
  • 在请求和响应中将表示为 ISO 8601 格式的 str ,比如: 2008-09-15.

datetime.time:

  • 一个 Python datetime.time.
  • 在请求和响应中将表示为 ISO 8601 格式的 str ,比如: 14:23:55.003.

datetime.timedelta:

  • 一个 Python datetime.timedelta.
  • 在请求和响应中将表示为 float 代表总秒数。
  • Pydantic 也允许将其表示为 "ISO 8601 时间差异编码", 查看文档了解更多信息

frozenset:

  • 在请求和响应中,作为 set 对待:
  • 在请求中,列表将被读取,消除重复,并将其转换为一个 set。
  • 在响应中 set 将被转换为 list 。
  • 产生的模式将指定那些 set 的值是唯一的 (使用 JSON 模式的 uniqueItems)。

bytes:

  • 标准的 Python bytes。
  • 在请求和相应中被当作 str 处理。
  • 生成的模式将指定这个 str 是 binary "格式"。

Decimal:

  • 标准的 Python Decimal。
  • 在请求和相应中被当做 float 一样处理。

任务

在请求函数中需要执行一些耗时操作,类似于django使用celery那样,为fastapi增加的后台任务。也可以将耗时任务依赖注入到一个方法里面,然后这个方法被请求函数依赖。 后台任务对应的那个函数最好不要有依赖项。

后台任务

from fastapi import BackgroundTasks, FastAPI

def task(email: str, message: str = ""):
    time.sleep(5)
    print(email)
    print(message)

@app.get("/")
async def get(backtask: BackgroundTasks):
    email = "999"
    message = "task"
    backtask.add_task(task, email,message)
return {"mes": "get"}

中间件中的后台任务

from starlette.background import BackgroundTask
from somewhere import functionA
@app.middleware("http")
async def middleware(request: Request, call_next):
........(do something)
response.background = BackgroundTask(functionA, arg)
return response

定时任务

from fastapi_restful.tasks import repeat_every

app = FastAPI()


@app.on_event("startup")
@repeat_every(seconds=60*60)  # 1 hour
def remove_expired_tokens_task() -> None:
    with open(r"1.txt","a") as f:
        f.write("9999")
print("this is 定时任务!")

中间件

中间价:即是在请求前后加入某些操作。

使用方法

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    print("this is before requests")  # 接收到request之前
    response = await call_next(request)
    print("this is after request before response")  # 请求方法处理之后,返回之前
    response.headers["X-Process-Time"] = str(999)
    return response

#  未经过中间件的过滤,可以直接返回信息:
from starlette.responses import JSONResponse
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    print("this is before requests")  # 接收到request之前
    q = request.get("q")
    print(q)
    if not q:
        return  JSONResponse({"code":250})
    response = await call_next(request)
    print("this is after request before response")  # 请求方法处理之后,返回之前
    response.headers["X-Process-Time"] = str(999)
    return response

高级中间件

https://www.starlette.io/middleware/

第三方

增加查看请处理时间的中间件:

from fastapi_restful.timing import add_timing_middleware, record_timing
add_timing_middleware(app, record=logger.info, prefix="app", exclude="untimed")

一次消费的问题

https://juejin.cn/post/6972031031155097631

在fastapi中request对象,只能在中间件中被消耗一次,然后某个中间件中对request做了操作,不能传递到下一个中间件,并且两个中间件同时使用request的信息,两个中间件的request不是一个对象,也可能会报错。中间件中可以使用request.state.key=value将想要传递的信息,传递到处理函数中。处理函数中同样方式取用。

针对两个对象不能同时使用的request,解决办法是重写路由,指定fastapi的请求路由,在处理之前或者后加入自己的逻辑代码,其中也能得到中间件添加的数据。如: class ContextIncludedRoute(APIRoute):

def get_route_handler(self) -> Callable:
    original_route_handler = super().get_route_handler()

    async def custom_route_handler(request: Request):
        print("here is router")
        print(request.state.role)
        #逻辑代码
        #不符合,可以直接返回response,例如JsonResponse
        response: Response = await original_route_handler(request)
        return response
    return custom_route_handler

# fastapi的app指定router
# app.router.route_class = ContextIncludedRoute

启停服务

启动服务的时刻可能去初始化数据库连接or do something;

@app.on_event("startup")
async def startup_event():
    print("before startup")


@app.on_event("shutdown")
async def shutdown_event():
    print("before shutdown")