Poetry × FastAPI × SQLAlchemy × Alembicで作る、モダンなPythonバックエンド環境構築ガイド

その他

目的

Python製WebAPIを、依存管理からDBマイグレーションまで完全自動化し、手動の venvrequirements.txt 管理から卒業する手法を紹介します。

これらを組み合わせることで:

  • 環境構築が自動化
  • コードからDB設計を自動反映
  • チーム開発でも「環境が一致」

という理想的な開発体験を実現します。

役割

ツール役割ひとことで言うと
Poetry依存管理 & 仮想環境Pythonのnpm的存在
FastAPIWebアプリケーション高速・型安全なAPIフレームワーク
SQLAlchemyORMPythonからDBを操作
AlembicDBマイグレーションスキーマの変更を安全に管理

手順

プロジェクトの作成

poetry new fastapi-sample
cd fastapi-sample

パッケージのインストール

poetry add fastapi uvicorn sqlalchemy psycopg2 alembic

初期状態のフォルダ構成

fastapi-sample/
├── pyproject.toml
├── README.md
├── src/
│   └── fastapi_sample/
│       └── __init__.py
└── tests/

フォルダ構成を準備

mkdir -p src/fastapi_sample/{routers,models}
touch src/fastapi_sample/{main.py,database.py,models/__init__.py}
src/
 └── fastapi_sample/
      ├── __init__.py
      ├── main.py
      ├── database.py
      ├── models/
      │   ├── __init__.py
      │   └── user.py
      └── routers/

database.py の作成(SQLAlchemy設定)

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./test.db")

engine = create_engine(
    DATABASE_URL,
    connect_args={"check_same_thread": False} if "sqlite" in DATABASE_URL else {}
)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

モデルの作成

from sqlalchemy import Column, Integer, String
from ..database import Base

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(50))
    email = Column(String(100), unique=True, index=True)
from ..database import Base
from .user import User

FastAPIアプリの作成

from fastapi import FastAPI
from . import models
from .database import engine
from src.fastapi_sample.routers import user as user_router

models.Base.metadata.create_all(bind=engine)

app = FastAPI()
app.include_router(user_router.router)

@app.get("/")
def read_root():
    return {"message": "Hello from FastAPI with src layout!"}

起動確認

poetry run uvicorn src.fastapi_sample.main:app --reload --port 8001

http://localhost:8001 が表示されれば成功

Alembicの初期化

poetry run alembic init migrations

以下が作成される

migrations/
├── env.py
├── script.py.mako
└── versions/
alembic.ini

alembic.iniの設定

接続URLを設定する

sqlalchemy.url = sqlite:///./test.db

migrations/env.pyの設定

src をPythonパスに追加して、Baseを読み込むように編集

import sys
import pathlib
from logging.config import fileConfig
from sqlalchemy import engine_from_config, pool
from alembic import context

# --- 追加 ---
from src.fastapi_sample.database import Base

# -- Alembic設定 --
config = context.config
fileConfig(config.config_file_name)
target_metadata = Base.metadata

マイグレーションの生成と実行

% poetry run alembic revision --autogenerate -m "create users table"
  Generating xxxx/fastapi-sample/migrations/versions/951446c9e367_create_users_table.py ...  done

sqliteでテーブルの確認

sqlite3 test.db

sqlite> .schema
CREATE TABLE users (
	id INTEGER NOT NULL, 
	name VARCHAR(50), 
	email VARCHAR(100), 
	PRIMARY KEY (id)
);
CREATE INDEX ix_users_id ON users (id);
CREATE UNIQUE INDEX ix_users_email ON users (email);

crud/user.pyの追加

from sqlalchemy.orm import Session
from fastapi_sample.models.user import User

def get_user(db: Session, user_id: int):
    return db.query(User).filter(User.id == user_id).first()

def get_users(db: Session, skip: int = 0, limit: int = 100):
    return db.query(User).offset(skip).limit(limit).all()

def create_user(db: Session, name: str, email: str):
    db_user = User(name=name, email=email)
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user

def update_user(db: Session, user_id: int, name: str, email: str):
    db_user = db.query(User).filter(User.id == user_id).first()
    if not db_user:
        return None
    db_user.name = name
    db_user.email = email
    db.commit()
    db.refresh(db_user)
    return db_user

def delete_user(db: Session, user_id: int):
    db_user = db.query(User).filter(User.id == user_id).first()
    if not db_user:
        return None
    db.delete(db_user)
    db.commit()
    return db_user

routers/user.pyの追加

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from fastapi_sample.database import SessionLocal
from fastapi_sample.crud import user as crud_user

router = APIRouter(prefix="/users", tags=["Users"])

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@router.post("/")
def create_user(name: str, email: str, db: Session = Depends(get_db)):
    return crud_user.create_user(db, name, email)

@router.get("/")
def read_users(db: Session = Depends(get_db)):
    return crud_user.get_users(db)

@router.get("/{user_id}")
def read_user(user_id: int, db: Session = Depends(get_db)):
    user = crud_user.get_user(db, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

@router.put("/{user_id}")
def update_user(user_id: int, name: str, email: str, db: Session = Depends(get_db)):
    user = crud_user.update_user(db, user_id, name, email)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

@router.delete("/{user_id}")
def delete_user(user_id: int, db: Session = Depends(get_db)):
    user = crud_user.delete_user(db, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

OpenAPIの確認

FastAPIではOpenAPI等のドキュメントが自動で生成される
http://localhost:8001/docs を開く

関連記事

カテゴリー

アーカイブ

Lang »