Open9
full-stack-fastapi-template を解体して中の実装を見る
やること
- 何も実装してないFastAPIを用意
- full-stack-fastapi-template のリポジトリを参考に、必要な実装を追加していく
- 調べたことをメモしていく
FastAPI...というより Python を普段書かないので、あくまでも個人的メモです 📝
- とりあえず必要そうなライブラリはざっとインストールしておく
- gitignore もコピーしておく
- mypy の設定追加
-
https://github.com/python/mypy
- Mypy is a static type checker for Python.
- pyproject.toml に設定追加
- 実行スクリプト作成
- https://github.com/fastapi/full-stack-fastapi-template/blob/master/backend/scripts/lint.sh
- GitHub Actions で使ってるので、そっちの設定も追加
-
https://github.com/python/mypy
- ruff の設定
-
https://github.com/astral-sh/ruff
- An extremely fast Python linter and code formatter, written in Rust.
- mypy の設定時にあったので合わせて設定
-
https://github.com/astral-sh/ruff
- alembic でマイグレーションを設定
- https://github.com/sqlalchemy/alembic
-
alembic init alembic
を実行 - alembic.ini
- 参考 PR は
script_location = app/alembic
になってるけど、これは変えずにscript_location = alembic
のままにする -
sqlalchemy.url = driver://user:pass@localhost/dbname
はalembic/env.py
で設定しているように見えるので削除 - それ以外はそのままでよさそう
- 参考 PR は
- alembic/env.py
-
from app.models import SQLModel # noqa
で app/models.py を見ると特に SQLModel が定義されているように見えない...というより sqlmodel からそのまま持ってきただけかな?- 中身はあとで見るとして、一旦 app.models.py をコピペで作成
-
from app.core.config import settings # noqa
で settings をimport してるので、中を見る- 中身は後で見るとして、一旦 config.py をコピペで作成
- 中で
env_file="../.env"
の記述があるので、一番上のディレクトリ内で.env
を作成して中身をコピペする- DB に関係するのは主に
POSTGRES_XXX
のところだと思う
- DB に関係するのは主に
- url は get_url で設定
- context.configure
- マイグレーションに必要な情報を設定
- 各設定は見るとして、差分を見る
- compare_type=True: 型が変わった時に変更する設定だと思う
- デフォルト True になってるっぽいので追記は不要そう
- dialect_opts={"paramstyle": "named"},
- SQLAlchemyが生成するSQL文のパラメータ埋め込み方法を named に固定してる
- ex:
-
sql = "SELECT * FROM users WHERE username = :username" params = {"username": "example_user"}
-
- ex:
- SQLAlchemyが生成するSQL文のパラメータ埋め込み方法を named に固定してる
- script.py.mako
- マイグレーションファイルを作るときのテンプレート
- 差分は型くらいだと思うんで、一旦そのままにする
-
- 続き
- app/core/db.py で DB の設定
- 起動時に管理者(SUPER_USER) がいないと作ってるっぽい
- 作ってるユーザーの中身はこの辺に記述がある
- これを initial_data.py で呼んでる
- さらにこれを prestart.sh で呼んでる
- https://github.com/fastapi/full-stack-fastapi-template/blob/master/backend/scripts/prestart.sh
- この中でマイグレーションも実行してるっぽい
alembic upgrade head
-
python app/backend_pre_start.py
の記述があるので、これで db を起動してるっぽい、中身を見る - prestart.sh は docker-compose内で呼んでる
- app/core/db.py で DB の設定
⭐️ とりあえず、この prestart.sh を呼べばマイグレーションは実行してくれそう
-
続き
- マイグレーションファイルは
alembic revision --autogenerate -m "xxx"
コマンドで作成する- マイグレーションファイルは
alembic/version
内にある - コマンド実行すると自動で user と item テーブルの記述ができる
- -> 事前に
app/models/py
にclass User(UserBase, table=True):
,class Item(ItemBase, table=True):
でテーブルを定義しており、autogenerate
オプションによって、それと実際の DB の状態を見て差分をマイグレーションとして作成してくれるっぽい
- -> 事前に
- ファイル名からは作成順がわからないので、ファイル内に入らないといけない
- ->
alembic.ini
のfile_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
のコメントアウトを外せば日付が入るので、こっちの方が良さそう
- ->
- マイグレーションファイルは
- alembic コマンド
-
alembic upgrade head # マイグレーションファイルの実行 alembic downgrade base # 最初の状態に戻す alembic downgrade [revision] # 指定した revision の状態にする、revision はマイグレーションファイル内に記述がある alembic current # 現在の状態表示 alembic history # 実行履歴
- マイグレーションファイルのテンプレートは
alembic/script.py.mako
にある- -> SQLModel を書いていれば自動で反映される
- -> この時点でマイグレーションファイルを作成して、そのまま実行すると
NameError: name 'sqlmodel' is not defined
って怒られる- -> テンプレートに
import sqlmodel.sql.sqltypes
を追加しておく必要がある、これによって今後マイグレーションファイルを作成したときに同じエラーに合うことはなくなる
- -> テンプレートに
-
- マイグレーションファイルは
-
FastAPI ...というより SQLModel かな?テーブル名は単数形が通常っぽい
- -> 複数形もできるが、
__tablename__
を記述しないといけないっぽい
- -> 複数形もできるが、
-
マイグレーションのバージョンは
alembic_version
テーブルで管理してる-
version_num
というカラムだけ持っていて、その中に revision が入ってる
-
これで DB 周りは一通りテーブル作成まではできた感じ
アプリ側を見ていく
- main.py
- sentryは一旦無視
- FastAPI の引数
- generate_unique_id_function
- SwaggerのoperationIdを生成する関数
- openapi_url
- OpenAPIの公開用のエンドポイント設定、社内利用の場合は非公開で問題ないので
openapi_url=None
で設定 - -> ローカルでも /docs で表示されなくなる
- settings.ENVIRONMENT で環境が取れるので、それで調整するのが良さそう
-
if settings.ENVIRONMENT == "production": openapi_url = None # 本番環境では非公開 else: openapi_url = "/openapi.json" # 開発・ステージングでは公開
-
- OpenAPIの公開用のエンドポイント設定、社内利用の場合は非公開で問題ないので
- title
- /docs とかに表示されるタイトル
- generate_unique_id_function
-
settings.all_cors_origins
- CORS の設定
- api (route の設定)
- api/main.py
- ここで routes/ 以下のrouteの設定を読み込んで、まとめて app/main.py に読み込ませてる
- とりあえず signup ができれば DB 接続、ログインとかに流れができるので、api/routes/users.py をコピーしてみる
- api/routes/users.py
- from app import crud
- create_user, update_user, get_user_by_email, authenticate, create_item メソッドがある
- DB アクセスしてCRUDするメソッドを置くところかな?
- -> テーブルが増えるとファイルも大きくなりそうだけど
- api/deps から CurrentUser とか読み込んでる
- -> deps に DB 接続とかログインユーザーとかのメソッドが入ってそうなので見ていく
- api/deps.py
- get_db: DB 接続, get_current_user: トークンからユーザー取得, get_current_active_superuser: CurrentUserを取得した上で、それが管理者(super_user)かを確認してる
-
from collections.abc import Generator
- Python の標準ライブラリ
- ジェネレーターオブジェクトのインターフェースを定義
- ジェネレーターは、yieldを使って値を逐次返すイテレーターの一種で、複数の値を生成しつつ、状態を保持できる
- from fastapi.security import OAuth2PasswordBearer
- FastAPIでOAuth2認証のためのトークン取得エンドポイントを設定するためのクラスOAuth2PasswordBearerをインポート
- OAuth2のパスワードフローでトークンを取得し、保護されたAPIへのアクセスに使用する
- from sqlmodel import Session
- データベースへの接続やトランザクションを管理するためのクラス
- このクラスを使用して、データベースとの読み書き操作(クエリの実行、データの挿入・更新・削除など)を行う
- core/security.py
- api/deps.py の中で使われてる
- 中を見ると、create_access_token, get_password_hash, verify_password ってメソッドがあるので、ログインとか登録の時に関わるトークン、パスワードを管理してるっぽい
- from app.utils import generate_new_account_email, send_email
- utils はメール周りとトークンの発行とかをやってる
- from dataclasses import dataclass
- Pythonの標準ライブラリ
- dataclassデコレータをインポート
- データを保持するためのシンプルなクラスを簡単に定義できる
- クラスに@dataclassデコレータを付けると、コンストラクタ(__init__メソッド)、repr、__eq__などのメソッドが自動的に生成してくれる
-
repr
- そのオブジェクトの「公式な(official)」文字列表現を返し、オブジェクトを再作成するのに役立つ情報を文字列で提供する
-
repr
- クラスに@dataclassデコレータを付けると、コンストラクタ(__init__メソッド)、repr、__eq__などのメソッドが自動的に生成してくれる
- Python 3.7で導入された
- import logging
- Pythonの標準ライブラリ
- DEBUG, INFO, WARNING, ERROR, CRITICALなどのログレベルがあり、出力したい情報の重要度に応じて使い分ける
- ログの出力先はコンソール、ファイル、ネットワークなど、さまざまな場所に出力できる
- 重要なログだけを出力するようにレベルを指定してフィルタリングでき、ノイズを減らすことができる
- from app import crud
- api/main.py
これで起動するようになった
/sign_up で DB 登録もできるようになってるのを確認
ユーザー作成できたのでログインを見ていく
- api/routes/login.py
- from fastapi.responses import HTMLResponse
- レスポンスを HTML で返す用
- recover_password_html_content で使ってて、メールの html を返してる?
- from fastapi.security import OAuth2PasswordRequestForm
- OAuth2のパスワードフローで使用する認証フォームを提供するためのクラス
- FastAPIのエンドポイントに対してユーザーがユーザー名とパスワードを送信し、トークンを取得する仕組みを簡単に実装できる
- from fastapi.responses import HTMLResponse
試したらログインできたので大丈夫そう
- tests
- test_pre_start.py
- データベースの起動を待機し、サービスの起動を順調に進めるためのテスト前の初期化スクリプト
- データベースが完全に応答可能になるまで接続をリトライするロジックが含まれており、接続が成功すると初期化が完了した旨のメッセージがログに出力される
- from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
- tenacityは、関数や操作が失敗した際に自動的にリトライを行うためのライブラリで、リトライのタイミングや回数、ログの設定などを柔軟に行うことができる
- retry: デコレータとして使用され、リトライ処理の条件やタイミングを定義
- その関数が失敗した場合に指定した条件に基づいてリトライを行う
- stop_after_attempt: リトライの終了条件を指定
- stop_after_attempt(n)のように使用し、n回リトライした後に停止することを指定
- wait_fixed: リトライ間の待機時間を指定
- wait_fixed(n)のように使用し、n秒待機してからリトライを行う設定ができる
- before_log: リトライ前にログを出力する設定
- after_log: リトライ後にログを出力する設定
- データベースの起動を待機し、サービスの起動を順調に進めるためのテスト前の初期化スクリプト
- test_pre_start.py