今回はFastAPIとSQLModelを用いて簡単なヒーローのデータベースとAPIを作成するチュートリアルの和訳です。
引用元は上記。
機械翻訳をガッツリ使って、一部意訳しているので間違いがあるかもしれません。
Simple Hero API with FastAPI¶
まずは、FastAPIを使って、シンプルなヒーローWeb APIを作ってみましょう。✨
Install FastAPI¶
まず最初にFastAPIをインストールします。
FastAPIはweb APIを作るためのフレームワークです。
しかし、WebAPIの実行にはサーバーと呼ばれる別のアプリも必要です。FastAPIではUvicorn を使います。Uvicornを標準的な依存関係とともにインストールしていきます。
仮想環境が有効になっていることを確認してください。
確認できたら、FastAPIとUvicornをインストールします。
$ python -m pip install fastapi "uvicorn[standard]"
SQLModel Code - Models, Engine¶
それでは、SQLModelのコードを見ていきましょう。
ここでは、ヒーローだけの 最もシンプルなバージョン から始めます(まだチームはありません)。
他のSQLModelのチュートリアルで見てきたものとほとんど同じコードです。
from typing import Optional
# One line of FastAPI imports here later 👈
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
# Code below omitted 👇
ここで以前使っていたコードからの変更点は1つだけで、connect_args
のcheck_same_thread
です。これは、SQLAlchemyがデータベースとの通信を担当する低レベルライブラリに渡す設定です。check_same_thread
はデフォルトでは True
に設定されていますが、これは単純なケースで誤用することを防ぐためです。しかし、ここでは複数のリクエストで同じsessionを共有しないようにします。これが、この設定が必要とする問題を防ぐための、実際の最も安全な方法です。
また、FastAPIでは各リクエストが複数のインタラクティブなスレッドで処理される可能性があるため、これを無効にする必要があります。
情報
詳細はFastAPI docs for async
and await
を参照してください。
重要なのは、複数のリクエストで同じ セッション を共有しないようにすることで、安全にコードを扱えます。
FastAPI App¶
次のステップでは、FastAPIアプリを作成します。
まず、fastapi
からFastAPI
クラスをインポートします。
そして、そのFastAPI
クラスのインスタンスであるapp
オブジェクトを作成します。
from typing import Optional
from fastapi import FastAPI
from sqlmodel import Field, Session, SQLModel, create_engine, select
# SQLModel code here omitted 👈
app = FastAPI()
# Code below omitted 👇
Create Database and Tables on startup
¶
アプリの実行が始まったら、関数create_tables
が呼ばれるようにしたいと思います。データベースとテーブルを作成するためです。
これは起動時に一度だけ呼ばれるべきもので、毎回のリクエストの前に呼ばれるものではありませんので、startup
イベントを処理する関数の中に入れています。
# 上のコードは省略しています👆。
app = FastAPI()
@app.on_event("startup") def on_startup():
create_db_and_tables()
# 以下のコードは省略👇
Create Heroes Path Operation¶
infomation
Path Operationとは何か(特定のHTTP Operationを持つエンドポイント)、そしてFastAPIでどのように動作するかについて再確認する必要がある場合は、FastAPI First Steps docsをご覧ください。
新しいヒーローを作成するための Path Operation コードを作成してみましょう。
これは、ユーザーが /heroes/
path に POST
operation でリクエストを送ったときに呼び出されます。
# Code above omitted 👆
app = FastAPI()
@app.on_event("startup")
def on_startup():
create_db_and_tables()
@app.post("/heroes/")
def create_hero(hero: Hero):
with Session(engine) as session:
session.add(hero)
session.commit()
session.refresh(hero)
return hero
# Code below omitted 👇
情報
これらのコンセプトについて再確認する必要がある場合は、FastAPIのドキュメントをチェックしてください。
- ファーストステップ
- パスパラメータ - データ検証とデータ変換](https://fastapi.tiangolo.com/tutorial/path-params/)
- リクエストボディ
The SQLModel Advantage¶
ここで、SQLModelクラスのモデルが、SQLAlchemyモデルとPydanticモデルの両方を同時に持っていることが光ります。✨
ここでは、同じクラスモデルを使って、APIが受け取るリクエストボディ**を定義しています。
FastAPI**はPydanticをベースにしているので、同じモデル(Pydanticの部分)を使って自動的にデータを検証し、JSONリクエストから実際のHero
クラスのインスタンスであるオブジェクトに変換します。
そして、この同じSQLModelオブジェクトはPydanticモデルのインスタンスであるだけでなく、SQLAlchemyモデルのインスタンスでもあるので、データベースに行を作成するためにsessionで直接使用することができます。
直感的にPythonの標準的なtypeアノテーションを使用することができ、データベースモデルとAPIデータモデルのための多くのコードを重複させる必要はありません。🎉
ヒント
このコードは後でさらに改良する予定ですが、今のところ、SQLModelクラスがSQLAlchemyモデルとPydanticモデルの両方を同時に持つことの威力をすでに示しています。
heroを読む Path Operation¶
では、すべてのヒーローを読むために、別のパス操作を追加してみましょう。
# Code above omitted 👆
app = FastAPI()
@app.on_event("startup")
def on_startup():
create_db_and_tables()
@app.post("/heroes/")
def create_hero(hero: Hero):
with Session(engine) as session:
session.add(hero)
session.commit()
session.refresh(hero)
return hero
@app.get("/heroes/")
def read_heroes():
with Session(engine) as session:
heroes = session.exec(select(Hero)).all()
return heroes
クライアントが パス /heroes/
に GET
HTTP 操作 でリクエストを送ると、データベースからヒーローを取得して返すこの関数を実行します。
One Session per Request¶
操作のグループごとにSQLModelのsessionを使用し、他の関係のない操作が必要な場合は別のセッションを使用するべきだということを覚えていますか?
ここではそれがより明確になります。
通常、ほとんどの場合、リクエストごとに1つのセッションを使用するべきです。
いくつかの孤立したケースでは、内部に新しいセッションを持ちたいと思うかもしれません、つまり、1つのリクエストに対して1つ以上のセッションを持つことになります。
しかし、異なるリクエスト間で同じセッションを共有することは ありません 。
この単純な例では、パス操作関数で新しいセッションを手動で作成するだけです。
今後の例では、FastAPI Dependencyを使ってsessionを取得し、他の依存関係と共有したり、テスト中に置き換えることができるようにします。🤓
Run the FastAPI Application¶
これでFastAPIアプリケーションを実行する準備が整いました。
すべてのコードを main.py
というファイルに入れてください。
そして、それを Uvicorn で実行します。
uvicorn main:app
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.
infomation
コマンド uvicorn main:app
は以下を参照しています。
-
- main
: ファイルmain.py
(Python の「モジュール」). -
- app
:main.py
の中でapp = FastAPI()
という行で作成されたオブジェクトです。
Uvicorn --reload
¶
開発中に(開発中に限り)、Uvicorn に --reload
というオプションを追加することもできます。これはコードに変更を加えるたびにサーバーを再起動するもので、こうすることでより早く開発を進めることができるようになります。🤓
uvicorn main:app --reload
INFO: Will watch for changes in these directories: ['/home/user/code/sqlmodel-tutorial']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.
必要以上にリソースを消費したり、エラーが発生しやすくなるため、運用時には絶対に--reload
を使用しないようにしてください。
Check the API docs UI¶
これで、ブラウザでそのURLに行くことができます http://127.0.0.1:8000
。ルートパス /
に対して path operation を作成していないので、その URL だけでは "Not Found" エラーが表示されるだけです... その "Not Found" エラーは、FastAPI アプリケーションによって生成されます。
しかし、パス /docs
にある 自動的に生成されたインタラクティブ API ドキュメント に行くことができます。http://127.0.0.1:8000/docs. ✨
この 自動 API ドキュメント UI は、上で定義した path をその operations と共に持っており、path 操作 が受け取るデータの形状を既に知っていることがわかります。
Play with the API¶
実際にボタンをクリックすることができます 試しに、Create Hero _path operation_でいくつかのヒーローを作成するリクエストを送ってみましょう。
そして、Read Heroes _path operation_でそれらを返してもらうことができます。
Check the Database¶
これで、ターミナルに戻ってCtrl+Cを押すことで、そのUvicornサーバーを終了させることができます。
そして、DB Browser for SQLiteを開いてデータベースをチェックし、データを調べて、確かにヒーローを保存したことを確認してください。🎉
Recap¶
これはもう、ヒーローのデータベースと対話するためのFastAPI web API アプリケーションです。🎉
改良や拡張できる点がいくつかあります。例えば、私たちはデータベースに各新しいヒーローのIDを決定してもらいたいのですが、ユーザーがそれを送信することを許可したくありません。
これらの改善はすべて次の章で行います。🚀