Open9

full-stack-fastapi-template を解体して中の実装を見る

ikeponikepon

やること

  • 何も実装してないFastAPIを用意
  • full-stack-fastapi-template のリポジトリを参考に、必要な実装を追加していく
  • 調べたことをメモしていく

FastAPI...というより Python を普段書かないので、あくまでも個人的メモです 📝

ikeponikepon
ikeponikepon
ikeponikepon

⭐️ とりあえず、この prestart.sh を呼べばマイグレーションは実行してくれそう

ikeponikepon
  • 続き

    • マイグレーションファイルは alembic revision --autogenerate -m "xxx" コマンドで作成する
      • マイグレーションファイルは alembic/version 内にある
      • コマンド実行すると自動で user と item テーブルの記述ができる
        • -> 事前に app/models/pyclass User(UserBase, table=True): , class Item(ItemBase, table=True): でテーブルを定義しており、 autogenerate オプションによって、それと実際の DB の状態を見て差分をマイグレーションとして作成してくれるっぽい
      • ファイル名からは作成順がわからないので、ファイル内に入らないといけない
        • -> alembic.inifile_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 周りは一通りテーブル作成まではできた感じ

ikeponikepon

アプリ側を見ていく

  • 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"  # 開発・ステージングでは公開
            
      • title
        • /docs とかに表示されるタイトル
    • settings.all_cors_origins
      • CORS の設定
ikeponikepon
  • 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)」文字列表現を返し、オブジェクトを再作成するのに役立つ情報を文字列で提供する
          • Python 3.7で導入された
        • import logging
          • Pythonの標準ライブラリ
          • DEBUG, INFO, WARNING, ERROR, CRITICALなどのログレベルがあり、出力したい情報の重要度に応じて使い分ける
          • ログの出力先はコンソール、ファイル、ネットワークなど、さまざまな場所に出力できる
          • 重要なログだけを出力するようにレベルを指定してフィルタリングでき、ノイズを減らすことができる

これで起動するようになった
/sign_up で DB 登録もできるようになってるのを確認

ikeponikepon

ユーザー作成できたのでログインを見ていく

  • api/routes/login.py
    • from fastapi.responses import HTMLResponse
      • レスポンスを HTML で返す用
      • recover_password_html_content で使ってて、メールの html を返してる?
    • from fastapi.security import OAuth2PasswordRequestForm
      • OAuth2のパスワードフローで使用する認証フォームを提供するためのクラス
      • FastAPIのエンドポイントに対してユーザーがユーザー名とパスワードを送信し、トークンを取得する仕組みを簡単に実装できる

試したらログインできたので大丈夫そう

ikeponikepon
  • 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: リトライ後にログを出力する設定