2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FastAPI×PostgreSQLをマイグレーションと接続してみる

Posted at

FastAPIを使ってPostgreSQLへ接続するのは、初めてですので下記のサイトを参考にさせていただきました。
FastAPIとPostgreSQLの結合:モデル定義とマイグレーション入門

Backend側(DastAPI)のディレクトリ構成

image.png


1.モジュールのインストール

pydantic-settingsのインストール

pydantic-settingsは、Pydanticライブラリの拡張機能で、環境変数や.envファイルなどの設定を型安全に読み込み、管理するためのツールです。これにより、設定値のバリデーション(検証)や型変換が自動的に行われ、安全で保守しやすい設定管理を実現します。通常はosモジュールで行うような環境変数管理を、クラスベースで簡潔かつ型チェックも同時に行うことができます。

このモジュールを下記のコマンド使ってインストールしましょう。

pipenv install pydantic-settings

psycopg2-binaryのインストール

psycopg2-binaryは、PythonからPostgreSQLデータベースに接続するためのライブラリ psycopg2 の、コンパイル済みのバイナリパッケージです。

下記のコマンドを使ってインストールしましょう。

pipenv install psycopg2-binary

SQLAlchemyのインストール

SQLAlchemyとは、Pythonでデータベースを操作するためのライブラリです。

下記のコマンドを使ってインストールしましょう。

pipenv install SQLAlchemy

Pydanticのインストール

Pydantic公式サイト

Pydanticのインストール

下記のコマンド使ってインストールします。

pipenv install pydantic
pipenv install pydantic-settings

2.appフォルダを新規作成

backendフォルダの直下にappフォルダを作成しましょう。
つぎに、__init__.pyファイルを作成します。このファイルは、「Pythonにこのディレクトリをパッケージとして認識させるために必要なファイル」です。
__init__.pyファイルの中身は、空のファイルで大丈夫です。

image.png

3.Modelの作成とORMを設定

ここでは、FastAPIとPiostgreSQLを接続する他のデータ情報を設定します。
前述で作成したappフォルダ配下にdbフォルダを作成します。
ここで作成したBaseクラスは、他のモデルクラスが継承する基底クラスです。
そのため、このクラスを継承することで、下記の利点が得られます。
1.作成するモデルクラスはSQLAlchemyの機能にアクセスできる。
2.データベースとのインタラクションが可能となる。
3.__tablename__メソッドを利用して、継承したクラスのインスタンスがデータベースにマッピングされる際のテーブル名を自動生成できる。

3-1.base_class.pyの作成

base_class.py
from typing import Any
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import as_declarative

@as_declarative()
class Base:
    id: Any
    __name__: str

    # Generate __tablename__ automatically
    @declared_attr
    def __tablename__(cls) -> str:
        return cls.__name__.lower()

4.ユーザー(User)モデルの作成

つぎに、appフォルダ配下にmodelsフォルダを作成します。
modelsフォルダの配下に前掲の基底クラス(base_class.pyを継承した)user.pyを作成します。
このファイルをマイグレーションすると、ORM機能によりPostgreSQLにモデル情報が反映されます。

user.py
from datetime import datetime
from app.db.base_class import Base
from sqlalchemy import Column, DateTime, String

class User(Base):
    __tablename__ = "users"
    id = Column(String, primary_key=True)
    name = Column(String(20), nullable=False, default="default_name")
    created_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

5.FastAPIとPostgreSQLの接続設定

前掲の「1.モジュールのインストール」で下記のモジュールをインストールしたのでここでは、インストール捜査は不要です。

5-1. 設定ファイルの作成

appディレクトリの中にcoreディレクトリを新規作成します。
配下に__init__.pyconfig.pyという名前のファイルを作成しましょう。
__init__.pyは空のファイルで大丈夫です。__init__.pyを配置することで、Pythonはこのディレクトリをパッケージとして認識します。

5-2.config.pyを更新

config.pyには、pydantic-settingsBaseSettingsクラスを利用します。
このBaseSettingsクラスを利用することで、環境変数や.envファイルからの設定値を簡単に読み込むことができるようになります。

config.py
import enum
from typing import Any,Optional

from pydantic import PostgresDsn,ValidationInfo,field_validator
from pydantic_settings import BaseSettings

class AppEnvironment(str,enum.Enum):
        DEVELOP = "development"
        PRODUCTION = "production"

class Settings(BaseSettings):
        ENVIRONMENT:AppEnvironment

        API_VI_STR:str = "/api/v1"
        PROJECT_NAME: str = "my-next-app13"

        POSTGRES_SERVER:str
        POSTGRES_USER: str
        POSTGRES_PASSWORD: str
        POSTGRES_DB: str
        POSTGRES_PORT: str
        SQLALCHEMY_DATABASE_URI: Optional[str] = None

        @field_validator("SQLALCHEMY_DATABASE_URI",mode="after")
        def assemble_db_Connection(cls,v:Optional[str],values:ValidationInfo)->Any:
            if isinstance(v, str):
                return v
        
            return str(
                PostgresDsn.build(
                    scheme="postgresql",
                    username=values.data.get("POSTGRES_USER"),
                    password=values.data.get("POSTGRES_PASSWORD"),
                    host=values.data.get("POSTGRES_SERVER"),
                    port=int(values.data.get("POSTGRES_PORT")),
                    path=f"{values.data.get('POSTGRES_DB') or ''}",
                )
            )
settings = Settings()    

5-3..envファイルの作成

backendファイル直下に.envファイルを新規作成します。中身は下記のように書きます。

.env
ENVIRONMENT='development' 

POSTGRES_USER='ユーザー名'
POSTGRES_PASSWORD='パスワード'
POSTGRES_DB='データベース名'
POSTGRES_SERVER='localhost'
POSTGRES_PORT='5432'

6.データベースへ接続するための設定

SQLAlchemyを使ってデータベースへ接続します。前掲のappディレクトリ内にsession.pyという名前のファイルを新規作成しましょう。
session.pyの中身は、下記のように記載します。

session.py
from app.core.config import settings
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session,sessionmaker

if settings.SQLALCHEMY_DATABASE_URI:
    engine = create_engine(
        settings.SQLALCHEMY_DATABASE_URI,
        pool_pre_ping=True,
    )
    SessionLocal = scoped_session(
        sessionmaker(autocommit=False,autoflush=False,bind=engine)
    )

    if settings.ENVIRONMENT == "development":
        db_info = f"Using database at (settings.SQLALCHEMY_DATABASE_URI)"
        print(db_info)
else:
    raise ValueError("SQLALCHEMY_DATABASE_URI is not set")

6-1.DB接続のテスト

接続はちゃんとできたかを確認するため、appフォルダの直下にtest_connection.pyを新規作成します。

test_connection.py
from sqlalchemy import text
from app.core.session import engine

with engine.connect() as connection:
    result = connection.execute(text("SELECT 'Hello, World!'"))
    print(result.scalar())

7.マイグレーション設定

7-1.軽量マイグレーションツールAlembicをインストール

Alembicは、SQLAlchemyで使用するための軽量データベースマイグレーションツールです。Alembicを用いると、データベーススキーマの変更履歴を一元的に管理できます。
下記のコマンド使ってインストールします。

pipenv install alembic

7-2.Alembicの初期設定

Alembicをインストール完了後に、次のコマンドを使ってAlembicの初期設定を行います。

pipenv run alembic init migration

このコマンド実行後に、下記のようにalembic.iniファイルとmigrationディレクトリが生成されます。

7.3 alembic.iniとenv.pyの設定と紐づけ

alembic.iniファイルとmigration/env.pyファイルに、プロジェクトのデータベース設定を反映させるため下記の操作を行います。

7-3-1.alembic.iniを更新

sqlalchemy.urlの値を%(DB_URL)sに設定します。

alembic.ini
sqlalchemy.url = %(DB_URL)s

7-3-2.env.pyの更新

migration/env.pyファイルを開き、以下の変更を加えましょう。

1.必要なモジュールをインポートし、configオブジェクトを取得する。
2.モデルのメタデータをインポートし、target_metadataに割り当てる。
3.config.set_section_optionDB_URLを動的に設定する。

「👈」の箇所が、追加または修正の部分となります。

migration/env.py
from logging.config import fileConfig

from sqlalchemy import engine_from_config
from sqlalchemy import pool
from app.core.config import settings #👈追加
from app.db.base_class import Base #👈追加
from alembic import context

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
    fileConfig(config.config_file_name)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from app.models.user import User # noqa
target_metadata = Base.metadata # 👈修正
#target_metadata = None #コメントアウト

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
config.set_section_option("alembic", "DB_URL", settings.SQLALCHEMY_DATABASE_URI)#👈追加

def run_migrations_offline() -> None:
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    url = config.get_main_option("sqlalchemy.url")
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )

    with context.begin_transaction():
        context.run_migrations()


def run_migrations_online() -> None:
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    connectable = engine_from_config(
        config.get_section(config.config_ini_section, {}),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(
            connection=connection, target_metadata=target_metadata
        )

        with context.begin_transaction():
            context.run_migrations()


if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

8.マイグレーションファイル作成と実行

8-1.マイグレーションファイル作成

以下のコマンドを実行することで、マイグレーションファイルが自動生成されます。

pipenv run alembic revision --autogenerate -m "description_of_your_change"

8-2.マイグレーションの実行

マイグレーションを実行するには、以下のコマンドを使用します。

pipenv run alembic upgrade head

下記のようにマイグレーションできれば完成です。
image.png

トラブルシューティング

(質問1)FastAPIで「pipenv run python -m app.test_connection」としたら下記のエラーが出た。

Loading .env environment variables...
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "C:\Users\xxx\Desktop\my-next-app13\backend\app\test_connection.py", line 1, in <module>
    from sqlalchemy import text
ModuleNotFoundError: No module named 'sqlalchemy'
(回答)

ModuleNotFoundError: No module named 'sqlalchemy' は、実行されている仮想環境に SQLAlchemy がインストールされていないことが原因です。

以下のチェックポイントと解決方法を順番に試してください。
pipenv の仮想環境にSQLAlchemyが入っていないので、下記のコマンドを使ってインストールします。

pipenv install sqlalchemy

(質問2)ディレクトリを修正して「pipenv run python -m app.test_connection」としたら下記のエラーが出た。

Loading .env environment variables...
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "C:\Desktop\my-next-app13\backend\app\test_connection.py", line 2, in <module>
    from app.core.session import engine
  File "C:\Desktop\my-next-app13\backend\app\core\session.py", line 1, in <module>
    from app.core.config import settings
  File "C:\Desktop\my-next-app13\backend\app\core\config.py", line 4, in <module>
    from pydantic import PostgresDsn,ValidationInfo,field_validator
ModuleNotFoundError: No module named 'pydantic'

ディレクトリ構造

----------------------------
backend/
  app/
    __init__.py   
    core/
      __init__.py 
      session.py
    test_connection.py
----------------------------

(回答)

ModuleNotFoundError: No module named 'pydantic' は、pipenv の仮想環境に Pydantic がインストールされていないのが原因です。

FastAPI + SQLAlchemy の構成ではPydanticは必須なので、単純にパッケージ追加すれば解決します。

✅ 解決方法:Pydantic をインストールする

FastAPI では Pydantic v2 系を推奨(今のFastAPIv2を使う想定)
なのでpipenvで以下を実行してください:

pipenv install pydantic
pipenv install pydantic-settings

サイト

FastAPIとPostgreSQLの結合:モデル定義とマイグレーション入門

Full Stack TikTok Clone: NestJS, GraphQL, Prisma, Postgres, React, Apollo Client, Zustand & Tailwind

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?