用了fastapi还需要nginx_FastApi速记

路由

from fastapi import Depends, FastAPI, Header, HTTPException

from .routers import items, users

app = FastAPI()


async def get_token_header(x_token: str = Header(...)):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")


app.include_router(users.router)
app.include_router(
    items.router,
    prefix="/items",
    tags=["items"],
    dependencies=[Depends(get_token_header)],
    responses={404: {"description": "Not found"}},
)

请求参数

所有的请求参数在被合理设置之后,都会以文档的形式在docs/中显示出来。如果需要更多的文档,可以使用summarydescriptionresponse_description。这一部分见下面一点的地方。

路径参数

最简单的方式,和flask一样,可以通过给函数设置默认值,来设置路径参数的默认值。

: int说明这个路径参数是int型,而不是float或者string,bool。

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

如果我们希望提供一个枚举类型(有限选择的)参数,可以这么做

class ModelName(str, Enum):
    alexnet = "alexnet"
    resnet = "resnet"
    lenet = "lenet"


@router.get("/model/{model_name}")
async def get_model(model_name: ModelName):
    if model_name == ModelName.alexnet:
        return {"model_name": model_name, "message": "Deep Learning FTW!"}

    if model_name.value == "lenet":
        return {"model_name": model_name, "message": "LeCNN all the images"}

    return {"model_name": model_name, "message": "Have some residuals"}

如果我们希望获得一个路径(比如文件的路径),则需要这么做。

结尾部分的 :path 说明该参数应匹配任意的路径

@router.get("/files/{file_path:path}")
async def read_file(file_path: str):
    return {"file_path": file_path}

查询参数

路径参数和查询参数都是函数的入参,没有强制的顺序要求。这些入参先匹配路径参数(通过名称被检测到),再匹配查询参数。

如果设置成=None,则依旧说明是可选的。另一种表达可选的方式: Optional[str]

from typing import Optional

db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]

@router.get("/items/")
async def read_item(skip: int = 0, limit: int = 10, q: str = None):
    return db[skip : skip + limit]

如果是bool类型的查询参数,则会自动将1,true,True等解析为True。false同理。

如果我们希望接收一个列表类型的值,可以如下这么做。

@app.get("/items/")
async def read_items(q: List[str]):
    query_items = {"q": q}
    return query_items

然后,输入如下网址:

http://localhost:8000/items/?q=foo&q=bar

q 会以一个 Python list 的形式接收到查询参数q 的多个值(foobar)。

请求体

请求体也是一个入参,函数参数将依次按如下规则进行识别:

  • 如果在路径中也声明了该参数,它将被用作路径参数。
  • 如果参数属于单一类型(比如 intfloatstrbool 等)它将被解释为查询参数。
  • 如果参数的类型被声明为一个 Pydantic 模型,它将被解释为请求体

请求体通过声明一个BaseModel,来表明格式。

from pydantic import BaseModel

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

@router.put("/items/{item_id}")
async def create_item(item_id: int, item: Item):
    return {"item_id": item_id, **item.dict()}

BaseModel也可以进行组合和继承。

请求体的字段

from typing import Optional

from fastapi import Body
from pydantic import BaseModel, Field

class Item(BaseModel):
    name: str
    description: Optional[str] = Field(
        None, title="The description of the item", max_length=300
    )
    price: float = Field(..., gt=0, description="The price must be greater than zero")
    tax: Optional[float] = None


@router.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results

注意,Field 是直接从 pydantic 导入的,而不是像其他的(QueryPathBody 等)都从 fastapi 导入。但是在技术细节上,他们都继承了相同的对象,也返回了相同的类,所以使用上也非常相似。

更进一步的入参检查

路径参数可以使用Path,查询参数可以使用Query,请求体可以用Body。具体用法参见源码。概述用法下见:

@router.get("/items/")
async def read_items(q: str = Query(..., min_length=3)): 
    # ...表示必填,None表示选填
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Path,Query可选的参数包括(部分):

  • title
  • description
  • alias,别名,例如 alias="item-query"
  • regex="^fixedquery$",正则匹配
  • deprecated=True 是否弃用
  • gt:大于(greater than)
  • lt:小于(less than)
  • ge:大于等于(greater than or equal)
  • le:小于等于(less than or equal)

pydantic提供的其他类型

https://pydantic-docs.helpmanual.io/usage/types/#validating-the-first-value

大概包括,选项类型、URl、颜色、Json、日期、uuid、Decimal、能在转成JSON时自动打码的Password类型、信用卡号、严格的格式(不自动转换)等等

为模式添加样例

在声明模式的时候添加样例

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

    class Config:
        schema_extra = {
            "example": {
                "name": "Foo",
                "description": "A very nice Item",
                "price": 35.4,
                "tax": 3.2,
            }
        }

为每个字段添加样例

class Item(BaseModel):
    name: str = Field(..., example="Foo")
    description: Optional[str] = Field(None, example="A very nice Item")
    price: float = Field(..., example=35.4)
    tax: Optional[float] = Field(None, example=3.2)

在使用的时候添加样例

@app.put("/items/{item_id}")
async def update_item(
    item_id: int,
    item: Item = Body(
        ...,
        example={
            "name": "Foo",
            "description": "A very nice Item",
            "price": 35.4,
            "tax": 3.2,
        },
    ),
):
    results = {"item_id": item_id, "item": item}
    return results

Cookies

cookie和上面的都一样,都写在函数的入参之中,例如这样

from fastapi import Cookie

@router.get("/items/")
async def read_items(ads_id: int = Cookie(...)):
    return {"ads_id": ads_id}

这样对cookie的管理好不方便。之后要去看看有没有别的管理模式

Headers

Header也一样,我的妈

from fastapi import Header

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

Header有一点点不同的地方在于,因为一般的HTTP头的key,都是大写字母打头,中横线连接。这意味着每个Header的key,在使用的时候,都需要alias。所以Header会对大写和中横线自动转换。

Header也可以接收列表型的值,也是通过相同的key实现的,类似查询参数。

返回值

通过模式返回

在装饰器中,声明自己返回时,所引用的模式(Schema)(也就是一个BaseModel)。这样可以直接返回一个对象,程序会自动会这个对象应用这个模式。

from pydantic import BaseModel, EmailStr

class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str = None


@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
    return user

当对象有中默认值时,返回的数据也会有默认值。如果不希望有这一特性,则可以通过在装饰器中设置response_model_exclude_unset=True来禁用这一特性。

文件

上传

以下两种方式都可以上传文件,区别在于:前一种将文件直接读在了内存之中,后者则存在磁盘之中,并提供了文件对象。

from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/files/")
async def create_file(file: bytes = File(...)):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
    return {"filename": file.filename}

UploadFile 有以下属性:

  • filename: str 上传的文件的文件名 (比如 myimage.jpg).
  • content_type: str 内容的类型 (MIME type / media type) (e.g. image/jpeg).
  • file: 一个SpooledTemporaryFile。这是一个类似Python File的对象,可以直接把这个传递给别的函数。

UploadFile 有以下异步方法:

  • write(data): 将字符串或者字节流写入文件。
  • read(size): 读取指定大小的字节流或字符数。
  • seek(offset): 前往指定某个偏移量
  • 比如await myfile.seek(0)将前往文件的开头
  • 当调用await myfile.read()一次后,再次读取内容时,这个函数会特别好用
  • close(): 关闭文件

下载

首先如果想异步调用文件下载接口,需要安装额外的依赖aiofiles

然后就很简单了

@file_router.get("/files/{file_path:path}", tags=["file"])
async def get_file(file_path: str):
   return FileResponse(file_path)

错误处理

raise HTTPException

fastapi提供了标准的HTTPException,可以提供指定状态码,detail(一切可以JSON化的东西),响应头等。

from fastapi import HTTPException

items = {"foo": "The Foo Wrestlers"}

@router.get("/items-header/{item_id}")
async def read_item_header(item_id: str):
    if item_id not in items:
        raise HTTPException(
            status_code=404,
            detail="Item not found",
            headers={"X-Error": "There goes my error"},
        )
    return {"item": items[item_id]}

exception_handler

可以注册一个自己的错误处理函数

@app.exception_handler(UnicornException)
async def exception_handler(request: Request, exc: UnicornException):
    return JSONResponse(
        status_code=418,
        content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
    )

RequestValidationError

这是FastAPI会抛出的一个错误,一般在BaseModel检查格式出错时抛出。这个类继承自pydanic的ValidationError,并额外添加了body这个属性。body内存着导致抛出意外的入参。errors()会返回一个JSON,里面包含了可读的错误信息。

API文档

文字文档

所有的请求参数都会被解析,生成文档,如果还想记录更多一点的内容,有以下这些方式可以使用:

  • summary 对这个请求有一个简要大概的介绍
  • description 对请求的详细介绍
  • response_description 对响应的介绍。就算不声明也会有默认的样式。

小框框内的就是summary,大框框内的是description。看得出来,description支持markdown格式。

487dc0b3c9cbe15d68982a388dc96c93.png

summary就作为函数的入参即可。description有两种方式,一种和summary一样,作为函数入参,另一种是写作函数的注释。

@app.post("/items/", response_model=Item, summary="Create an item", description="...")
async def create_item(item: Item):
    """
    Create an item with all the information:

    - **name**: each item must have a name
    - **description**: a long description
    - **price**: required
    - **tax**: if the item doesn't have tax, you can omit this
    - **tags**: a set of unique tag strings for this item
    """
    return item

响应的描述会显示在这里

39371c3b93aa62ce134adaeeea79dbe8.png

这样去使用

@app.post(
    "/items/",
    response_model=Item,
    summary="Create an item",
    response_description="The created item",
)
async def create_item(item: Item):
    """
    Create an item with all the information:

    - **name**: each item must have a name
    - **description**: a long description
    - **price**: required
    - **tax**: if the item doesn't have tax, you can omit this
    - **tags**: a set of unique tag strings for this item
    """
    return item

标记

有两种标记可供使用,一个是tags,标记这个API属于哪一部分。另一个是deprecated,标记这个API是否已经被遗弃。

PATCH

通过BaseModel的exclude_unset.copy(update=update_data),可以很方便地实现部分更新。

@app.patch("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
    stored_item_data = items[item_id]
    stored_item_model = Item(**stored_item_data)
    update_data = item.dict(exclude_unset=True)
    updated_item = stored_item_model.copy(update=update_data)
    items[item_id] = jsonable_encoder(updated_item)
    return updated_item

依赖注入

依赖注入是FastAPI的一套核心玩法,这套核心玩法相当重要,其重要程度类似于Flask的线程安全的g。

依赖注入,意味着我们可以声明一个函数运行时,所需要的其他资源、或需要做的其他事情。有些资源很像函数的入参,通过请求参数的继承组合可以达到类似的效果(这种情况下也没必要用依赖注入)。但是需要做的其他事情这点,通过对各种事件的组合,可以形成一个函数运行时所依赖的“依赖树

简单的例子

比如使用依赖注入,就像第9行那样使用即可。Depends的参数必须是一个callable的东西。这个callable的东西同步异步都无妨,FastAPI会自己处理。这个callable的入参,也就是视图函数的入参,这一点FastAPI也会自动解析。

注意这个callable,一个可以被初始化的类,也是callable的。

from typing import Optional
from fastapi import Depends

async def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}


@router.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
    return commons


@router.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
    return commons

2f360b76d927574237549343e0246319.png

子依赖

我们可以让依赖产生层次关系,比如下面这个例子,read_query将同时依赖q和last_query两个入参。

def query_extractor(q: Optional[str] = None):
    return q


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


@router.get("/items/")
async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)):
    return {"q_or_cookie": query_or_default}

如果两个依赖有相同的依赖,则FastAPI会自动识别出有相同的依赖,并将依赖的结果缓存下来,第二次就直接提供缓存的结果。如果不希望使用缓存值,可以使用Depends(callable, use_cache=False))

使用依赖来做检查

如果我们希望运行视图函数前检查一些东西,比如登录状态,权限等等,这些检查不需要返回什么,则可以把这些权限检查都放在一个列表之中.

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


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

如上所示,如果哪里不满意,就直接抛出错误就好。

yield

通过使用yield,我们可以更方便地管理某些资源。

async def get_db():
    db = DBSession()
    try:
        yield db
    finally:
        db.close()

还有一种玩法

class MySuperContextManager:
    def __init__(self):
        self.db = DBSession()

    def __enter__(self):
        return self.db

    def __exit__(self, exc_type, exc_value, traceback):
        self.db.close()


async def get_db():
    with MySuperContextManager() as db:
        yield db

安全认证

基于OAuth2的认证

直接上代码

from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

# 入参只是指明一下,哪个接口可以获得token。
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")

# 请求接口
@router.post("/token")
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    return ...

@router.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_user)):
    return current_user

async def get_current_user(token: str = Depends(oauth2_scheme)):
    ...

OAuth2PasswordBearer提供了自动解析Header中Bearer的值。OAuth2PasswordRequestForm提供了username和password的表单,当然,这里完全可以换成自己定义的。

也没提供啥有用的,OAuth2PasswordBearer还行,OAuth2PasswordRequestForm完全可以换成自己定义的其他BashModel。

中间层

这是一个添加请求运行时间的例子。

@router.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response

跨域

app = FastAPI()

origins = [
    "http://localhost.tiangolo.com",
    "https://localhost.tiangolo.com",
    "http://localhost",
    "http://localhost:8080",
]
origins = ["*"]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值