FastAPI for Fun

本文主要針對(duì)使用FastAPI構(gòu)建App可能用到的知識(shí)點(diǎn)做一個(gè)歸納總結(jié),讓我們從一個(gè)簡單的Hello world開始吧。

Hello world

# 步驟1:導(dǎo)入FastAPI
from fastapi import FastAPI

#步驟2:創(chuàng)建一個(gè)FastAPI“實(shí)例” 
app = FastAPI()

#步驟3:創(chuàng)建路徑操作
@app.get("/")
async def root():
#步驟4:定義路徑操作功能
    return {"message": "Hello World"}


if __name__ == '__main__':
    #步驟5:運(yùn)行開發(fā)服務(wù)器
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)

直接運(yùn)行上面的代碼或者命令行窗口運(yùn)行uvicorn main:app --reload

  • main:文件main.py(Python“模塊”)。
  • app:main.py在線內(nèi)創(chuàng)建的對(duì)象app = FastAPI()。
  • --reload:更改代碼后使服務(wù)器重新啟動(dòng)。僅用于開發(fā)。

這樣在瀏覽器輸入http://127.0.0.1:8000/可以看到返回{"message":"Hello World"}

下面讓我們一點(diǎn)一點(diǎn)來擴(kuò)展

您可以在FastAPI應(yīng)用程序app中配置幾件事:title、description、version

from fastapi import FastAPI

app = FastAPI(
    title="My First Project",
    description="Let's have fun with it.",
    version="0.0.1",)

@app.get("/")
async def root():
    return {"message": "Hello World"}


if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)!

FastAPI提供的自動(dòng)API文檔(http://127.0.0.1:8000/docs)會(huì)根據(jù)你的配置進(jìn)行變更:

[一]路徑參數(shù)

from fastapi import FastAPI

app = FastAPI()


@app.get("/users/me")
async def read_user_me():
    return {"user_id": "the current user"}


@app.get("/users/{user_id}")
async def read_user(user_id: int):
    return {"user_id": user_id}

注意路徑操作是按順序評(píng)估,因此如果你輸入http://127.0.0.1:8000/users/me
雖然以上兩個(gè)路徑參數(shù)都符合,但會(huì)按排在前面的進(jìn)行執(zhí)行。

我們看看async def read_user(user_id: int) 這里的int是用來聲明的,它有兩個(gè)作用,數(shù)據(jù)轉(zhuǎn)換和數(shù)據(jù)驗(yàn)證。

如果你輸入http://127.0.0.1:8000/users/3
請(qǐng)注意,函數(shù)返回的值不是string "3", 而是int 3
如果你輸入http://127.0.0.1:8000/users/3.3,則會(huì)收到一個(gè)HTTP錯(cuò)誤:

{
    "detail": [
        {
            "loc": [
                "path",
                "user_id"
            ],
            "msg": "value is not a valid integer",
            "type": "type_error.integer"
        }
    ]
}

如果你要對(duì)一個(gè)路徑進(jìn)行操作, 可以這樣:

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

:path告訴參數(shù)應(yīng)與任何路徑匹配,否則路徑一般帶有一個(gè)斜杠(/),可能不會(huì)被識(shí)別。例如/home/johndoe/myfile.txt, 注意在files和你的路徑參數(shù)之間應(yīng)該使用雙斜杠(//),URL應(yīng)該:http://127.0.0.1:8000/files//home/johndoe/myfile.txt

路徑“操作”其實(shí)是指HTTP“方法”之一,它是一個(gè)裝飾器。

常用的有

  • POST: 創(chuàng)建數(shù)據(jù)
  • GET: 讀取數(shù)據(jù)
  • PUT:更新數(shù)據(jù)
  • DELETE:刪除數(shù)據(jù)

還有奇特一點(diǎn)的

  • OPTIONS
  • HEAD
  • PATCH
  • TRACE

之前講過數(shù)據(jù)驗(yàn)證是類型驗(yàn)證,進(jìn)一步的,甚至可以進(jìn)行數(shù)值驗(yàn)證。

Path
from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    *,
    item_id: int = Path(..., title="The ID of the item to get", gt=0, le=1000),
    q: str,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

async 是異步關(guān)鍵字,異步的概念python很早就有,例如:

@asyncio.coroutine
def smart_fib(n):

async / await 在python3.5+被引入,這里不詳述, 對(duì)應(yīng)上面的:

async def  smart_fib(n):

Path的第一個(gè)參數(shù)是默認(rèn)值,由于路徑參數(shù)始終需要一個(gè)參數(shù),因?yàn)樗仨毷锹窂降囊徊糠?。這種情況下我們可以用...聲明它,以將其標(biāo)記為必需。實(shí)際上我們寫任何默認(rèn)值都不起作用。

數(shù)值驗(yàn)證
gt : greater than
ge: greater than or equal
lt : less than
le : less than or equal


我們可以將幾個(gè)參數(shù)傳遞給路徑操作裝飾器以對(duì)其進(jìn)行配置,請(qǐng)注意,這些參數(shù)直接傳遞給路徑操作裝飾器,而不是傳遞給路徑操作函數(shù)。

from typing import Set

from fastapi import FastAPI, status
from pydantic import BaseModel

app = FastAPI()


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


@app.post("/items/", response_model=Item, 
            status_code=status.HTTP_201_CREATED,
            tags=["items"],
            summary="Create an item",
            description="Create an item with all",
            deprecated=False)
async def create_item(*, item: Item):
    return item

status_code(響應(yīng)狀態(tài)碼): 也可以直接傳遞int代碼,例如404
tag(標(biāo)簽): 傳遞參數(shù)tags用list的str(通常只有一個(gè)str)
summary(摘要)
description(描述)
deprecated(棄用):True代表在API文檔它將被明確標(biāo)記為不推薦使用,使用刪除線

[二]查詢參數(shù)

聲明不屬于路徑參數(shù)的其他功能參數(shù)時(shí),它們將自動(dòng)解釋為“查詢”參數(shù)。

from fastapi import FastAPI

app = FastAPI()

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


@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10,short: bool = False):
    return fake_items_db[skip : skip + limit]

該查詢是?URL中位于關(guān)鍵字之后的一組鍵值對(duì),以&字符分隔。
http://127.0.0.1:8000/items/?skip=0&limit=10

我們還可以聲明bool類型, 1 \true\on\yes 以及它們的大小寫變體將被轉(zhuǎn)換為True。
http://127.0.0.1:8000/items/foo?short=yes

當(dāng)聲明非路徑參數(shù)的默認(rèn)值時(shí)(目前,我們僅看到查詢參數(shù)),則不需要此值。如果您不想添加特定值,而只是將其設(shè)為可選值,則將默認(rèn)值設(shè)置為None。但是,當(dāng)您需要一個(gè)查詢參數(shù)時(shí),就不能聲明任何默認(rèn)值

例如下面這樣一個(gè)例子:

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_user_item(item_id: str, needy: str, skip: int = 0, limit: int = None):
    item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit}
    return item

在這種情況下,有3個(gè)查詢參數(shù):

needy,是必需的str。
skip,int默認(rèn)值為0。
limit,可選的int。

但是limitL int = None 會(huì)面臨類型檢查錯(cuò)誤

于是使用Optional(可選的類型聲明),要改寫成:

from typing import Optional
@app.get("/items/{item_id}")
async def read_user_item(item_id: str, needy: str, skip: int = 0, limit: Optional[int] = None):
    item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit}
    return item

Query

類似于路徑參數(shù)使用Path來聲明相同類型的驗(yàn)證, 可以使用Query為查詢參數(shù)聲明更多驗(yàn)證。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str = Query(None, min_length=3, max_length=50, regex="^fixedquery$")
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

長度檢查:min_length=3, max_length=50
正則表達(dá)式檢查:regex="^fixedquery$"

假設(shè)您希望參數(shù)為item-query。像:http://127.0.0.1:8000/items/?item-query=foobaritems 但是item-query不是有效的Python變量名稱。
最接近的是item_query,但是您仍然需要它完全是item-query...

然后,您可以聲明一個(gè)alias,該別名將用于查找參數(shù)值

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str = Query(
        None,
        alias="item-query",
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
        max_length=50,
        regex="^fixedquery$",
        deprecated=True,
    )
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

通用驗(yàn)證和元數(shù)據(jù):

  • alias
  • title
  • description
  • deprecated

特定于字符串的驗(yàn)證:

  • min_length
  • max_length
  • regex

我覺得FastAPI一個(gè)非常nice的優(yōu)勢(shì)是它基于Pydantic模型來完成request body的許多校驗(yàn)工作,以避免在函數(shù)內(nèi)部寫很多類型處理,使代碼看起來更簡潔。下面一起看看。

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item

item使用該類型聲明,F(xiàn)astAPI將:

  • 以JSON讀取請(qǐng)求的正文。
  • 轉(zhuǎn)換相應(yīng)的類型(如果需要)。
  • 驗(yàn)證數(shù)據(jù)。

上面的該模型聲明一個(gè)JSON“ object”(或Python dict),例如:

{
    "name": "Foo",
    "description": "An optional description",
    "price": 45.2,
    "tax": 3.5
}

由于description和tax是可選的(默認(rèn)值為None),此JSON“ object”也將有效:

{
    "name": "Foo",
    "price": 45.2
}

復(fù)雜一點(diǎn)的,我們可以同時(shí)聲明body,path和query參數(shù):

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Item, q: str = None):
    result = {"item_id": item_id, **item.dict()}
    if q:
        result.update({"q": q})
    return result

功能參數(shù)將被識(shí)別如下:

  • 如果在path中也聲明了該參數(shù),它將用作路徑參數(shù)。
  • 如果參數(shù)是一個(gè)的單一類型(如int,float,str,bool,等等)將被解釋為一個(gè)查詢參數(shù)。
  • 如果參數(shù)聲明為Pydantic模型的類型,則它將被解釋為請(qǐng)求正文。

Body

和的相同方法Query和Path為查詢和路徑參數(shù)定義額外的數(shù)據(jù),F(xiàn)astAPI提供了等效的Body。

from fastapi import Body, FastAPI
from pydantic import BaseModel

app = FastAPI()


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


class User(BaseModel):
    username: str
    full_name: str = None


@app.put("/items/{item_id}")
async def update_item(
    *, item_id: int, item: Item, user: User, importance: int = Body(...)
):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    return results

如果這里的importance不使用Body,由于它是一個(gè)單一類型int, 它會(huì)被當(dāng)做查詢參數(shù)。但這里使用Body, 那么我們期望的json是下面這樣子:

{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    },
    "user": {
        "username": "dave",
        "full_name": "Dave Grohl"
    },
    "importance": 5
}

注意這里多個(gè)body的時(shí)候(User、Item),會(huì)使用正文中的鍵作為參數(shù)名稱,使得User和Item的屬性值再往里嵌套一層。但如果是單個(gè)Body, 我們也想要使其屬性值往里面嵌套一層的話,就要使用embed。

from fastapi import Body, FastAPI
from pydantic import BaseModel

app = FastAPI()


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


@app.put("/items/{item_id}")
async def update_item(*, item_id: int, item: Item = Body(..., embed=True)):
    results = {"item_id": item_id, "item": item}
    return results

這樣,期望的body是:

{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    }
}

代替:

{
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2
}

如果對(duì)Item里的price 我們希望進(jìn)行數(shù)據(jù)驗(yàn)證的話,也是有辦法的,我們使用Field。

from fastapi import Body, FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


class Item(BaseModel):
    name: str
    description: 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: float = None


@app.put("/items/{item_id}")
async def update_item(*, item_id: int, item: Item = Body(..., embed=True)):
    results = {"item_id": item_id, "item": item}
    return results

另外,example 是配置例子,例子會(huì)在docs API顯示。

from fastapi import Body, FastAPI
from pydantic import BaseModel

app = FastAPI()


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


@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

我們甚至可以嵌套模型:

from typing import Set

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None
    tags: Set[str] = []
    image: Image = None


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

期望的body 是這樣的:

{
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2,
    "tags": ["rock", "metal", "bar"],
    "image": {
        "url": "http://example.com/baz.jpg",
        "name": "The Foo live"
    }
}

注意,在Image模型中,我們有一個(gè)url字段,我們可以將其聲明為Pydantic的HttpUrl,而不是str, 該字符串將被檢查為有效的URL,并在JSON Schema / OpenAPI中進(jìn)行記錄。


下是一些您可以使用的其他數(shù)據(jù)類型:

  • UUID
    • 一個(gè)標(biāo)準(zhǔn)的“通用唯一標(biāo)識(shí)符”,在許多數(shù)據(jù)庫和系統(tǒng)中通常作為ID使用。
    • 在請(qǐng)求和響應(yīng)中將以表示str。
  • datetime.datetime
    • 一個(gè)Python datetime.datetime。
    • 在請(qǐng)求和響應(yīng)中,將以strISO 8601格式表示,例如:2008-09-15T15:53:00+05:00。
  • datetime.date
    • Python datetime.date。
    • 在請(qǐng)求和響應(yīng)中,將以strISO 8601格式表示,例如:2008-09-15。
  • datetime.time
    • 一個(gè)Python datetime.time。
    • 在請(qǐng)求和響應(yīng)中,將以strISO 8601格式表示,例如:14:23:55.003。
  • datetime.timedelta
    • 一個(gè)Python datetime.timedelta。
    • 在請(qǐng)求和響應(yīng)中,將以float總秒數(shù)表示。
    • Pydantic還允許將其表示為“ ISO 8601時(shí)間差異編碼”,有關(guān)更多信息,請(qǐng)參閱文檔
  • frozenset
    • 在請(qǐng)求和響應(yīng)中,將與視為相同set
      • 在請(qǐng)求中,將讀取列表,消除重復(fù),并將其轉(zhuǎn)換為set
      • 作為響應(yīng),set將會(huì)轉(zhuǎn)換為list。
      • 生成的架構(gòu)將指定set值是唯一的(使用JSON架構(gòu)的uniqueItems)。
  • bytes
    • 標(biāo)準(zhǔn)Python bytes。
    • 在請(qǐng)求和響應(yīng)中將被視為str
    • 生成的模式將指定,這是一個(gè)strbinary“格式”。
  • Decimal
    • 標(biāo)準(zhǔn)Python Decimal。
    • 在請(qǐng)求和響應(yīng)中,處理方式與相同float
from datetime import datetime, time, timedelta
from uuid import UUID

from fastapi import Body, FastAPI

app = FastAPI()


@app.put("/items/{item_id}")
async def read_items(
    item_id: UUID,
    start_datetime: datetime = Body(None),
    end_datetime: datetime = Body(None),
    repeat_at: time = Body(None),
    process_after: timedelta = Body(None),
):
    start_process = start_datetime + process_after
    duration = end_datetime - start_process
    return {
        "item_id": item_id,
        "start_datetime": start_datetime,
        "end_datetime": end_datetime,
        "repeat_at": repeat_at,
        "process_after": process_after,
        "start_process": start_process,
        "duration": duration,
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 最美的邂逅不過第一次相遇。踏上青色石階,第一滴雨點(diǎn)落下,天高云淡,云卷云舒,看亭臺(tái)花落,遇見是青澀的。兩人...
    反轉(zhuǎn)乾坤閱讀 466評(píng)論 1 5
  • 忙碌于領(lǐng)導(dǎo)交代的緊急又重要的工作;奔波在公司,新房裝修之間;支撐在繁忙又充滿希望的日子里;幫助弟弟走上了再次求學(xué)之...
    曼曼_1db0閱讀 244評(píng)論 0 0
  • 生活啊,你知道 不止眼前的茍且 還有那詩和遠(yuǎn)方 悠悠時(shí)光荏苒 歲月如詩有味 在你我心中成長 風(fēng)輕輕拂去往昔的灰塵 ...
    小荷安德閱讀 139評(píng)論 1 2

友情鏈接更多精彩內(nèi)容