これは、なにをしたくて書いたもの?
とあるPythonを扱うチュートリアルを見ているのですが、サンプルとしてFastAPIが使われるようなので先に独立して触っておこうかなと
思いまして。
なかなか人気もありそうですし。
FastAPI
FastAPIのWebサイトはこちら。
登場は2018年と新しいフレームワークのようですが、かなり人気のようですね。調べてみたところ、GitHubリポジトリーのStarの伸びが
凄まじいです。
Webサイトのトップページによると、以下が特徴なようです。
- 高速 … Node.jsやGoと同等のパフォーマンス(StarletteとPydanticの恩恵が大きい)で、最も高速なPythonフレームワークのひとつ
- 高速なコーディングとバグの削減
- 優れたエディターのサポート
- 使いやすく、学びやすい
- 少ないコードでの実装が可能
- プロダクションレディなコードをドキュメントからインタラクティブに、自動で得ることができる
- OpenAPIの基づいている
これらのより詳しい説明はこちらですね。
チュートリアルやドキュメント、デプロイ向けのガイドなどはこちら。
APIリファレンスはこちら。
Reference - Code API - FastAPI
Python 3.8以上であれば動作するようです。
ひとまず、Exampleを試してみましょう。
環境
今回の環境はこちら。
$ python3 --version Python 3.10.12 $ pip3 --version pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)
FastAPIのExampleを試す
まずはインストールから。
FastAPI自身と
$ pip3 install fastapi
ASGIサーバーというものをインストールするようです。ASGIサーバーとしてはUvicornとHypercornが挙げられています。
今回はドキュメントに従って、Uvicornをインストールしておきます。
$ pip3 install uvicorn[standard]
インストールされたモジュールはこちら。
$ pip3 list Package Version ----------------- ------------ annotated-types 0.6.0 anyio 4.2.0 click 8.1.7 exceptiongroup 1.2.0 fastapi 0.109.2 h11 0.14.0 httptools 0.6.1 idna 3.6 pip 22.0.2 pydantic 2.6.0 pydantic_core 2.16.1 python-dotenv 1.0.1 PyYAML 6.0.1 setuptools 59.6.0 sniffio 1.3.0 starlette 0.36.3 typing_extensions 4.9.0 uvicorn 0.27.0.post1 uvloop 0.19.0 watchfiles 0.21.0 websockets 12.0
では、Exampleに従ってソースコードを作成してみます。
main.py
from typing import Union 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: Union[str, None] = None): return {"item_id": item_id, "q": q}
起動してみます。
$ uvicorn main:app --reload
ポート8000、変更を監視してリロードするモードで起動したようです。
INFO: Will watch for changes in these directories: ['/path/to'] INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) INFO: Started reloader process [12543] using WatchFiles INFO: Started server process [12545] INFO: Waiting for application startup. INFO: Application startup complete.
アクセスして確認してみます。
$ curl localhost:8000 {"Hello":"World"} $ curl localhost:8000/items/5 {"item_id":5,"q":null} $ curl localhost:8000/items/5?q=test {"item_id":5,"q":"test"} $ curl localhost:8000/items/5?r=test {"item_id":5,"q":null}
/
へのアクセスはいいとして、/items/{item_id}
に割り当てている関数定義を確認してみます。
@app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q}
{item_id}
がパスパラメーター、q: Union[str, None]
がQueryStringに対応しているみたいですね。
この状態でhttp://localhost:8000/docs
にアクセスしてみます。
すでにOpenAPI定義があります、と…。
ここでアクセスするURLをhttp://localhost:8000/redoc
にすると、RedocでのOpenAPI定義を確認できます。
便利ですね。
次は、ドキュメントに習ってソースコードを修正してみます。
main.py
from typing import Union from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str price: float is_offer: Union[bool, None] = None @app.get("/") def read_root(): return {"Hello": "World"} @app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} @app.put("/items/{item_id}") def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id}
PUTメソッドを追加しました。
ソースコードを修正して保存すると、Uvicornを--reload
オプション付きで起動しているので、リロードが行われます。
その時のログはこんな感じです。
WARNING: WatchFiles detected changes in 'main.py'. Reloading... INFO: Shutting down INFO: Waiting for application shutdown. INFO: Application shutdown complete. INFO: Finished server process [12862] INFO: Started server process [12865] INFO: Waiting for application startup. INFO: Application startup complete.
確認。
$ curl -XPUT -H 'Content-Type: application/json' localhost:8000/items/3 -d '{"name": "sample", "price": 500}' {"item_name":"sample","item_id":3}
OKそうですね。
確認結果は省略しますが、OpenAPI定義やRedocの方にも反映されます。
なにを書いたのか?
ここまでの内容の要約が、こちらに書かれています。
まず、Python 3.8以降で使える以下のような型宣言がポイントになっています。
@app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None):
これでエディターのサポート、データのバリデーション(JSON検証)、入力データからPythonオブジェクトへの変換、
Pythonオブジェクトから出力データへの変換、OpenAPI定義(Redoc含む)という一連の結果が得られます。
エンドポイントを見てみます。
こちらはHTTP GETメソッドに対応していて、{item_id
}はパスパラメーターです。
@app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None):
q
はq
という名前のQueryStringパラメーターに対応していて、= None
があるためオプション扱いです。
HTTP PUTメソッドに対応しているこちらは、HTTPボディをItem
クラスのインスタンスとして読み込みます。メディアタイプは
application/json
になっているようです。
@app.put("/items/{item_id}") def update_item(item_id: int, item: Item):
HTTPボディで扱うクラスは以下の定義ですが、name
およびprice
が必須項目、is_offer
がオプションという扱いです。
class Item(BaseModel): name: str price: float is_offer: Union[bool, None] = None
この後はチュートリアルに進むのがよいみたいです。
Tutorial - User Guide - FastAPI
目次を見ると、だいたいこれを眺めていけばやりたいことはできそうな感じですね。
テストを書く
せっかくなので、テストまで書いてみましょう。
テストはチュートリアルのこちらを見ればよさそうです。
テスティングフレームワークとしてはpytest、HTTPリクエストにはhttpxを使います。
インストール。
$ pip3 install pytest httpx
結果はこうなりました。
$ pip3 list Package Version ----------------- ------------ annotated-types 0.6.0 anyio 4.2.0 certifi 2024.2.2 click 8.1.7 exceptiongroup 1.2.0 fastapi 0.109.2 h11 0.14.0 httpcore 1.0.2 httptools 0.6.1 httpx 0.26.0 idna 3.6 iniconfig 2.0.0 packaging 23.2 pip 22.0.2 pluggy 1.4.0 pydantic 2.6.0 pydantic_core 2.16.1 pytest 8.0.0 python-dotenv 1.0.1 PyYAML 6.0.1 setuptools 59.6.0 sniffio 1.3.0 starlette 0.36.3 tomli 2.0.1 typing_extensions 4.9.0 uvicorn 0.27.0.post1 uvloop 0.19.0 watchfiles 0.21.0 websockets 12.0
__init__.py
の作成。
$ touch __init__.py
作成したテストコードはこちら。
test_main.py
from fastapi.testclient import TestClient from main import app client = TestClient(app) def test_read_main(): response = client.get("/") assert response.status_code == 200 assert response.json() == {"Hello": "World"} def test_read_item(): response = client.get("/items/10") assert response.status_code == 200 assert response.json() == {"item_id": 10, "q": None} def test_update_item(): response = client.put( "/items/5", json={"name": "test", "price": 300} ) assert response.status_code == 200 assert response.json() == {"item_name": "test", "item_id": 5}
先ほど起動したUvicornは、実行しておく必要はありません。
テストを実行。
$ pytest ================================================================================== test session starts =================================================================================== platform linux -- Python 3.10.12, pytest-8.0.0, pluggy-1.4.0 rootdir: /path/to plugins: anyio-4.2.0 collected 3 items test_main.py ... [100%] =================================================================================== 3 passed in 0.69s ====================================================================================
こんな感じでしょうか。
おわりに
PythonのWeb APIフレームワーク、FastAPIを試してみました。
謳い文句どおり簡単に使えそうですし、ドキュメントも揃っている感じで良さそうですね。
あまりPythonでWeb APIを書くことはないのですが、それでもFlaskを使ったりしていたのでこれからはFastAPIでもいいかなと
思いました。