Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第7章 実践:uv・Ruff・型で作るモダンプロジェクト

これまでに学んだ「uv によるプロジェクト管理」「Ruff によるコード品質管理」「Python の静的型付け」「dataclass と Pydantic によるデータモデリング」を統合し、実際に動くモダンな Python プロジェクトを構築・運用する一連の流れを体験します。

本章では、シンプルなタスク管理アプリケーションを題材に、初期化から実装、検証、実行までの標準的な開発サイクルをハンズオン形式で進めます。

7.1 プロジェクトの初期化と構成

まず、新しい作業ディレクトリを作成し、uv init でプロジェクトを初期化します。

uv init task-manager
cd task-manager

ディレクトリ構成の設計

保守性の高い標準的な Python プロジェクト構成を作成します:

task-manager/
├── pyproject.toml         # プロジェクト定義と各種ツール設定
├── .python-version        # Python バージョン
├── README.md              # プロジェクト概要
└── src/
    └── task_manager/
        ├── __init__.py    # パッケージ初期化ファイル
        ├── models.py      # 型定義とデータモデル
        ├── service.py     # ビジネスロジック
        └── main.py        # エントリーポイント

7.2 開発ツールの追加

開発に必要なリンター・フォーマッター(ruff)および静的型チェッカー(ty)を開発用依存関係として追加します。

uv add --dev ruff ty

7.3 pyproject.toml の設定

プロジェクトルートの pyproject.toml に、Ruff と ty の動作設定をまとめて記述します。

[project]
name = "task-manager"
version = "0.1.0"
description = "uv, Ruff, 型ヒントを活用したモダン Python プロジェクトのサンプル"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

# --- Ruff 設定 ---
[tool.ruff]
target-version = "py312"
line-length = 88

[tool.ruff.lint]
select = [
    "E",    # pycodestyle エラー
    "W",    # pycodestyle 警告
    "F",    # Pyflakes
    "I",    # isort (インポート整列)
    "B",    # flake8-bugbear
    "UP",   # pyupgrade
    "RUF",  # Ruff 独自ルール
]
ignore = ["E501"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

# --- ty 設定 ---
[tool.ty]

[tool.ty.rules]
unused-ignore-comment = "warn"
redundant-cast = "ignore"
possibly-missing-attribute = "error"

7.4 アプリケーションの実装

型安全性を重視した各モジュールを実装します。

データモデル (src/task_manager/models.py)

第5章および第6章で学んだ Literal、TypedDict、および @dataclass(frozen=True) を活用してデータ構造を定義します。

from dataclasses import dataclass
from typing import Literal, TypedDict

type TaskStatus = Literal["todo", "in_progress", "done"]

class TaskDict(TypedDict):
    """JSON 連携用の辞書型表現"""
    id: int
    title: str
    status: TaskStatus

@dataclass(frozen=True)
class Task:
    """タスクを表す不変データモデル"""
    id: int
    title: str
    status: TaskStatus = "todo"

    def to_dict(self) -> TaskDict:
        return {
            "id": self.id,
            "title": self.title,
            "status": self.status,
        }

サービスロジック (src/task_manager/service.py)

型注釈を漏れなく記述し、タスクの登録・検索・状態変更を行うサービスクラスを実装します。

from task_manager.models import Task, TaskStatus

class TaskService:
    """タスクを管理するサービスクラス"""

    def __init__(self) -> None:
        self._tasks: dict[int, Task] = {}
        self._next_id: int = 1

    def add_task(self, title: str) -> Task:
        """新しいタスクを追加する"""
        task = Task(id=self._next_id, title=title)
        self._tasks[task.id] = task
        self._next_id += 1
        return task

    def get_task(self, task_id: int) -> Task | None:
        """ID を指定してタスクを取得する(存在しない場合は None)"""
        return self._tasks.get(task_id)

    def update_status(self, task_id: int, new_status: TaskStatus) -> Task:
        """タスクの進行状況を更新する"""
        current_task = self.get_task(task_id)
        if current_task is None:
            raise ValueError(f"Task with ID {task_id} not found")

        updated_task = Task(
            id=current_task.id,
            title=current_task.title,
            status=new_status,
        )
        self._tasks[task_id] = updated_task
        return updated_task

    def list_tasks(self, status: TaskStatus | None = None) -> list[Task]:
        """条件に一致するタスク一覧を取得する"""
        tasks = list(self._tasks.values())
        if status is not None:
            return [task for task in tasks if task.status == status]
        return tasks

エントリーポイント (src/task_manager/main.py)

作成したサービスを呼び出して実行するメイン関数を記述します。

from task_manager.service import TaskService

def main() -> None:
    service = TaskService()

    # タスクの登録
    task1 = service.add_task("uv の使い方を学ぶ")
    task2 = service.add_task("Ruff でコードを検証する")

    print(f"作成タスク: {task1.title} (状態: {task1.status})")

    # タスクの完了更新
    service.update_status(task1.id, "done")

    # 一覧の表示
    print("\n--- 全タスク一覧 ---")
    for task in service.list_tasks():
        print(f"[{task.status.upper()}] ID={task.id}: {task.title}")

if __name__ == "__main__":
    main()

7.5 コード品質の検証とフォーマット

作成したコードに対して、Ruff によるリント検査とコードフォーマットを実行します。

1. リント検査と自動修正

uv run ruff check --fix .

不要なインポートの除去やインポート順序の整列が瞬時に実行されます。

2. コードフォーマット

uv run ruff format .

インデントやクォートが PEP 8 / Black スタイルに自動統一されます。

7.6 静的型チェックの実行

次に、ty を実行して型定義の整合性を検証します。

uv run ty check src

すべての引数・戻り値・型パラメータが正しく整合していれば、以下のように出力されます:

All checks passed!

もし型の不整合や None 参照の危険がある場合は、具体的なファイルと行番号とともに警告が表示され、不具合を本番実行前に修正できます。

7.7 アプリケーションの実行

最後に、uv run を用いてアプリケーションを実行します。

uv run python -m task_manager.main

期待通りの動作がターミナルに表示されれば、完成です:

作成タスク: uv の使い方を学ぶ (状態: todo)

--- 全タスク一覧 ---
[DONE] ID=1: uv の使い方を学ぶ
[TODO] ID=2: Ruff でコードを検証する

7.8 モダン開発サイクルのまとめ

本章で実践したモダン Python 開発の標準的なルーティンは以下の通りです:

  1. 環境と依存関係の管理: uv add や uv sync を使い、常に再現性のある仮想環境を保つ。
  2. 型を意識した設計: 変数・関数・モデルに明確な型注釈を付与し、自己文書化と不具合抑止を図る。
  3. 高速リントと整形: 保存時またはコミット前に uv run ruff check --fix と uv run ruff format を実行する。
  4. 厳格な型検証: uv run ty check で静的解析を行い、型安全性を担保する。
  5. 安全な実行: uv run を介して環境差分のない状態で実行・テストする。

これらのツールとプラクティスを組み合わせることで、高速でストレスのない、極めて信頼性の高い Python 開発が実現できます。