FastAPI チュートリアル、vue と組み合わせてフロントエンドとバックエンドの分離を実現

英語のドキュメント: https://fastapi.tiangolo.com/
中国語のドキュメント: https://fastapi.tiangolo.com/zh/

もてはやされるSanicフレームワークがなぜあえて最強を主張するのでしょうか?: https://zhuanlan.zhihu.com/p/360513398

Web フレームワーク ランキング: https://github.com/the-benchmarker/web-frameworks

1. FastAPI チュートリアル

簡単な紹介

FastAPI は、Sanic と同様、Python の非同期 Web フレームワークです。Sanic と比較すると、FastAPI はより成熟しており、コミュニティもより活発です。

FastAPI は巨人の肩の上に立っていますか?
この巨人は、大部分において、Flask フレームワークを指します。
FastAPI は構文の点で Flask に非常に似ており、同じ目的を持っています。
実際、FastAPI だけでなく Sanic も、迅速な開発を可能にする Flask ベースの Web API フレームワークです。

FastAPI は、Python 3.6 以降を使用し、標準の Python 型ヒントに基づいて API を構築するための最新の高速 (高性能) Web フレームワークです。

次のような利点があります。

  • 高速NodeJS や Goに 匹敵する極端なパフォーマンス(Starlette と Pydantic のおかげ)。Starlette 用于路由匹配,Pydantic 用于数据验证
  • コーディングの効率化: 機能開発速度が約200%~300%向上
  • バグの減少: 人間 (開発者) によるエラーが約 40% 減少します。
  • スマート: 優れたエディターのサポート。あらゆる場所での自動完了により、デバッグ時間を短縮します
  • シンプル: 使いやすく、学びやすいように設計されており、ドキュメントを読む時間が短縮されます。
  • 短い: コードの重複を最小限に抑えます。豊富な機能は、さまざまなパラメータ宣言によって実現されます。バグが少ない
  • Robust : 使用可能なレベルのコードを生成します。自動的に生成されるインタラクティブなドキュメントもあります
  • 標準化: API の関連オープン標準 ( OpenAPI  (旧名 Swagger) および JSON スキーマ) に基づいています (および完全な互換性があります) 。

FastAPI の最大の特徴は Python 型のアノテーションを使用することであり、FastAPI を使用するには Python バージョン 3.6 以上が必要です。

FastAPIをインストールする

pip install fastapi は、Starlette と Pydantic を自動的にインストールします。その後、uvicorn が関連アプリケーションを実行するサーバーであるため、pip install uvicorn をインストールします。または、ワンステップで胃に行きます: pip install fastapi[all] はすべての依存関係をインストールします。

チュートリアル

Python 高性能 Web フレームワーク - FastApi 総合ガイド: https://zhuanlan.zhihu.com/p/397029492
最も人気のある非同期フレームワーク: https://www.cnblogs.com/traditional/p/14733610.html

公式ウェブサイトのチュートリアル: https://fastapi.tiangolo.com/zh/tutorial/

上級ユーザー ガイド: https://fastapi.tiangolo.com/zh/advanced/

同時実行、デプロイ、ビルド プロジェクト テンプレート

Python の型ヒントの概要

https://fastapi.tiangolo.com/zh/python-types/

Python の型アノテーション、知っておくべきことはすべてここにあります: https://zhuanlan.zhihu.com/p/419955374
オブジェクト アノテーション属性のベスト プラクティス: https://docs.python.org/zh-cn/3/howto /注釈.html

2. FastAPIの利用

簡単な使用例

例: 同期コード

from typing import Optional
from fastapi import FastAPI
 
app = FastAPI()
 
 
@app.get("/")
def read_root():
    return {"Hello": "World"}
 
 
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}

例: 非同期コード

新しい main.py ファイルを作成します

import os
import uvicorn
from pathlib import Path
from fastapi import FastAPI

app = FastAPI()


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


if __name__ == '__main__':
    # os.system('uvicorn main_test:app --reload')
    uvicorn.run(f'{Path(__file__).stem}:app', host="0.0.0.0", port=5555)
    pass

FastAPI では、サービスの実行に uvicorn を使用することをお勧めします。Uvicorn は、uvloop と httptools の上に構築された超高速 ASGI サーバーです。

uvicorn main:app コマンドの意味は次のとおりです。

  • main: main.py ファイル (Python の「モジュール」)。
  • app: main.py ファイル内の app = FastAPI() によって作成されたオブジェクト。
  • --reload: コードを更新した後にサーバーを再起動します。このオプションは開発時にのみ使用してください。

http://127.0.0.1:5555にアクセスして、  JSON 形式の応答を確認します: {"message": "Hello World"}

リクエストクエリパラメータ

「リクエスト パス」は、一般に「エンドポイント」または「ルート」とも呼ばれます。

import uvicorn
from pathlib import Path
from fastapi import FastAPI

app = FastAPI()  # 创建 api 对象


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):
    return fake_items_db[skip: skip + limit]


@app.get("/")  # 根路由。「请求路径」也通常被称为「端点」或「路由」。
async def root():
    return {"name": "king", 'age': 100}


@app.get("/say/{data}")
async def say(data: str, q: int = None):
    return {"data": data, "q": q}


if __name__ == '__main__':
    # os.system('uvicorn main_test:app --reload')
    uvicorn.run(f'{Path(__file__).stem}:app', host="127.0.0.1", port=5555)
    pass

ブラウザ アクセス
http://127.0.0.1:5555/
http://127.0.0.1:5555/say/test
http://127.0.0.1:5555/items/?skip=0&limit=10
クエリは何ですか? URL キーワードの後の、& 文字で区切られた一連のキーと値のペア。
URL のクエリ:
スキップ: クエリの開始パラメータ
limit: クエリの終了パラメータ

@app.get()と@router.get()の違いについて

どちらも HTTP GET リクエストのハンドラーを定義するために使用されますが、いくつかの違いがあります。

  • @app.get(): このメソッドは、FastAPI アプリケーション インスタンス上でルートを直接定義するための短縮形です。
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello, World!"}
  • @router.get(): このメソッドは、FastAPI ルーター オブジェクト (Router) でルートを定義する方法です。スタンドアロンのルーター オブジェクトを作成し、 @router.get() を使用して特定のパスのルートを定義できます。
from fastapi import FastAPI, APIRouter

router = APIRouter()

@router.get("/items")
async def read_items():
    return {"message": "Read all items"}

上記の例では、router というルーター オブジェクトを作成し、router.get() を使用して「/items」パスの GET リクエスト ハンドラーを定義します。複数のルーター オブジェクトを使用する場合、それらを有効にするために FastAPI アプリケーション インスタンスに追加する必要があることに注意してください。

# app.include_router() メソッドを通じてルーター オブジェクトを FastAPI アプリケーション インスタンスに追加し、定義されたルートが正しく処理されるようにします。

app.include_router(ルーター)

概要: app.get() はアプリケーション インスタンスのルートを定義するために使用され、router.get() はルーター オブジェクトのルートを定義するために使用されます。2 つの違いは定義場所と適用方法ですが、どちらも HTTP GET リクエストの処理に使用できます。

要約する

  • 輸入 FastAPI
  • インスタンスを作成します app 。
  • パス操作デコレーターを作成します(例 @app.get("/"): .
  • パス操作関数を作成します(上記と同様 def root(): ...)。
  • 開発サーバー (例 uvicorn main:app --reload: .

リクエストの「パスパラメータ」

パスパラメータをパラメータに反映する必要があります。

「リクエスト パス」は、一般に「エンドポイント」または「ルート」とも呼ばれます。

FastAPI は単純なアプリケーションを作成します。

import os
import uvicorn
from pathlib import Path
from fastapi import FastAPI

# 类似于 app = Flask(__name__)
app = FastAPI()


# 绑定路由和视图函数
@app.get("/")
async def root():
    return {"message": "Hello World"}


# 在 Windows 中必须加上 if __name__ == "__main__",
# 否则会抛出 RuntimeError: This event loop is already running
if __name__ == '__main__':
    # 启动服务,因为我们这个文件叫做 main_test.py,所以需要启动 main_test.py 里面的 app
    # os.system('uvicorn main_test:app --reload')
    # uvicorn.run("main_test:app", host="0.0.0.0", port=8000)
    uvicorn.run(f'{Path(__file__).stem}:app', host="0.0.0.0", port=5555)
    pass

ブラウザに「localhost:5555」と入力すると、対応する出力が表示され、view 関数で直接辞書を返すことができることがわかります。もちろん、辞書に加えて、他のデータ型も可能です。

# -*- coding:utf-8 -*-
from fastapi import FastAPI
import uvicorn

app = FastAPI()


@app.get("/int")
async def index1():
    return 666


@app.get("/str")
async def index2():
    return "佛祖保佑,佛祖保佑"


@app.get("/bytes")
async def index3():
    return b"satori"


@app.get("/tuple")
async def index4():
    temp_tuple = ("佛祖保佑", "佛祖保佑")
    return temp_tuple


@app.get("/list")
async def index5():
    return [{"name": "擒贼先擒王", "age": 18}, {"name": "捉奸先捉双", "age": 16}]


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

リクエストを使用してリクエストを直接送信する

インポートリクエスト

print(requests.get("http://localhost:5555/int").text)
print(requests.get("http://localhost:5555/str").text)
print(requests.get("http ://localhost:5555/bytes").text)
print(requests.get("http://localhost:5555/tuple").text)
print(requests.get("http://localhost:5555/list) ")。文章)

ただし、タプルは自動的にリストに変換されて返されます。ここでは、ルート内のパスを指定します。FastAPI のパス形式は他のフレームワークと同じであることがわかりますが、現在のパスはハードコーディングされています。パス パラメータを動的に宣言したい場合はどうすればよいでしょうか?

パスパラメータ

パスの「パラメータ」または「変数」は、Python 形式文字列と同じ構文を使用して宣言できます。パス パラメータとクエリ パラメータを区別するには、FastAPI で十分です。

fastapi から FastAPI をインポート

app = FastAPI()


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

path パラメーター の値は 、関数に引数として渡されitem_id ます 。item_idhttp://127.0.0.1:5555/items/foo を実行してアクセスすると、次の応答が表示されます: {"item_id":"foo"}

 例:

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(item_id: str, q: str = None, short: bool = False):
    item = {"item_id": item_id}
    if q:
        item.update({"q": q})
    if not short:
        item.update(
            {"description": "This is an amazing item that has a long description"}
        )
    return item

パス パラメーター item_id の値は、パラメーター item_id として関数に渡されます。パス パラメーターではない他の関数パラメーターを宣言すると、それらは自動的に「クエリ文字列」パラメーターとして解釈されます。

アクセス パスを確認し、次のいずれかの URL アクセス メソッドを実行します。
http://127.0.0.1:8000/items/Lao Wang sleeps next door?short=1
http://127.0.0.1:8000/items/Lao Wang sleeps Next door?short=True
http://127.0.0.1:8000/items/Lao Wang は隣で寝ています?short=true
http://127.0.0.1:8000/items/Lao Wang は隣で寝ています?short=on
http: //127.0 .0.1:8000/items/Lao Wang sleeps next door?short=yes
すべての大文字と小文字がブール値パラメータ True に変換されることがわかります。これはいわゆるファジー検証パラメータであり、これは良いことです開発者向けのニュース。注: short パラメータにデフォルト値がない場合は、それを渡す必要があります。渡さないと、FastAPI がエラーを報告します。

{     "detail": [         {             "loc": [                 "query",                 "needy"             ],             "msg": "field required",             "type": "value_error.missing"         }     ] }










例:

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):
    return fake_items_db[skip : skip + limit]

クエリ文字列は、URL の ? の後に & 記号で区切られたキーと値のペアのコレクションです。

Query を使用してクエリに対して追加の検証を実行できます

from typing import Optional

from fastapi import FastAPI, Query

app = FastAPI()

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

クエリには次のフィールド チェックがあります。

  • min_length 最小長
  • max_length 最大長
  •  正規表現の通常の一致
  • Query の 最初のパラメータはデフォルト値であり、...それが必須であることを示します。

Path と Query は同様に使用され、Query フィールドも検証できます。

また、数値検証を宣言することもできます。

from fastapi import FastAPI, Path, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
        *,
        item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000),
        q: str,
        size: float = Query(..., gt=0, lt=10.5)
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results
  • gt:以上
  • ge:以上
  • lt: 未満
  • le: 以下

同様に Cookie もあります

from typing import Optional

from fastapi import Cookie, FastAPI

app = FastAPI()


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

およびヘッダー:

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}

リクエストボディ + パス + クエリパラメータ、リクエストボディに基づいて URL 動的パスパラメータとクエリパラメータを追加します

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

テンプレートエンジンについて

FastAPI は Flask のような独自のテンプレート エンジン (Jinja2) を持たない、つまりデフォルトのテンプレート エンジンがありませんが、別の観点から見ると、FastAPI はテンプレート エンジンの選択においてより柔軟で非常に快適になっています。

Jinja2 テンプレートを例として、依存関係をインストールします。

pip install jinja2
pip install aiofiles # fastapi の非同期静的ファイル

# -*- coding:utf-8 -*-
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import uvicorn

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")  # 挂载静态文件,指定目录

templates = Jinja2Templates(directory="templates")  # 模板目录


@app.get("/data/{data}")
async def read_data(request: Request, data: str):
    return templates.TemplateResponse("index.html", {"request": request, "data": data})


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

htmlファイルのレンダリング

<html>
<head>
    <title>士可杀不可辱(あなたは私を殺すことはできますが、私を性交することはできません)</title>
</head>
<body>
    <h1>高呼: { { data }} </h1>
</body>
</html>

ブラウザに「http://127.0.0.1:8000/data/」と入力します。

TemplateRespone 応答が返されるときは、要求のコンテキスト オブジェクトが取得され、受信パラメータが同じディクショナリに配置される必要があることに注意してください。このように、Jinja2 は Flask のように使用できます。

パスのタグタグを group に設定します

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

app = FastAPI()


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


@app.post("/items/", response_model=Item, tags=["items"])
async def create_item(item: Item):
    return item


@app.get("/items/", tags=["items"])
async def read_items():
    return [{"name": "Foo", "price": 42}]


@app.get("/users/", tags=["users"])
async def read_users():
    return [{"username": "johndoe"}]

概要と説明を設定することもできます

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

app = FastAPI()


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


@app.post(
    "/items/",
    response_model=Item,
    summary="Create an item",
    description="Create an item with all the information, name, description, price, tax and a set of unique tags",
)
async def create_item(item: Item):
    return item

複数行のコメント:

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

app = FastAPI()


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


@app.post("/items/", response_model=Item, summary="Create an 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

 廃止されたルート:

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/", tags=["items"])
async def read_items():
    return [{"name": "Foo", "price": 42}]


@app.get("/users/", tags=["users"])
async def read_users():
    return [{"username": "johndoe"}]


@app.get("/elements/", tags=["items"], deprecated=True)
async def read_elements():
    return [{"item_id": "Foo"}]

型指定されたパスパラメータ

標準の Python 型アノテーションを使用して、関数内のパス パラメーターの型を宣言できます。

fastapi から FastAPI をインポート

app = FastAPI()


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

この例では、item_id は int 型として宣言されています。

この例を実行し、ブラウザを開いて http://127.0.0.1:5555/items/3 にアクセスすると、
次の応答が得られます: {"item_id":3}
関数によって受信された (および返された) 値に注意してください。は、文字列「3」ではなく、Python int value である 3 です。
したがって、FastAPI は、上記の型宣言を通じてリクエストの自動「解析」を提供します。

# -*- coding:utf-8 -*-

from fastapi import FastAPI
import uvicorn

app = FastAPI()


@app.get("/apple/{item_id}")
async def get_item(item_id: int):
    """
        Flask 定义类型是在路由当中,也就是在 <> 里面,变量和类型通过 : 分隔
        FastAPI 是使用类型注解的方式,此时的 item_id 要求一个整型(准确的说是一个能够转成整型的字符串)
    """
    return {"item_id": item_id}

if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

順序の問題

パス操作は順番に実行されるため、ルートを定義するときは順序に注意する必要があります。

fastapi から FastAPI をインポート

app = FastAPI()


@app.get("/users/me")
async def read_user_me():
    return {"user_id": "現在のユーザー"}


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

パス操作は順番に実行されるため、  ここではパスの前になければ一致しません。 /users/me この 時点でアクセスすると 、文字列 "me" が解析できないため、解析エラーが返されます。整数に変換します。/users/{user_id}/users/{user_id}/users/me

デフォルト値 (列挙型)

特定のパスパラメータは、型アノテーションを通じて指定された型として宣言できます(正確に言うと、デフォルトは文字列なので、指定された型に変換できます)が、それを少数の値のみにしたい場合は、何をすべきでしょうか?

この時点で、標準の Python 型を使用できます Enum 。 と を継承する サブクラスをインポートし Enum て作成します 。から継承することで  、API ドキュメントはこれらの値を入力する必要があることを認識し  、それらを正しく表示できるようになります。次に、利用可能な有効な値となる固定値を持つクラス プロパティを作成します。strEnumstrstring

from enum import Enum

from fastapi import FastAPI


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


app = FastAPI()


@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
    if model_name is 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"}

パスには / が含まれます

/files/{file_path} のようなルートがあり、ユーザーが渡した file_path には明らかに / が含まれていると仮定し、file_path が /root/test.py であると仮定すると、ルートは /files//root/test.py になります。 、どうやらこれには問題があります。

OpenAPIは、テストと定義が困難な状況を引き起こす可能性があるため、パス パラメーターを宣言してパラメーターの中にパスを含める方法をサポートしていません。ただし、 Starlette の内部ツールを使用してFastAPIに実装することは可能です  。

パスを含むパス パラメータは、 Starlette から直接オプションを使用して宣言できます/files/{file_path:path}

この場合、パラメータ名の後に、  パラメータが任意のパスfile_pathに一致する必要があるというステートメント が続きます:path

したがって、次のように使用できます。

fastapi から FastAPI をインポート

app = FastAPI()


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

スラッシュ ( ) で始まるパラメータの包含が必要な場合があります /home/johndoe/myfile.txt/

この場合、URL は  と  の間に二重スラッシュ ( ) が/files//home/johndoe/myfile.txt入りますfileshome//

from fastapi import FastAPI
import uvicorn

app = FastAPI()


# 声明 file_path 的类型为 path,这样它会被当成一个整体
@app.get("/files/{file_path:path}")
async def get_file(file_path: str):
    return {"file_path": file_path}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

127.0.0.1:8000/ドキュメントを設定します 

「127.0.0.1:8000/docs」ページ自体については、次のように設定することもできます。

from fastapi import FastAPI
import uvicorn

app = FastAPI(
    title="测试文档",
    description="这是一个简单的 demo",
    docs_url="/my_docs",
    openapi_url="/my_openapi"
)


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


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

パスパラメータ、データ検証

クエリパラメータのデータ検証ではQueryを使用し、パスパラメータのデータ検証ではPathを使用しますが、両者の使用方法は全く同じであり、違いはありません。

from fastapi import FastAPI, Path
import uvicorn

app = FastAPI()

@app.get("/items/{item-id}")
async def read_items(item_id: int = Path(..., alias="item-id")):
    return {"item_id": item_id}

if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

path パラメーターは必須であり、パスの一部であるため、 ... を使用して必須パラメーターとしてマークする必要があります。もちろん、これを行わなくても問題ありません。デフォルト値を指定するとデフォルト値を使用できなくなります。パス パラメータを指定しないと、デフォルト値と一致しないためです。まったく対応するルート。他のいくつかのチェックについては、クエリ パラメーターとまったく同じであるため、ここでは繰り返しません。

ただし、前に述べたように、パス パラメーターはクエリ パラメーターの前に置く必要があり、FastAPI にはこの要件はありませんが、明らかにこの方法で記述した方が快適です。しかし、ここで問題が発生します。パス パラメーターではエイリアスを指定する必要があるが、特定のクエリ パラメーターではエイリアスを指定しない場合、この時点で問題が発生します。

@app.get("/items/{item-id}")
async def read_items(q: str,
                     item_id: int = Path(..., alias="item-id")):

    return {"item_id": item_id, "q": q}

明らかに、この時点の Python の構文では、item_id を q の後に配置する必要があります。もちろん、そうしても問題はありません。FastAPI では、名前、型で宣言されているため、パラメーターの順序に関する要件はありません。およびパラメータのデフォルト値を使用して、パラメータの順序に関係なくパラメータを検出します。しかしこのとき、 item_id を q の前に置きたい場合はどうすればよいでしょうか?

@app.get("/items/{item-id}")
async def read_items(*, item_id: int = Path(..., alias="item-id"),
                     q: str):
    
    return {"item_id": item_id, "q": q}

最初のパラメータを * に設定すると、item_id と q の両方をキーワードで渡す必要があるため、現時点では、デフォルト パラメータをデフォルト以外のパラメータの前に許可することができます。もちろん、FastAPI でパラメータを渡すことについて心配する必要はありません。すべてのパラメータがキーワード パラメータを介して渡されると考えることができます。

リクエストの「クエリパラメータ」

  • クエリ パラメータは、FastAPI の型アノテーションを通じて宣言でき、クエリ パラメータを省略することもできます。その場合、リクエストに関連するすべての情報が Request オブジェクトに入力されます。
  • パスパラメータではないパラメータが関数内で定義されている場合、それらは自動的にクエリパラメータとして解釈されます。

例:

fastapi から FastAPI をインポート

app = FastAPI()

fake_items_db = [{"アイテム名": "フー"}、{"アイテム名": "バー"}、{"アイテム名": "バズ"}]


@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):
    return fake_items_db[skip : スキップ + リミット]

クエリ文字列は、 URL に続き  、記号で区切られた キーと値のペアのコレクションです& 。

たとえば、次の URL では: http://127.0.0.1:5555/items/?skip=0&limit=10

クエリパラメータは次のとおりです。

  • skip: 対応する値は 0
  • limit: 対応する値は 10

例:

from fastapi import FastAPI
import uvicorn

app = FastAPI()


@app.get("/user/{user_id}")
async def get_user(user_id: str, name: str, age: int):
    """
        函数中参数定义了 user_id、name、age 三个参数
        显然 user_id 和 路径参数中的 user_id 对应,
        然后 name 和 age 会被解释成查询参数
        这三个参数的顺序没有要求,但是一般都是路径参数在前,查询参数在后
    """
    return {"user_id": user_id, "name": name, "age": age}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

オプションパラメータ、必須パラメータ

  • 非パス パラメータのデフォルト値が None に設定されている場合、パラメータはオプションです
  • 非パスパラメータにデフォルト値がない場合、パラメータを渡す必要があります。

デフォルト値を次のように設定して、 None オプションのクエリ パラメータを宣言します。

import Union の入力から

fastapi から FastAPI をインポート

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(item_id: str, q: Union[str, None] = None):
    if q:
        return {"item_id": item_id, "q": q }
    戻り値 {"アイテムID": アイテムID}

この例では、関数パラメータ q のデフォルトは None であるため、q はオプションです。FastAPI は、パラメータ item_id がパス パラメータであり、q がそうでないことを認識できるため、q はクエリ パラメータです。

複数の型パラメータ ( Union )

複数の種類を指定します。たとえば、user_id は整数として解析され、解析が失敗すると文字列に縮退します。

from typing import Union, Optional
from fastapi import FastAPI
import uvicorn

app = FastAPI()


@app.get("/user/{user_id}")
async def get_user(user_id: Union[int, str], name: Optional[str] = None):
    """
        通过 Union 来声明一个混合类型,int 在前、str 在后。会先按照 int 解析,解析失败再变成 str
        然后是 name,它表示字符串类型、但默认值为 None(不是字符串),那么应该声明为 Optional[str]
    """
    return {"user_id": user_id, "name": name}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

したがって、FastAPI の設計は依然として非常に優れており、パラメータの型の制限を Python の型アノテーションによって実現するのは非常に賢いと言えるため、これにも Python の型アノテーションを十分に習得する必要があります。

bool型は自動変換される

ブール型の場合、FastAPI は自動変換をサポートします

from fastapi import FastAPI
import uvicorn

app = FastAPI()


@app.get("/{flag}")
async def get_flag(flag: bool):
    return {"flag": flag}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

print(requests.get("http://localhost:5555/1").json())
print(requests.get("http://localhost:5555/True").json())
print(リクエスト。 get("http://localhost:5555/true").json())
print(requests.get("http://localhost:5555/False").json())
print(requests.get("http ://localhost:5555/false").json())
print(requests.get("http://localhost:5555/on").json())
print(requests.get("http://localhost) :5555/yes").json())
print(requests.get("http://localhost:5555/off").json())
print(requests.get("http://localhost:5555/no) ").json())

複数のパス、クエリパラメータ

複数のパス パラメーターとクエリ パラメーターを同時に宣言でき、FastAPI はそれらを認識します。また、特定の順序で宣言する必要はありません。これらは名前によって検出されます。

import Union の入力から

fastapi から FastAPI をインポート

app = FastAPI()


@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(
    user_id: int, item_id: str, q: Union[str, None] = None, short: bool = False
):
    item = {"item_id": item_id, "owner_id": user_id}
    if q:
        item.update({"q": q})
    if not short:
        item.update(
            {"description": "これは素晴らしいアイテムです。長い説明があります"}
        )
    アイテムを返します

FastAPI では、関数のパラメーターに動的パス パラメーターが含まれている限り、任意の数のパス パラメーターを定義できます。もちろん、クエリ パラメータはいくつでも使用でき、FastAPI はそれらを適切に処理できます。

from typing import Optional
from fastapi import FastAPI
import uvicorn

app = FastAPI()


@app.get("/postgres/{schema}/v1/{table}")
async def get_data(
        schema: str,
        table: str,
        select: str = "*",
        where: Optional[str] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None):
    """
    标准格式是:路径参数按照顺序在前,查询参数在后
    但其实对顺序是没有什么要求的
    """
    query = f"select {select} from {schema}.{table}"
    if where:
        query += f" where {where}"
    if limit:
        query += f" limit {limit}"
    if offset:
        query += f" offset {offset}"
    return {"query": query}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

http://localhost:5555/postgres/ods/v1/staff
http://localhost:5555/postgres/ods/v1/staff?select=id, name&where=id > 3&limit=100

依存 (依存関係の注入)

from typing import Optional
import uvicorn
from fastapi import Depends, FastAPI

app = FastAPI()


async def common_parameters(q: Optional[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)):
    # common_parameters 接收三个参数:q、skip、limit
    # 然后在解析请求的时候,会将 q、skip、limit 传递到 common_parameters 中,然后将返回值赋值给 commons
    # 但如果解析不到某个参数时,那么会判断函数中参数是否有默认值,没有的话就会返回错误,而不是传递一个 None 进去
    return commons


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


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

request.get("http://localhost:5555/items").json()
{q': なし,'skip':@,limit': 100}
request.get("http://localhost:5555/ items?g=id,name").json()
{g':id, name',skip': 0,limit': 100}

したがって、Depends は依存性注入を非常にうまく実装できます。そして、それらが互いに独立していることを示すために、特別に 2 つのルートを作成しました。したがって、共有ロジックや共有データベース接続、強化されたセキュリティ、認証、ロール権限などが存在する場合、非常に実用的になります。

FastAPI は、開発者がコンポーネントをFastAPIに簡単に統合できるようにする、使いやすく強力な依存関係注入システムを提供します。

「依存性注入」とは何ですか?

依存関係の注入は、クラス間の依存関係を削除する設計パターンです。依存クラスをコンテナに配置し、これらのクラスのインスタンスを解析することは、依存関係の注入です。目的はクラスの分離を達成することです。

例:

from typing import Optional
from fastapi import Depends, FastAPI

app = FastAPI()


async def common_parameters(q: Optional[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)):
    return commons


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

この例の依存関係は、次のパラメーターを受け取ることを想定しています。

  • タイプの str オプションのクエリパラメータ q
  • タイプ の int オプションのクエリ パラメータ skip。デフォルト値は次のとおりです。 0
  • タイプ の int オプションのクエリ パラメータ limit。デフォルト値は次のとおりです。 100

依存関係関数は を返します dict

クラスを依存関係として使用します。

from typing import Optional
from fastapi import Depends, FastAPI

app = FastAPI()

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


class CommonQueryParams:
    def __init__(self, q: Optional[str] = None, skip: int = 0, limit: int = 100):
        self.q = q
        self.skip = skip
        self.limit = limit


@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
    response = {}
    if commons.q:
        response.update({"q": commons.q})
    items = fake_items_db[commons.skip: commons.skip + commons.limit]
    response.update({"items": items})
    return response

ネストされたサブ依存関係を使用します。

from typing import Optional
from fastapi import Cookie, Depends, FastAPI

app = FastAPI()


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


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

パス内の依存関係を使用します。

from fastapi import Depends, FastAPI, Header, HTTPException

app = FastAPI()


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"}]

すべてのパス操作適用できるグローバル依存関係:

from fastapi import Depends, FastAPI, Header, HTTPException


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 = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])


@app.get("/items/")
async def read_items():
    return [{"item": "Portal Gun"}, {"item": "Plumbus"}]


@app.get("/users/")
async def read_users():
    return [{"username": "Rick"}, {"username": "Morty"}]

クエリパラメータ、データ検証

FastAPI は、文字列などのよりインテリジェントなデータ検証を実行することをサポートしています。ユーザーが文字列を渡すときに、長さが 6 ~ 15 の文字列のみを渡すことができるようにしたいのですが、どうすればよいでしょうか?

from typing import Optional
from fastapi import FastAPI, Query
import uvicorn

app = FastAPI()


@app.get("/user")
async def check_length(
        # 默认值为 None,应该声明为 Optional[str],当然声明 str 也是可以的。只不过声明为 str,那么默认值应该也是 str
        # 所以如果一个类型允许为空,那么更规范的做法应该是声明为 Optional[类型]。
        password: Optional[str] = Query(None, min_length=6, max_length=15)
):
    return {"password": password}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

パスワードはオプションですが、渡す場合は文字列にする必要があり、やはり長さ 6 ~ 15 の文字列にする必要があります。したがって、None が渡された場合、デフォルト値を宣言する場合、None と Query(None) は同等ですが、Query はパラメータを制限する他のパラメータもサポートしています。

request.get("http://localhost:5555/user?password=12345").json()
requests.get("http://localhost:5555/user?password=123456").json()

クエリの最小長と最大長を制限することに加えて、他の関数もあります

クエリパラメータと文字列検証

fastapi から FastAPI をインポート

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar" }]}
    if q:
        results.update({"q": q})
    結果を返します

クエリ パラメーター q のタイプは であり str、デフォルトは である Noneため、オプションです。

追加のチェックサム

制約の追加: q オプションであっても、このパラメーターが指定されている限り、パラメーター値の長さは 50 文字を超えることはできません

fastapi からクエリをインポートします。

import Union の入力から

fastapi からインポート FastAPI、クエリ

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, max_length=50)):
    results = {"items": [{"item_id": " Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    結果を返す

さらにチェックを追加

パラメーターを追加することもできます min_length 。

import Union の入力から

fastapi からインポート FastAPI、クエリ

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Union[str, None] = Query(default=None, min_length=3, max_length=50)
):
    results = {"items": [{" item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    結果を返す

正規表現を追加する

パラメータ値が一致する必要がある正規表現を定義できます。

import Union の入力から

fastapi からインポート FastAPI、クエリ

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Union[str, None] = Query(
        default=None, min_length=3, max_length=50, pattern="^fixedquery$"
    )
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    結果を返す

必須パラメータとして宣言されている

... は Python の特別なオブジェクトであり、これを通じてパラメータが必須パラメータであることを認識できます。

from fastapi import FastAPI, Query
import uvicorn

app = FastAPI()


@app.get("/user")
async def check_length(password: str = Query(..., min_length=6)):
    """将第一个参数换成 ... 即可实现该参数是必传参数
    """
    return {"password": password}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

クエリパラメータはリストになります

を指定した場合 a=1&a=2、 を取得したときにリストを取得するにはどうすればよいでしょうか?

from typing import Optional, List
from fastapi import FastAPI, Query
import uvicorn

app = FastAPI()


@app.get("/items")
async def read_items(
        a1: str = Query(...),
        a2: List[str] = Query(...),
        b: List[str] = Query(...)
):
    return {"a1": a1, "a2": a2, "b": b}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

最初の「a2」と「b」は対応するリストであり、次に「a1」は最後の値のみを取得します。また、「ちょっとくどいな」と思われる方もいるかもしれませんが、関数宣言ではこのように書いても良いのでしょうか?

@app.get("/items")
async def read_items(
        a1: str,
        a2: List[str],
        b: List[str]
):
    return {"a1": a1, "a2": a2, "b": b}

a1 はOKですが、a2 と b はダメです。リスト型のクエリ パラメータの場合、デフォルト値があるかどうかに関係なく、クエリを明示的に追加して必要なパラメータを示す必要があります。None が許可される (またはデフォルト値がある) 場合は、次のように記述する必要があります。

from typing import Optional, List
from fastapi import FastAPI, Query
import uvicorn

app = FastAPI()


@app.get("/items")
async def read_items(
        a1: str,
        a2: Optional[List[str]] = Query(None),
        b: List[str] = Query(["1", "嘿嘿"])
):
    return {"a1": a1, "a2": a2, "b": b}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

パラメータの別名

定義したクエリ パラメータの名前が item-query であるとします。関数パラメータに反映する必要があるため、明らかに Python 変数の命名規則に準拠していません。このとき、どうすればよいでしょうか。答えは別名です。

from typing import Optional, List
from fastapi import FastAPI, Query
import uvicorn

app = FastAPI()


@app.get("/items")
async def read_items(
        # 通过 url 的时候使用别名即可
        item1: Optional[str] = Query(None, alias="item-query"),
        item2: str = Query("哈哈", alias="@@@@"),
        item3: str = Query(..., alias="$$$$")  # item3 是必传的
):
    return {"item1": item1, "item2": item2, "item3": item3}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

数値検出

クエリは文字列の検証だけでなく、値の検証もサポートしています。gt、ge、lt、le などのパラメータを渡すことができます。これらのパラメータが何をするのかは言わずもがなであると思います。例を示します。 :

from fastapi import FastAPI, Query
import uvicorn

app = FastAPI()


@app.get("/items")
async def read_items(
        # item1 必须大于 5
        item1: int = Query(..., gt=5),
        # item2 必须小于等于 7
        item2: int = Query(..., le=7),
        # item3 必须必须等于 10
        item3: int = Query(..., ge=10, le=10)
):
    return {"item1": item1, "item2": item2, "item3": item3}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

リクエストオブジェクト

リクエストとは何ですか? まず、あらゆるリクエストが Request オブジェクトに対応し、リクエストされたすべての情報がこの Request オブジェクトに含まれており、FastAPI も例外ではないことがわかります。

パスパラメータはパラメータに反映する必要がありますが、リクエスト: Request を定義したため、クエリパラメータを記述することはできません。リクエストに関連するすべての情報が Request オブジェクトに入ります。

from fastapi import FastAPI, Request
import uvicorn

app = FastAPI()


@app.get("/girl/{user_id}")
async def read_girl(user_id: str,
                    request: Request):
    """路径参数是必须要体现在参数中,但是查询参数可以不写了
       因为我们定义了 request: Request,那么请求相关的所有信息都会进入到这个 Request 对象中"""
    header = request.headers  # 请求头
    method = request.method  # 请求方法
    cookies = request.cookies  # cookies
    query_params = request.query_params  # 查询参数
    return {"name": query_params.get("name"), "age": query_params.get("age"), "hobby": query_params.getlist("hobby")}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

すべてのリクエスト関連情報は、Request オブジェクトを通じて取得できます。以前に間違ったパラメータを渡した場合、FastAPI は自動的にエラー メッセージを返しましたが、Request を通じて自分でそれを分析し、返されたエラー メッセージを指定できます。

応答オブジェクト

Request があるため、Response も必要です。辞書を直接返すこともできますが、実際には FastAPI が辞書を Response オブジェクトに変換します。

応答は内部的に次のパラメーターを受け取ります。

  • content:返回的数据
  • status_code:状态码
  • headers:返回的请求头
  • media_type:响应类型(就是 HTML 中 Content-Type,只不过这里换了个名字)
  • background:接收一个任务,Response 在返回之后会自动异步执行

from fastapi import FastAPI, Request, Response
import uvicorn
import orjson

app = FastAPI()


@app.get("/girl/{user_id}")
async def read_girl(user_id: str, request: Request):
    query_params = request.query_params  # 查询参数
    data = {
        "name": query_params.get("name"), 
        "age": query_params.get("age"), 
        "hobby": query_params.getlist("hobby")
    }
    # 实例化一个 Response 对象
    response = Response(
        # content,我们需要手动转成 json 字符串,如果直接返回字典的话,那么在包装成 Response 对象的时候会自动帮你转
        orjson.dumps(data),
        # status_code,状态码
        201,
        # headers,响应头
        {"Token": "xxx"},
        # media_type,就是 HTML 中的 Content-Type
        "application/json",
    )
    # 如果想设置 cookie 的话,那么通过 response.set_cookie 即可
    # 删除 cookie 则是 response.delete_cookie
    return response


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

Response を通じて、リクエスト ヘッダー、ステータス コード、Cookie などをカスタマイズできます。

Response に加えて、FileResponse、HTMLResponse、PlainTextResponse など、他の多くのタイプの応答があり、それらはすべて fastapi.responses にあります。これらはすべて Response を継承しますが、応答タイプは自動的に設定されます。次に例を示します。

from fastapi import FastAPI
from fastapi.responses import Response, HTMLResponse
import uvicorn

app = FastAPI()


@app.get("/index")
async def index():
    response1 = HTMLResponse("<h1>你好呀</h1>")
    response2 = Response("<h1>你好呀</h1>", media_type="text/html")
    # 以上两者是等价的,在 HTMLResponse 中会自动将 media_type 设置成 text/html
    return response1


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

 パラメーターを使用して response_model、応答に使用するモデルを宣言します。

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

app = FastAPI()


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


@app.post("/items/", response_model=Item)
async def create_item(item: Item):
    return item
  • response_model_exclude_unset=True: それらのデフォルト値は応答には含まれず、実際に設定された値のみが含まれます
  • response_model_include どのような属性が含まれるか
  • response_model_exclude 特定の属性を省略する

status_codeパラメータを使用して、応答に使用する HTTP ステータス コードを宣言します。

from fastapi import FastAPI

app = FastAPI()


@app.post("/items/", status_code=201)
async def create_item(name: str):
    return {"name": name}

フォームフィールドの場合は、次を使用しますForm

from fastapi import FastAPI, Form

app = FastAPI()


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

Fileクライアントを定義するために使用されるアップロード ファイル (事前にインストールされるアップロード ファイルを受け取りますpython-multipart):

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}

HTTP エラー応答をクライアントに返します。すぐに使用できますHTTPException

from fastapi import FastAPI, HTTPException

app = FastAPI()

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


@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item": items[item_id]}

response_description設定応答の説明を使用します。

from typing import Optional, Set

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


@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

リクエストボディ

クライアント(ブラウザなど)からAPIにデータを送信する必要がある場合、データは「リクエストボディ」として送信されます。

リクエストボディは、クライアントによって API に送信されるデータです。

応答本文は、API がクライアントに送信するデータです。

POSTデータを送信するには、 (より一般的)、PUTDELETE または のいずれかの方法を使用する必要がありますPATCH

明らかに、POST、PUT、その他のタイプのリクエストに対応して、リクエスト本文を解析して応答本文を構築できなければなりません。

モデル

FastAPI では、リクエストボディとレスポンスボディの両方がモデルに対応します。

from typing import Optional, List
from fastapi import FastAPI, Request, Response
from pydantic import BaseModel
import uvicorn

app = FastAPI()


class Girl(BaseModel):
    """数据验证是通过 pydantic 实现的,我们需要从中导入 BaseModel,然后继承它"""
    name: str
    age: Optional[str] = None
    length: float
    hobby: List[str]  # 对于 Model 中的 List[str] 我们不需要指定 Query(准确的说是 Field)


@app.post("/girl")
async def read_girl(girl: Girl):
    # girl 就是我们接收的请求体,它需要通过 json 来传递,并且这个 json 要有上面的四个字段(age 可以没有)
    # 通过 girl.xxx 的方式我们可以获取和修改内部的所有属性
    return dict(girl)  # 直接返回 Model 对象也是可以的


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

例:

fastapi から FastAPI をインポート
pydantic から BaseModel をインポート


クラス項目(BaseModel):
    名前: str
    説明: str | なし = なし
    価格: フロート
    税: フロート | なし = なし


app = FastAPI()


@app.post("/items/")
async def create_item(item:Item):
    item_dict = item.dict()
    if item.tax:price_with_tax
        = item.price + item.tax
        item_dict.update({"price_with_tax": Price_with_tax})
    return item_dict

クエリ パラメーターを宣言する場合と同様、モデル プロパティにデフォルト値がある場合、それは必要ありません。それ以外の場合は必須のプロパティです。デフォルトを設定して None オプションのプロパティにします。

Request オブジェクトを使用して本文を渡します

Pydantic モデルを使用したくない場合は、  Body パラメーターを使用することもできます。ドキュメント 「リクエストボディ - 複数のパラメータ: リクエストボディの単一値」を参照してください。

from fastapi import FastAPI, Request
import uvicorn

app = FastAPI()


@app.post("/girl")
async def read_girl(request: Request):
    # 是一个协程,所以需要 await
    data = await request.body()
    print(data)


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

リクエスト モジュールを使用してポスト リクエストを送信する場合、data パラメータまたは json パラメータを介してリクエストを渡すことができます。

  • json={"name": "satori", "age": 16, "length": 155.5} を渡すとjson文字列に変換されて送信され、プログラム内の印刷は以下のように表示されます: b'{ "名前": "サトリ"、"年齢": 16、"身長": 155.5}'
  • data パラメータを使用してリクエストを送信した場合(値は変更されない)、k1=v1&k2=v2 の形式に結合されて送信されます(後述のフォーム送信に相当)。プログラムは次のように出力します: b'name=satori&age= 16&length=155.5'

したがって、await request.body() が取得するのは最も原始的なバイト ストリームであり、await request.body() のほかに await request.json() もありますが、後者は前者を内部で呼び出して到達後に取得することがわかります。バイト ストリームを読み込むと、辞書へのロードが自動的に行われます。したがって、await request.json() を使用するには、リクエストの送信時に渡す json パラメーターを使用する必要があります (辞書から変換された json が渡されるため、辞書に解析することもできます)。そうでない場合は、await を使用します。 request.json() を正しく解析できませんでした。

パスパラメータ、クエリパラメータ、リクエストボディ

  • このパラメータがpath 内でも宣言されている場合は 、 path パラメータとして使用されます。
  • パラメータが 単一タイプ(例 int、 、floatstrなど) の場合、クエリ boolパラメータ として解釈されます 
  • パラメータの型が Pydantic モデルとして宣言されている場合、それはリクエストの本文として解釈されます 
# -*- coding:utf-8 -*-
# @Author: komeiji satori
from typing import Optional, List
from fastapi import FastAPI, Request, Response
from pydantic import BaseModel
import uvicorn

app = FastAPI()


class Girl(BaseModel):
    name: str
    age: Optional[str] = None
    length: float
    hobby: List[str]


@app.post("/girl/{user_id}")
async def read_girl(user_id, q: str, girl: Girl):
    return {"user_id": user_id, "q": q, **dict(girl)}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

パス パラメーター、クエリ パラメーター、およびリクエスト本文を指定した後でも、FastAPI は正しく区別できます。もちろん、Request オブジェクトを使用することもできます。

from typing import Optional, List, Dict
from fastapi import FastAPI, Request, Response
from pydantic import BaseModel
import uvicorn

app = FastAPI()


@app.post("/girl/{user_id}")
async def read_girl(user_id, request: Request):
    q = request.query_params.get("q")
    data: Dict = await request.json()
    data.update({"user_id": user_id, "q": q})
    return data


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

単一のリクエストボディパラメータを埋め込む

Item Pydantic モデルからのリクエスト本文パラメータが 1 つだけあると仮定します itemデフォルトでは、FastAPI は そのようなリクエスト本文を直接期待します。item ただし、追加の本文パラメータを宣言する場合と同様に、値にキーとモデル コンテンツを含む JSON を 期待する場合は 、特別なBody パラメータ を使用できますembeditem: Item = Body(embed=True)

入力から import 注釈付き

from fastapi import Body、FastAPI
from pydantic import BaseModel

app = FastAPI()


クラス項目(BaseModel):
    名前: str
    説明: str | なし = なし
    価格: フロート
    税: フロート | なし = なし


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
    results = {"item_id": item_id, "item": item} が
    結果を返します

この場合、FastAPI は 次のようなリクエスト本文を想定します。

{     "アイテム": {         "名前": "フー"、         "説明": "詐欺師"、         "価格": 42.0、         "税金": 3.2     } }






の代わりに:

{     "名前": "フー"、     "説明": "詐欺師"、     "価格": 42.0、     "税": 3.2 }




リクエストに含めることができるリクエスト本体は1 つだけですが、複数のリクエスト本体パラメータをルート操作関数に追加できます。

リクエスト本文の一部として受信される単一の値を宣言することもできます。

リクエストボディパラメータが 1 つだけ宣言されている場合、FastAPI に元のリクエストボディをキーに埋め込むように指示することもできます。

複数のリクエストボディパラメータ

# -*- coding:utf-8 -*-
# @Author: komeiji satori
from typing import Optional, List
from fastapi import FastAPI, Request, Response
from pydantic import BaseModel
import uvicorn

app = FastAPI()


class Girl(BaseModel):
    name: str
    age: Optional[str] = None


class Boy(BaseModel):
    name: str
    age: int


@app.post("/boy_and_girl")
async def read_boy_and_girl(girl: Girl, boy: Boy):
    return {"girl": dict(girl), "boy": dict(boy)}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

この時、渡す場合は以下のように渡す必要があります。

2 つの JSON をネストして、より大きな JSON を形成する必要があります。キーは関数パラメーター名です。したがって、このメソッドは実際には次と同等です。

# -*- coding:utf-8 -*-
# @Author: komeiji satori
from typing import Optional, List, Dict
from fastapi import FastAPI, Request, Response
from pydantic import BaseModel
import uvicorn

app = FastAPI()


class BoyAndGirl(BaseModel):
    girl: Dict
    boy: Dict


@app.post("/boy_and_girl")
async def read_boy_and_girl(boy_and_girl: BoyAndGirl):
    return dict(boy_and_girl)


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

この方法も実現可能ですが、辞書内の辞書に制限はありません。もちろん、辞書を取得した後も、Request オブジェクトを使用して自分で判断することはできます。これは、JSON の場合、内部フィールドが変更される可能性があり、最も重要なことは、多くのフィールドが存在する可能性があるためです。現時点では、個人的には Request オブジェクトを使用することを好みます。

PathQuery リクエスト本文パラメータの混合使用 

PathuseとQuery request body パラメーター宣言を自由に組み合わせて使用​​すると 、FastAPI がそれを処理する方法を認識します。

入力から import 注釈付き


fastapi import FastAPI から、 pydantic import BaseModel からのパス

app = FastAPI()


クラス項目(BaseModel):
    名前: str
    説明: str | なし = なし
    価格: フロート
    税: フロート | なし = なし


@app.put("/items/{item_id}")
async def update_item(
    item_id: Annotated[int, Path(title="取得するアイテムのID", ge=0, le=1000)], q
    : str | None = なし、
    item: item | None = なし、
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    if item:
        results.update({"item ": item})
    結果を返します

この場合、 リクエスト本文から何を取得するかはitem オプションであることに注意してください。デフォルト値が であるためです None

複数のリクエストボディパラメータ

fastapi から FastAPI をインポート
pydantic から BaseModel をインポート

app = FastAPI()


クラス項目(BaseModel):
    名前: str
    説明: str | なし = なし
    価格: フロート
    税: フロート | なし = なし


クラス ユーザー(BaseModel):
    ユーザー名: str
    full_name: str | なし = なし


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: アイテム, user: ユーザー):
    results = {"item_id": item_id, "item": item, "user": user} が
    結果を返します

リクエストボディ

{     "アイテム": {         "名前": "フー",         "説明": "詐欺師",         "価格": 42.0,         "税": 3.2     },     "ユーザー": {         "ユーザー名": "デイブ",         " full_name": "デイブ・グロール"     } }










リクエスト本文内の単一の値

入力から import 注釈付き

from fastapi import Body、FastAPI
from pydantic import BaseModel

app = FastAPI()


クラス項目(BaseModel):
    名前: str
    説明: str | なし = なし
    価格: フロート
    税: フロート | なし = なし


クラス ユーザー(BaseModel):
    ユーザー名: str
    full_name: str | なし = なし


@app.put("/items/{item_id}")
async def update_item(
    item_id: int, item: アイテム、ユーザー: ユーザー、重要度: Annotated[int, Body()]
):
    results = {"item_id": item_id , "item": アイテム、"user": ユーザー、"importance": 重要度}
    結果を返します

リクエスト本文:

{     "アイテム": {         "名前": "フー",         "説明": "詐欺師",         "価格": 42.0,         "税": 3.2     },     "ユーザー": {         "ユーザー名": "デイブ",         " full_name": "デイブ・グロール"     },     "重要性": 5 }











複数のリクエストボディパラメータとクエリパラメータ

デフォルトでは、単一の値はクエリパラメータとして解釈されるため、明示的に追加する必要はありません Query

入力から import 注釈付き

from fastapi import Body、FastAPI
from pydantic import BaseModel

app = FastAPI()


クラス項目(BaseModel):
    名前: str
    説明: str | なし = なし
    価格: フロート
    税: フロート | なし = なし


クラス ユーザー(BaseModel):
    ユーザー名: str
    full_name: str | なし = なし


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int,
    item: アイテム,
    ユーザー: ユーザー,
    重要度: Annotated[int, Body(gt=0)],
    q: str | None = なし、
):
    results = {"item_id": item_id、"item": item、"user": ユーザー、"importance": 重要度}
    if q:
        results.update({"q": q})
    結果を返す

フォームフォーム

request.post を呼び出すと、パラメータがデータを介して渡される場合、それはフォームフォームを送信することと同等であるため、FastAPI では await request.form() を通じて取得できます。 注: 内部的には、await request.body() も最初に呼ばれました。

from fastapi import FastAPI, Request, Response
import uvicorn

app = FastAPI()


@app.post("/girl")
async def girl(request: Request):
    # 此时 await request.json() 报错,因为是通过 data 参数传递的,相当于 form 表单提交
    # 如果是通过 json 参数传递,那么 await request.form() 会得到一个空表单
    form = await request.form()
    return [form.get("name"), form.getlist("age")]


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

他の方法も使用できます。

# -*- coding:utf-8 -*-
# @Author: komeiji satori
from fastapi import FastAPI, Form
import uvicorn

app = FastAPI()


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


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

Form と同様に、クエリ パラメータ、パス パラメータなどを Request オブジェクトと一緒に使用できます。上記の例のように、もう 1 つのリクエスト Request を定義した場合でも、await request.form() を通じて関連するフォームを取得できます。情報。そのため、特定のパラメータが型アノテーションに適していないと思われる場合は、Request オブジェクトのみを使用してそれを分析できます。

ファイルのアップロード

FastAPI はユーザー ファイルのアップロードをどのように受信しますか? まず、ファイルアップロード機能を使用したい場合は、パッケージ python-multipart をインストールする必要があります。 pip install python-multipart を直接実行するだけです。

from fastapi import FastAPI, File, UploadFile
import uvicorn

app = FastAPI()


@app.post("/file1")
async def file1(file: bytes = File(...)):
    return f"文件长度: {len(file)}"


@app.post("/file2")
async def file1(file: UploadFile = File(...)):
    return f"文件名: {file.filename}, 文件大小: {len(await file.read())}"


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

1 つはバイト ストリームを直接取得し、もう 1 つはファイル ハンドルに似たオブジェクトを取得することがわかります。複数のファイルがアップロードされた場合はどうすればよいですか?

from typing import List
from fastapi import FastAPI, UploadFile, File
import uvicorn

app = FastAPI()


@app.post("/file")
async def file(files: List[UploadFile] = File(...)):
    """指定类型为列表即可"""
    for idx, f in enumerate(files):
        files[idx] = f"文件名: {f.filename}, 文件大小: {len(await f.read())}"
    return files


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

この時点で、FastAPI ファイルのアップロードが実装されています。もちろん、ファイルのアップロードはフォームの処理には影響しません。ファイルとフォームを同時に処理してみることもできます。

静的リソースを返す

aiofiles をインストールする必要があります。pip で直接インストールするだけです。

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
import uvicorn

app = FastAPI()

# name 参数只是起一个名字,FastAPI 内部使用
app.mount("/static", StaticFiles(directory=r"C:\Users\satori\Desktop\bg"), name="static")

if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

ブラウザ入力: localhost:5555/static/1.png の場合、C:\Users\satori\Desktop\bg にある 1.png ファイルが返されます。

 サブアプリケーション

2 つの独立した FastAPI アプリケーションがある場合は、1 つをメイン アプリケーションとして設定し、もう 1 つをサブアプリケーションとして設定できます。

from fastapi import FastAPI

app = FastAPI()


@app.get("/app")
def read_main():
    return {"message": "Hello World from main app"}


subapi = FastAPI()


@subapi.get("/sub")
def read_sub():
    return {"message": "Hello World from sub API"}


app.mount("/subapi", subapi)

演技

root_pathプロキシの設定に使用できます。

コマンドラインを使用します: uvicorn main:app --root-path /api/v1 

または、コードで設定します。

from fastapi import FastAPI, Request

app = FastAPI(root_path="/api/v1")


@app.get("/app")
def read_main(request: Request):
    return {"message": "Hello World", "root_path": request.scope.get("root_path")}

テンプレートを使用する

FastAPIでは任意のテンプレートを使用できますが、一般的な選択は Jinja2 です。インストール: pip install jinja2

使用:

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles

from fastapi.templating import Jinja2Templates

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")

templates = Jinja2Templates(directory="templates")


@app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
    return templates.TemplateResponse("item.html", {"request": request, "id": id})

テンプレートファイルtemplates/item.html:

<html>
<head>
    <title>アイテムの詳細</title>
    <link href="{ { url_for('static', path='/styles.css') }}" rel="stylesheet">
</head>
<本文>

    <h1>アイテム ID: { { id }}</h1>

</body>
</html>

エラー処理

from fastapi import FastAPI, HTTPException
import uvicorn

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id != "foo":
        # 里面还可以传入 headers 设置响应头
        raise HTTPException(status_code=404, detail="item 没有发现")
    return {"item": "bar"}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

HTTPException は、API の関連情報を運ぶ通常の Python 例外クラス (Exception から継承) であり、例外であるため返すことはできませんが、発生させます。このメソッドは、情報が少なすぎる可能性があるため、エラーを返します。

カスタム例外

FastAPI は内部的に HTTPException を提供しますが、これをカスタマイズすることもできます。ただし、注意: 例外をカスタマイズした後、ハンドラーを定義し、例外とハンドラーをバインドし、例外がハンドラーでスローされたときに対応する例外をトリガーする必要があります。

from fastapi import FastAPI, Request
from fastapi.responses import ORJSONResponse
import uvicorn

app = FastAPI()


class ASCIIException(Exception):
    """"""
    pass


# 通过装饰器的方式,将 ASCIIException 和 ascii_exception_handler 绑定在一起
@app.exception_handler(ASCIIException)
async def ascii_exception_handler(request: Request, exc: ASCIIException):
    """当引发 ASCIIException 的时候,会触发 ascii_exception_handler 的执行
       同时会将 request 和 exception 传过去"""
    return ORJSONResponse(status_code=404, content={"code": 404, "message": "你必须传递 ascii 字符串"})


@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if not item_id.isascii():
        raise ASCIIException
    return {"item": f"get {item_id}"}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

リクエストとレスポンスに関しては、fastapi のルートマッピングが starlette で実現されているため、fastapi だけでなく starlette からもインポートできます。

カスタム404

存在しない URL にアクセスするときは、ユーザーに次のようにプロンプ​​トを表示する必要があります。「ページを見つけるために火星に行くつもりです。」

from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
from fastapi.exceptions import StarletteHTTPException
import uvicorn

app = FastAPI()


@app.exception_handler(StarletteHTTPException)
async def not_found(request, exc):
    return ORJSONResponse({"code": 404, "message": "您要找的页面去火星了。。。"})


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

この時点で、存在しない URL にアクセスすると、カスタム JSON 文字列が返されます。

バックグラウンドタスク

バックグラウンド タスクは、応答が返された直後に実行されるタスクです。

リクエストに時間がかかる場合は、バックグラウンドで実行できます。これは FastAPI がすでに実行しています。見てみましょう:

import time
from fastapi import FastAPI, BackgroundTasks
from starlette.background import BackgroundTask
from fastapi import Response, Request
import uvicorn
import orjson


app = FastAPI()


def send_email(email: str, message: str = ""):
    """发送邮件,假设耗时三秒"""
    time.sleep(3)
    print(f"三秒之后邮件发送给 {email!r}, 邮件信息: {message!r}")


@app.get("/user/{email}")
async def order(email: str, bg_tasks: BackgroundTasks):
    """这里需要多定义一个参数
       此时任务就被添加到后台,当 Response 对象返回之后触发"""
    bg_tasks.add_task(send_email, email, message="这是一封邮件")
    # 我们在之前介绍 Response 的时候说过,里面有一个参数 background
    # 所以我们也可以将任务放在那里面
    # 因此我们还可以:
    # return Response(
    #     orjson.dumps({"message": "邮件发送成功"}), 
    #     background=BackgroundTask(send_email, email, message="这是一封邮件")
    # )
    return {"message": "邮件发送成功"}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

APIルーター

APIRouter は Flask のブループリントに似ており、大規模なプロジェクトをより適切に整理できます。次に例を示します。

現在のプロジェクト ディレクトリには、app ディレクトリと main.py があり、app ディレクトリには app01.py があります。これらがどのように構成されているかを見てみましょう。

app/app01.py

# app/app01.py
from fastapi import APIRouter

router = APIRouter(prefix="/router")


# 以后访问的时候要通过 /router/v1 来访问
@router.get("/v1")
async def v1():
    return {"message": "hello world"}

main.py

# main.py
from fastapi import FastAPI
from app.app01 import router
import uvicorn

app = FastAPI()

# 将 router 注册到 app 中,相当于 Flask 中的 register_blueprint
app.include_router(router)

if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

これで、/router/v1 を介して外部からアクセスできるようになります。

例:

from fastapi import APIRouter

router = APIRouter()


@router.get("/users/", tags=["users"])
async def read_users():
    return [{"username": "Rick"}, {"username": "Morty"}]


@router.get("/users/me", tags=["users"])
async def read_user_me():
    return {"username": "fakecurrentuser"}


@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
    return {"username": username}

すべてのパスに対して同じことを行います。

from fastapi import APIRouter, Depends, HTTPException

from ..dependencies import get_token_header

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

fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}


@router.get("/")
async def read_items():
    return fake_items_db


@router.get("/{item_id}")
async def read_item(item_id: str):
    if item_id not in fake_items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"name": fake_items_db[item_id]["name"], "item_id": item_id}


@router.put(
    "/{item_id}",
    tags=["custom"],
    responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
    if item_id != "plumbus":
        raise HTTPException(
            status_code=403, detail="You can only update the item: plumbus"
        )
    return {"item_id": item_id, "name": "The great Plumbus"}

この例では、各パスを個別に宣言するのではなく、プレフィックス、ラベル、依存関係を追加し、すべてのパスに戻り値を追加するため、コードが簡素化されます。

ミドルウェア

ミドルウェアはWeb開発において非常に一般的であると言えますが、端的に言えば、ミドルウェアは関数またはクラスです。リクエストがビュー関数に入る前に、ミドルウェア (リクエストミドルウェアと呼ばれます) を通過し、ミドルウェアでは、ビュー関数が応答を返すときと同様に、リクエストに対して前処理を実行したり、インターセプタを実装したりすることができます。その後、ミドルウェア(レスポンスミドルウェアと呼ばれます)も経由し、ミドルウェア内でレスポンスを磨き上げることもできます。

カスタムミドルウェア

FastAPIはFlaskのようなカスタムミドルウェアもサポートしていますが、Flaskにはリクエストミドルウェアとレスポンスミドルウェアがありますが、FastAPIではその2つが1つにまとめられているので、使い方を見てみましょう。

from fastapi import FastAPI, Request, Response
import uvicorn
import orjson

app = FastAPI()


@app.get("/")
async def view_func(request: Request):
    return {"name": "古明地觉"}


@app.middleware("http")
async def middleware(request: Request, call_next):
    """
    定义一个协程函数,然后使用 @app.middleware("http") 装饰,即可得到中间件
    """
    # 请求到来时会先经过这里的中间件
    if request.headers.get("ping", "") != "pong":
        response = Response(content=orjson.dumps({"error": "请求头中缺少指定字段"}),
                            media_type="application/json",
                            status_code=404)
        # 当请求头中缺少 "ping": "pong",在中间件这一步就直接返回了,就不会再往下走了
        # 所以此时就相当于实现了一个拦截器
        return response
    # 然后,如果条件满足,则执行 await call_next(request),关键是这里的 call_next
    # 如果该中间件后面还有中间件,那么 call_next 就是下一个中间件;如果没有,那么 call_next 就是对应的视图函数
    # 这里显然是视图函数,因此执行之后会拿到视图函数返回的 Response 对象
    # 所以我们看到在 FastAPI 中,请求中间件和响应中间件合在一起了
    response: Response = await call_next(request)
    # 这里我们在设置一个响应头
    response.headers["status"] = "success"
    return response


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

組み込みミドルウェア

ミドルウェアをカスタマイズすることで、ビュー機能を変更することなく機能を拡張できます。ただし、カスタム ミドルウェアに加えて、FastAPI は多くの組み込みミドルウェアも提供します。

from fastapi import FastAPI

app = FastAPI()

# 要求请求协议必须是 https 或者 wss,如果不是,则自动跳转
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
app.add_middleware(HTTPSRedirectMiddleware)

# 请求中必须包含 Host 字段,为防止 HTTP 主机报头攻击,并且添加中间件的时候,还可以指定一个 allowed_hosts,那么它是干什么的呢?
# 假设我们有服务 a.example.com, b.example.com, c.example.com
# 但我们不希望用户访问 c.example.com,就可以像下面这么设置,如果指定为 ["*"],或者不指定 allow_hosts,则表示无限制
from starlette.middleware.trustedhost import TrustedHostMiddleware
app.add_middleware(TrustedHostMiddleware, allowed_hosts=["a.example.com", "b.example.com"])

# 如果用户的请求头的 Accept-Encoding 字段包含 gzip,那么 FastAPI 会使用 GZip 算法压缩
# minimum_size=1000 表示当大小不超过 1000 字节的时候就不压缩了
from starlette.middleware.gzip import GZipMiddleware
app.add_middleware(GZipMiddleware, minimum_size=1000)

これらに加えて、他にも組み込みミドルウェアがあり、自分で確認できますが、あまり一般的には使用されていません。

CORS (クロスドメイン設定)

CORS は重要すぎるため、個別に説明する必要があります。

CORS (Cross-Origin Resource Sharing) とは、ブラウザーで実行されているフロントエンドにバックエンドと通信する JavaScript コードがあり、フロントエンドとバックエンドが異なるソースにある状況を指します。ソース: プロトコル (http、https)、ドメイン (baidu.com、app.com、localhost)、およびポート (80、443、8000)。違いがある限り、それは別のソースです。たとえば、以下はすべて異なるソースです。

  • http://localhost
  • https://localhost
  • http://localhost:8080

どちらもローカルホストですが、使用するプロトコルまたはポートが異なるため、生成元が異なります。フロントエンドが localhost:8080 で実行され、localhost:5555 と通信しようとすると、ブラウザは HTTP OPTIONS リクエストをバックエンドに送信し、バックエンドはこのソースを承認するための適切なヘッダーを送信します。そのため、バックエンドは「フロントエンドに対応するソースが許可されている場合、ブラウザはフロントエンドがバックエンドにリクエストを送信することを許可します。許可されていない場合は、クロスドメインエラーが発生します。

デフォルトでは、フロントエンドとバックエンドは同じソース上にある必要があります。ソースが異なる場合、フロントエンドのリクエストは失敗します。フロントエンドとバックエンドの分離はすでに主流となっているため、クロスドメインの問題を解決する必要があります。

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import uvicorn

app = FastAPI()
app.add_middleware(
    CORSMiddleware,
    # 允许跨域的源列表,例如 ["http://www.example.org"] 等等,["*"] 表示允许任何源
    allow_origins=["*"],
    # 跨域请求是否支持 cookie,默认是 False,如果为 True,allow_origins 必须为具体的源,不可以是 ["*"]
    allow_credentials=False,
    # 允许跨域请求的 HTTP 方法列表,默认是 ["GET"]
    allow_methods=["*"],
    # 允许跨域请求的 HTTP 请求头列表,默认是 [],可以使用 ["*"] 表示允许所有的请求头
    # 当然 Accept、Accept-Language、Content-Language 以及 Content-Type 总之被允许的
    allow_headers=["*"],
    # 可以被浏览器访问的响应头, 默认是 [],一般很少指定
    # expose_headers=["*"]
    # 设定浏览器缓存 CORS 响应的最长时间,单位是秒。默认为 600,一般也很少指定
    # max_age=1000
)

if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

上記により、クロスドメインの問題を解決できます。

CORSMiddlewareクロスドメインの構成に使用します。

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

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

]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],

)


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

次のパラメータをサポートしています。

  • allow_origins - クロスオリジンリクエストを許可するオリジンのリスト。たとえば ['https://example.org', 'https://www.example.org']['*'] 任意のオリジンを許可することができます 。
  • allow_origin_regex - クロスオリジンリクエストを許可するオリジンと一致する正規表現文字列。たとえば 'https://.*\.example\.org'
  • allow_methods - クロスオリジンリクエストに許可される HTTP メソッドのリスト。デフォルトは です ['GET']を使用して、すべての標準メソッドを許可できます ['*'] 。
  • allow_headers - クロスオリジンリクエストを許可する HTTP ヘッダーのリスト。デフォルトは です []['*'] すべて許可リクエストヘッダーを使用できます 。AcceptAccept-LanguageContent-Language および Content-Type リクエスト ヘッダーは常に CORS リクエストを許可します。
  • allow_credentials - クロスオリジンリクエストが Cookie をサポートしていることを示します。デフォルトは です Falseまた、 証明書が許可されている場合allow_origins は設定できず ['*']、発行元を指定する必要があります。
  • expose_headers - ブラウザがアクセスできる応答ヘッダーを示します。デフォルトは です []
  • max_age - ブラウザが CORS 応答をキャッシュする最大時間を秒単位で設定します。デフォルトは です 600

高度な操作

FastAPI の高レベルの操作をいくつか見てみましょう。これらの操作の中には使用できないものもありますが、使用するとはるかに便利になります。

その他の回答

返される json データは次のとおりです: JSONResponse、UJSONResponse、ORJSONResponse、Content-Type は application/json、返される html は HTMLResponse、Content-Type は text/html、返される PlainTextResponse、Content-Type は text/plain ただし、リターン リダイレクト、バイト ストリーム、ファイルという 3 種類の応答を使用することもできます。

リダイレクト

from fastapi import FastAPI
from fastapi.responses import RedirectResponse
import uvicorn

app = FastAPI()


@app.get("/index")
async def index():
    return RedirectResponse("https://www.bilibili.com")


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

ページ内の/indexにアクセスするとbilibiliにジャンプします。

バイトストリーム

バイト ストリームを返すには、非同期ジェネレーターを使用する必要があります。

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import uvicorn

app = FastAPI()


async def some_video():
    for i in range(5):
        yield f"video {i} bytes ".encode("utf-8")


@app.get("/index")
async def index():
    return StreamingResponse(some_video())


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

ファイル オブジェクトがある場合は、それを直接返すこともできます。

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import uvicorn

app = FastAPI()


@app.get("/index")
async def index():
    return StreamingResponse(open("main.py", encoding="utf-8"))


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

書類

ファイルを返す場合は、FileResponse を渡すこともできます。

from fastapi import FastAPI
from fastapi.responses import FileResponse
import uvicorn

app = FastAPI()


@app.get("/index")
async def index():
    # filename 如果给出,它将包含在响应的 Content-Disposition 中。
    return FileResponse("main.py", filename="这不是main.py")


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

HTTP認証

ユーザーがリクエストにアクセスするときに自分の身元を確認するためにユーザー名とパスワードを入力するようにしたい場合はどうすればよいでしょうか?

from fastapi import FastAPI, Depends
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import uvicorn

app = FastAPI()

security = HTTPBasic()


@app.get("/index")
async def index(credentials: HTTPBasicCredentials = Depends(security)):
    return {"username": credentials.username, "password": credentials.password}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

テスト

入力が完了すると、情報は認証情報に保存され、検証のために取得できるようになります。

ウェブソケット

FastAPI が WebSocket を実装する方法:

from fastapi import FastAPI
from fastapi.websockets import WebSocket
import uvicorn

app = FastAPI()


@app.websocket("/ws")
async def ws(websocket: WebSocket):
    await websocket.accept()
    while True:
        # websocket.receive_bytes()
        # websocket.receive_json()
        data = await websocket.receive_text()
        await websocket.send_text(f"收到来自客户端的回复: {data}")


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555)

次に、ブラウザ経由で通信します。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        ws = new WebSocket("ws://localhost:5555/ws");
 
        //如果连接成功, 会打印下面这句话, 否则不会打印
        ws.onopen = function () {
            console.log('连接成功')
        };
 
        //接收数据, 服务端有数据过来, 会执行
        ws.onmessage = function (event) {
            console.log(event)
        };
 
        //服务端主动断开连接, 会执行.
        //客户端主动断开的话, 不执行
        ws.onclose = function () {  }
 
    </script>
</body>
</html>

テスト

例:

from fastapi import FastAPI, WebSocket

from fastapi.responses import HTMLResponse

app = FastAPI()

html = """
<!DOCTYPE html>
<html>
    <head>
        <title>Chat</title>
    </head>
    <body>
        <h1>WebSocket Chat</h1>
        <form action="" onsubmit="sendMessage(event)">
            <input type="text" id="messageText" autocomplete="off"/>
            <button>Send</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
            var ws = new WebSocket("ws://localhost:8000/ws");
            ws.onmessage = function(event) {
                var messages = document.getElementById('messages')
                var message = document.createElement('li')
                var content = document.createTextNode(event.data)
                message.appendChild(content)
                messages.appendChild(message)
            };
            function sendMessage(event) {
                var input = document.getElementById("messageText")
                ws.send(input.value)
                input.value = ''
                event.preventDefault()
            }
        </script>
    </body>
</html>
"""


@app.get("/")
async def get():
    return HTMLResponse(html)


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    while True:
        data = await websocket.receive_text()
        await websocket.send_text(f"Message text was: {data}")

FastAPI サービスのデプロイメント

非同期フレームワークを使用する場合、Web サービスのボトルネックはデータベースにあるため、最も重要なことは、非同期ドライバーを使用してデータベースにアクセスすることです。

FastAPI の内容のほとんどは上で紹介しましたが、次に FastAPI サービスのデプロイメントを見てみましょう. 実際、デプロイメントは非常に簡単で、uvicorn.run を実行するだけです。ただし、ここには多くのパラメータがあるため、主にこれらのパラメータを紹介したいと思います。

def run(app, **kwargs):
    config = Config(app, **kwargs)
    server = Server(config=config)
    ...
    ...

app と **kwargs の両方が Config に渡されていることがわかります。そのため、Config にどのようなパラメーターがあるかを確認するだけで済みます。以下に選択肢があります:

  • app:第一个参数,不需要解释
  • host:监听的ip
  • port:监听的端口
  • uds:绑定的 unix domain socket,一般不用
  • fd:从指定的文件描述符中绑定 socket
  • loop:事件循环实现,可选项为 auto|asyncio|uvloop|iocp
  • http:HTTP 协议实现,可选项为 auto|h11|httptools
  • ws:websocket 协议实现,可选项为 auto|none|websockets|wsproto
  • lifespan:lifespan 实现,可选项为 auto|on|off
  • env_file:环境变量配置文件
  • log_config:日志配置文件
  • log_level:日志等级
  • access_log:是否记录日志
  • use_colors:是否带颜色输出日志信息
  • interface:应用接口,可选 auto|asgi3|asgi2|wsgi
  • debug:是否开启 debug 模式
  • reload:是否自动重启
  • reload_dirs:要自动重启的目录
  • reload_delay:多少秒后自动重启
  • workers:工作进程数
  • limit_concurrency:并发的最大数量
  • limit_max_requests:能 hold 住的最大请求数

3. 例: fastapi 開発インターフェイス (Web レンダリングなしの API インターフェイスのみ)

例: トップ 250 の豆板映画

import requests
from scrapy.http import HtmlResponse
import uvicorn
from pathlib import Path
from fastapi import FastAPI

app = FastAPI()

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                  "(KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.62",
}


def douban_movie_top250(page_num: int):
    index = (page_num - 1) * 25
    # https://movie.douban.com/top250?start=50&filter=
    url = f"https://movie.douban.com/top250?start={index}&filter="
    __resp = requests.get(url, headers=headers)
    if 200 == __resp.status_code:
        resp = HtmlResponse(url, body=__resp.content, encoding='utf-8')
        movie_name_list = resp.xpath('//div[@class="item"]//div[@class="hd"]/a/span[1]/text()').extract()
        movie_url_list = resp.xpath('//div[@class="item"]//div[@class="hd"]/a/@href').extract()
        movie_info_list = list(zip(movie_name_list, movie_url_list))
        return movie_info_list
    else:
        return {'请求失败': f" status_code ---> {__resp.status_code}"}


@app.get("/douban/movie_top250")
async def get_item(page_num):
    """和 Flask 不同,Flask 是使用 <>,而 FastAPI 使用 {}"""
    try:
        page_num_int = int(page_num)
    except BaseException as be:
        return {"错误信息": "页码必须是数字"}
    data = douban_movie_top250(page_num_int)
    return {"data": data}


if __name__ == '__main__':
    '''
    http://127.0.0.1:5555/douban/movie_top250?page_num=1
    '''
    print(f'{Path(__file__).stem}:app')
    uvicorn.run(f'{Path(__file__).stem}:app', host="0.0.0.0", port=5555)
    pass

アクセス:http://127.0.0.1:5555/douban/movie_top250?page_num=5

例: ステーション B でビデオ字幕を取得する

import uvicorn
from pathlib import Path
from fastapi import FastAPI
import requests
import json
from scrapy.http import HtmlResponse

app = FastAPI()


headers = {
    'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
    'cookie': 'buvid3=31D606A5-C08F-CF7F-6345-DFE18CDF1FCA25535infoc; b_nut=1681180325; CURRENT_FNVAL=4048; _uuid=FA2D2910A-1F4C-9ED10-24FB-710C228FE726630982infoc; buvid_fp=dd9cac90362a92030f254a522e274486; buvid4=F13E840C-6245-40B7-9B63-5FDD1992821229542-023041110-0bxmZkxc4Ip6QXGeEfs0Og%3D%3D; CURRENT_PID=130742c0-d811-11ed-ac38-37fb01852f74; rpdid=|(JYYkY~RRYu0J\'uY)uk~lJY|; i-wanna-go-back=-1; header_theme_version=CLOSE; home_feed_column=5; is-2022-channel=1; nostalgia_conf=-1; DedeUserID=384760568; DedeUserID__ckMd5=8fd50449771672ee; b_ut=5; FEED_LIVE_VERSION=V_NO_BANNER_1; bsource=search_bing; browser_resolution=1863-969; bp_video_offset_384760568=787947892852654100; b_lsid=C105138DE_187B25E90A6; SESSDATA=d3f7b6a0%2C1697876752%2C413d0%2A42; bili_jct=e41d9dfdbd372b0cb95222cfa0d33199; sid=59e50ddx; innersign=1; PVID=1; innersign=1'
}


def get_subtitle(video_id=None):
    if video_id:
        url = f'https://www.bilibili.com/video/{video_id}/'
        resp = requests.get(url, headers=headers)
        if 200 == resp.status_code:
            scrapy_resp = HtmlResponse(url, body=resp.content, encoding='utf-8')
            try:
                temp = scrapy_resp.css('html').re('"subtitle_url":"(.*?)"')[0]
            except BaseException as be:
                return {'请求失败': str(be)}
            subtitle_url = temp.replace(r'\u002F', '/')
            print(subtitle_url)
            r = requests.get(subtitle_url)
            if 200 == r.status_code:
                return r.json()
            else:
                return {'请求失败, 失败状态码': resp.status_code}
        else:
            return {'请求失败, 失败状态码': resp.status_code}
    else:
        return {"请求失败": '视频 id 错误'}


@app.get("/bilibili/{video_id}")
async def get_item(video_id):
    """和 Flask 不同,Flask 是使用 <>,而 FastAPI 使用 {}"""
    data = get_subtitle(video_id)
    return {"data": data}


if __name__ == '__main__':
    '''
    http://127.0.0.1:5555/bilibili/BV1bW411n7fY
    '''
    print(f'{Path(__file__).stem}:app')
    uvicorn.run(f'{Path(__file__).stem}:app', host="0.0.0.0", port=5555)
    pass

4. 例: fastapi が Web レンダリング Web サイトを開発

FastAPI は Python Web フレームワークであり、「 Web ページをレンダリングするためのテンプレート エンジン」は付属していませんが、このため、任意の Web テンプレートを使用できます。公式の例は jinjia2 です。

テンプレートは、フルスタック Web 開発の重要な部分です。Jinja を使用すると、Python Web アプリケーションのフロントエンドを強化する豊富なテンプレートを構築できます。

Jinja は、API 応答のレンダリング プロセスを支援するために設計された Python で書かれたテンプレート エンジンです。どのテンプレート言語にも、実際に渡される値に置き換えられる変数があり、テンプレートのレンダリング時にテンプレート ロジックを制御するタグがあります。

jinja2をインストールする

インストール: pip install jinja2 aiofiles

Jinja テンプレートは単なるテキスト ファイルです。Jinja は、任意のテキストベースの形式 (HTML、XML、CSV、LaTeX など) を生成できます。Jinja テンプレートには特定の拡張子を付ける必要はありません。.html、.xml、またはその他の拡張子を使用できます。

テンプレートの拡張子について: ファイルの拡張子に関係なく、任意のファイルをテンプレートとして読み込むことができます。user.html.jinja などの .jinja 拡張子を追加すると、一部の IDE またはエディター プラグインが簡単になる場合がありますが、必須ではありません。自動エスケープはファイル拡張子に基づいて適用できるため、この場合は追加のサフィックスを考慮する必要があります。
テンプレートを識別するためのもう 1 つの優れたヒューリスティックは、拡張子に関係なくテンプレート フォルダーにテンプレートが配置されていることです。これはプロジェクトの一般的なレイアウトです。

Jinja テンプレート エンジンは、中括弧 {} を使用して、その式と構文を通常の HTML、テキスト、およびテンプレート ファイル内のその他の変数と区別します。{ {}} 構文は変数ブロックと呼ばれます。{% %} 構文には、if/else、ループ、マクロなどの制御構造が含まれます。Jinja テンプレート言語で使用される 3 つの一般的な構文ブロックには、次のものがあります。

  • {% ... %}: この構文は制御構造などのステートメントに使用されます。
  • { { todo.item }}: この構文は、渡された式の値を出力するために使用されます。
  • {# Test #}: この構文はコメントを書くときに使用され、ページには表示されません。

Jinja2 は、Flask、Bottle、Pelican、および Django によって使用される人気のあるテンプレート言語です。

最初の Jinja テンプレートをレンダリングする

コード:

import jinja2
environment = jinja2.Environment()
template = environment.from_string("Hello, {
   
   { name }}!")
result = template.render(name="渲染第一个jinja2模板")
print(result)

Jinja のコア コンポーネントは、Environment() クラスです。この例では、パラメータを指定せずに Jinja 環境が作成されます。次に、environment.from_string を使用して環境をカスタマイズします。ここでは、共通の環境を作成し、その中に文字列 Hello, { { name }}! をテンプレートとしてロードします。

この例では、Jinja を使用するときに通常実行される 2 つの重要な手順を示します。

  1. テンプレートの読み込み: プレースホルダー変数を含むソースを読み込みます。デフォルトでは、これらは中括弧のペア { { }} で囲まれています。
  2. テンプレートのレンダリング: プレースホルダーにコンテンツを入力します。コンテキストとして辞書またはキーワード引数を指定できます。

実行結果は以下の通りです。

外部ファイルをテンプレートとして使用する

上記と同じように、外部ファイルをテンプレート ソースとして使用し、プロジェクト内に新しいフォルダーを作成できます。作業ディレクトリに、templates/ という名前のフォルダーを作成します。

その後、テンプレート ディレクトリにindex.html テンプレート ファイルを作成し、Jinja2 構文を使用してそれらをレンダリングできます。たとえば、template/index.html に次の内容を記述します。

<!DOCTYPE html>
<html>
 <head>
 <title>ようこそ</title>
 <link href="{ { url_for('static', path='/styles.css') }}" rel="stylesheet">
 </head>
 <body>
 <h1>こんにちは、{ { name }}</h1>
 </body>
</html>

main.py に戻ります。

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")


@app.get("/{name}")
async def home(request: Request, name: str):
    return templates.TemplateResponse("index.html", {
        "request": request,
        "name": name
    })

if __name__ == '__main__':
    pass

ファイル全体のディレクトリ構造は次のとおりです。

FastAPI サービスを開始します: uvicorn main:app --reload --port 8888

次に、別のターミナルを開いて、curl 127.0.0.1:8888/Yuzhou1su コマンドを実行すると、次のように名前がレンダリングされた結果が表示されます。

ブラウザから http://127.0.0.1:8888/Yuzhou1su にアクセスして、CSS によってレンダリングされた色を確認します。

Jinja テンプレート変数は、文字列に変換できる限り、任意の Python タイプまたはオブジェクトにすることができます。モデル、リスト、またはディクショナリ タイプをテンプレートに渡すことができ、そのプロパティを前にリストした 2 番目のブロックに配置することでそのプロパティが表示されます。次のセクションでは、フィルターについて見ていきます。フィルターはすべてのテンプレート エンジンの重要な部分であり、Jinja フィルターを使用すると、リストからの値の連結やオブジェクトの長さの取得などの特定の機能を実行できます。Jinja で一般的に使用される機能: 変数、フィルター、if ステートメント、ループ、マクロ、テンプレートの継承。

変数

テンプレート変数は、テンプレートに渡されるコンテキスト ディクショナリによって定義されます。

テンプレートでは、変数がアプリケーションによって渡される限り、変数を自由に操作できます。変数には、アクセスできる属性または要素が含まれる場合もあります。変数がどのプロパティを持つかは、変数を提供するアプリケーションによって異なります。

標準の Python __getitem__「添え字」構文 ( [] ) に加えて、ドット ( . ) を使用して変数の属性にアクセスすることもできます。

次の行も同じことを行います。

{
   
   { foo.bar }}
{
   
   { foo['bar'] }}

フィルター

Python と Jinja の構文は非常に似ていますが、文字列の連結、文字列の最初の文字を大文字に設定するなどの変更操作は、Python の構文を使用して Jinja で実行することはできません。したがって、このような変更操作を実行するには、Jinja でフィルターを使用します。

変数はフィルターによって変更できます。フィルターはパイプ記号 (|) で変数から区切られ、オプションのパラメーターは括弧内に含めることができます。複数のフィルターを連鎖させることができます。1 つのフィルターの出力は次のフィルターに適用されます。フィルタの定義形式は以下のとおりです。

{
   
   { variable | filter_name(*args) }}

パラメーターなしでフィルターします:

{
   
   { variable | filter_name }}
{
   
   { name|striptags|title }}

デフォルト フィルタ: 値が未定義の場合は、渡されたデフォルト値を返し、それ以外の場合は変数の値を返します。

{
   
   { my_variable | default('my_variable is not defined') }}

エスケープ フィルター: このフィルターは、生の HTML 出力をレンダリングするために使用されます。文字列 s 内の文字 & < > ' ” を HTML セーフ シーケンスに変換します。このような文字が含まれる可能性のあるテキストを HTML に表示する必要がある場合は、このオプションを使用します。 .marks the returnトークン化された文字列としての値。

{
   
   { "<title>Todo Application</title>" | escape }}
<title>Todo Application</title>

型変換フィルター: これらのフィルターには、あるデータ型から別のデータ型に変換するための int フィルターと float フィルターが含まれます。

{
   
   { 3.142 | int }}
3
{
   
   { 20 | float }}
20.0

join filter : join(*value*, *d=u''* , *attribute=None*) は、シーケンス内の文字列を連結した文字列を返します。要素間の区切り文字はデフォルトで空の文字列になります。オプションのパラメータで定義できます。

{
   
   { [1, 2, 3] | join('|') }}
 -> 1|2|3
{
   
   { [1, 2, 3] | join }}
 -> 123

オブジェクトの特定のプロパティを連結することもできます。

{
   
   { users|join(', ', attribute='username') }}

長さフィルター: このフィルターはシーケンスまたはコレクションの長さを返します。これは Python の len() 関数と同じように機能します。

Todo count: {
   
   { todos | length }}
Todo count: 4

if 条件

Jinja での if ステートメントの使用法は、Python での使用法と似ています。{% %} 制御ブロック内で使用されます。例を見てみましょう:

{% if todos %}
<ul>
{% for todo in todos %}
 <li>{
   
   { todo.name|e }}</li>
{% endfor %}
</ul>
{% endif %}

ループ条件

Jinja では変数を反復処理することもできます。これはリストまたは一般的な関数です。たとえば、次のようになります。

{% for todo in todos %}
 <li>{
   
   { todo.name|e }}</li>
{% endfor %}

for ループ内の特別な変数 (loop.index など) にアクセスできます。これにより、現在の反復のインデックスが得られます。

大きい

マクロは、通常のプログラミング言語の関数に相当します。これらは、一般的なイディオムを再利用可能な関数に組み込むのに役立ち、同じことを繰り返さないようにします (「DRY」原則)。

{% macro input(name, value='', type='text', size=20) %}
 <div class="form">
 <input type="{
   
   { type }}" name="{
   
   { name }}"
            value="{
   
   { value|escape }}" size="{
   
   { size }}">
 </div>
{% endmacro %}

ここで、フォームに入力をすばやく作成するには、次のマクロを呼び出します。

{
   
   { input('item') }}

レンダリングが完了すると、次の値が返されます。

 <div class="form">
 <input type="text" name="item" value="" size="20" />
 </div>

FastAPI の神社

FastAPI は、実際には API とマイクロサービスを構築するために設計されています。Jinja を使用して HTML を提供する Web アプリケーションを構築するために使用できますが、実際にはそれが最適化されたものではありません。

サーバー上で大量の HTML をレンダリングする大規模な Web サイトを構築したい場合は、Django の方が良い選択かもしれません。

ただし、React、Angular、Vue などのフロントエンド フレームワークを使用して最新の Web サイトを構築している場合、FastAPI からデータを取得することは優れたオプションです。

5. FastAPI、vue 開発 Web サイト

5.1 FastAPI と Vue3 間の通信

https://zhuanlan.zhihu.com/p/632387477

Vue3とFastAPIをベースにしたデータベースの運用: https://zhuanlan.zhihu.com/p/632393099

5.2 フロントエンドとして Vue.js を接続し、バックエンドとして Fastapi を接続する

https://www.cnblogs.com/hahaha111122222/p/15904405.html

ディレクトリ構造

§── main.py
└── テンプレート
    └── home.html

バックエンドfastapi

pip インストール fastapi[all]
pip インストール jinja2

main.py

  • / でフロントエンドを提供し、そのパスで home.html をレンダリングします。
  • テンプレート フォルダーを使用して HTML を保持し、Jinja に渡します。
  • また、フロントエンドから /add にリクエストを送信します。
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel

templates = Jinja2Templates(directory="templates") 

app = FastAPI()


class TextArea(BaseModel):
    content: str

@app.post("/add")
async def post_textarea(data: TextArea):
    print(data.dict())
    return {**data.dict()}

@app.get("/")
async def serve_home(request: Request):
    return templates.TemplateResponse("home.html", {"request": request})

フロントエンド - home.html

  • テキストエリアとボタンを備えたダミー アプリケーションを作成してみましょう。
  • バックエンドにリクエストを送信するために Axios を使用しています。
  • これらは同じポート上で実行されているため、/add を Axios に直接渡すことができます。
<html>
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<body>
    <div id="app">
        <textarea name="" id="content" cols="30" rows="10" v-model="content"></textarea>
        <button @click="addText" id="add-textarea">click me</button>
    </div>

    <script>
        new Vue({
            el: "#app",
            data: {
                title: '',
                content: ''
            },
            methods: {
                addText() {
                    return axios.post("/add", {
                        content: this.content
                    }, {
                        headers: {
                            'Content-type': 'application/json',
                        }
                    }).then((response) => {
                        console.log("content: " + this.content);
                    });
                }
            }
        });
    </script>
</body>

</html>

実行、アクセステスト

コマンド: uvicorn main:app --reload

最終的には、ひどいテキストエリアとボタンができてしまいます。しかし、それは物事をよりよく理解するのに役立ちます。

5.3 FastApi+Vue+LayUI は、シンプルなフロントエンドとバックエンドの分離デモを実装します

実際の使用では、通常、フロントエンド プロジェクトとバックエンド プロジェクトを分離することが推奨されます。次に、FastApi+Vue+LayUI を使用して、フロントエンドとバックエンドを分離したデモを作成します。

後部

バックエンドは FastApi を採用し、コード test.py

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import JSONResponse
from pathlib import Path
import uvicorn
import subprocess

app = FastAPI()
templates = Jinja2Templates(directory="templates")


@app.get('/info')
async def user_list():
    # vue的响应数据
    ret_list = [
        {'id': '1', 'value': 'one'},
        {'id': '2', 'value': 'two'},
        {'id': '3', 'value': 'three'},
    ]
    return JSONResponse(content=ret_list)


@app.get("/check")
async def home(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})


if __name__ == '__main__':
    '''
        http://127.0.0.1:5555/check
    '''
    # print(f'{Path(__file__).stem}:app')
    uvicorn.run(f'{Path(__file__).stem}:app', host="0.0.0.0", port=5555)
    pass

フロントエンド

フロントエンドは、Vue、LayUI、Axios JS および CSS の CDN リソースを直接インポートします。Vue インスタンスのマウント フェーズでは、axios を使用してバックエンド インターフェイスを呼び出してデータを取得し、LayUI のスタイルを使用してテーブル要素を美しくします。

コード

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <!-- 引入 layui.css -->
    <link rel="stylesheet" href="https://www.layuicdn.com/layui/css/layui.css"/>
    <!-- 引入 layui.js -->
    <script src="https://www.layuicdn.com/layui/layui.js" type="text/javascript" charset="utf-8"></script>
    <title>Home</title>
</head>
<body>
<div id="app">
    <table class="layui-table">
        <tr v-for="fo in info_list">
            <td> [[ fo.id ]] </td>
            <td> [[ fo.value ]] </td>
        </tr>

    </table>
</div>
<table id="test" class="layui-table"></table>

<script>
    const {createApp, ref} = Vue
    const vue_app = createApp({
        data() {
            return {
                info_list: [{id: 1, name: '默认值'}],
                // info: "hello vue..."
            }
        },
        mounted() {
            this.showinfo();
        },
        methods: {
            showinfo() {
                axios.get('/info').then(response => {
                    this.info_list = response.data;
                    // console.log(response);
                    console.log(`this.info_list ---> ${this.info_list.toString()}`);
                }, err => {
                    console.log(err);
                })
            }
        }
    });
    // vue_app.config.delimiters = ['[[', ']]'];
    vue_app.config.compilerOptions.delimiters = ['[[', ']]']
    vue_app.mount('#app');
</script>
</body>
</html>

vue と jinja2 は両方とも、デフォルトでフロントエンドで変数の値を表示するために「{ {content}}」を使用するため、競合が発生します。

vue が値を表示する方法を変更できます。つまり、「インターポレーター」を変更します。

vue 2 ウェイ:

<script>
    const vue = new Vue({         el:"#app",         区切り文字: ["[[", "]]"],         data:{             selects:['enu','cha'],             userData:[]         } </script>






vue 3 ウェイ:

Vue 3 では、動的データをテンプレートにレンダリングするためのデフォルトの差分表記は二重中括弧 ( { { }} ) です。ただし、補間のデフォルトの表記法を変更したい場合、Vue 3 には簡単な方法が用意されています。
Vue アプリケーション インスタンスを作成する前に、createApp 関数の config メソッドを使用してグローバル デルタ シンボルを構成します:
import { createApp } from 'vue';
const app = createApp({});
//app.config.delimiters = [ ' ${', '}'];

app.config.compilerOptions.delimiters = ['${', '}'];

app.mount('#app');
上記のコードでは、app.config.delimiters を使用して差分記号を ${ } に変更します。
変更後、テンプレート内の新しいデルタ記号を使用して動的データを表示できます:
<template>
  <div>
    <p>${ message }</p>
  </div>
</template>

<script>
export default {   data() {     return {       message: "Hello, world!"     };   } }; </script>上記の例では、${ } デルタ表記を使用してメッセージ データを表示しています。差分シンボルを変更した後は、新しい差分シンボルがテンプレート内の変数名と競合しないことを確認する必要があることに注意してください。同時に、差分符号の変更は現在のアプリケーション インスタンスのスコープ内でのみ有効であり、他のアプリケーション インスタンスには影響しません。








プロジェクトを実行する

FastApi バックエンド サーバーを起動し、/test/check インターフェイスにアクセスします。

axios を使用して get リクエストと post リクエストを送信する方法の詳細な説明

https://blog.csdn.net/grand_brol/article/details/108167088

Q&A

Q:/infoリクエスト インターフェイスに常にリダイレクトがあるのはなぜですかTemporary Redirect?

A: 理由は、FastApiインターフェイスを定義したときに形式uri標準化されていなかったためです。ブラウザに必要のないリクエストを、uri定義たビュー関数にリダイレクトします//uri/FastApi//

5.4 Python fastapi+vue を使用して Web サイトを迅速に構築する

https://www.elprup.com/2020/09/19/fastapi_vue/

従来の Web サイトは、Express、nodejs ベースの koa、Python ベースの Django、Tornado などの Web フレームワークによって完全に実行されます。新しいタイプの Web サイトは、フロントエンド システムを Vue で開発し、API フレームワークを使用してバックエンド API リクエスト インターフェイスを開発するモードに進化しました。

5.5 FastAPI と Vue.js を使用した単一ページ アプリケーションの開発

元のアドレス: https://testdriven.io/blog/developing-a-single-page-app-with-fastapi-and-vuejs/

ソースアドレス: https://github.com/testdrivenio/fastapi-vue

翻訳 1: https://juejin.cn/post/7113790977848360967

翻訳 2: https://www.cnblogs.com/leimu/p/16992966.html

おすすめ

転載: blog.csdn.net/freeking101/article/details/132039250