はじめに
最近、AIと一緒にPythonツールをいくつか作っていて、こんなことに気づきました。
プロジェクトによって、設定ファイルの構成がバラバラ。
あるプロジェクトには requirements.txt と pytest.ini があって、別のプロジェクトには pyproject.toml だけがある。conftest.py はどこにでも出てくる。何が正解なのか、そもそも「旧来スタイル」と「モダンスタイル」という区別があること自体、最初はわかっていませんでした。
この記事では、実際に requirements.txt + pytest.ini 構成から pyproject.toml に移行した経験をもとに、各ファイルの役割と移行手順を整理します。
対象読者
- Python を書き始めて間もない方
-
requirements.txtは使ったことがあるがpyproject.tomlはよくわからない方
設定ファイルの全体像
まず登場する設定ファイルを整理します。大きく「旧来スタイル」と「モダンスタイル」に分かれています。
旧来スタイル(requirements.txt 方式)
設定が複数ファイルに分散しているのが特徴です。
| ファイル | 役割 |
|---|---|
requirements.txt |
依存パッケージの一覧。pip install -r requirements.txt で使う |
pytest.ini |
pytestの動作設定(テストパス・オプションなど) |
setup.py / setup.cfg
|
パッケージとして配布する場合のメタデータ設定(最近は新規ではあまり書かない) |
.flake8 / mypy.ini
|
LinterやType checkerごとの個別設定ファイル |
モダンスタイル(pyproject.toml 方式)
pyproject.toml 1ファイルに集約するのが思想です。
| セクション | 役割 |
|---|---|
[project] |
パッケージ名・バージョン・依存パッケージ(requirements.txtの代替) |
[tool.pytest.ini_options] |
pytestの動作設定(pytest.iniの代替) |
[tool.mypy] / [tool.ruff]
|
各ツールの設定(個別ファイルの代替) |
[build-system] |
ビルドバックエンドの指定 |
conftest.py はどちらでも使う
conftest.py はpytestのファイルであり、旧来・モダンどちらのスタイルとも無関係です。pytestを使う限り、すべてのプロジェクトで登場します。
| ファイル | 役割 | スタイルとの関係 |
|---|---|---|
conftest.py |
テスト共通fixture・フックの定義 | スタイル無関係。pytestを使う全プロジェクトで使用 |
pytest.ini |
pytestの動作設定 | 旧来スタイルで使う |
[tool.pytest.ini_options] |
pytestの動作設定 | モダンスタイルで使う(pytest.iniの代替) |
移行前のファイル構成
今回の移行対象はこちらです。
csv-analyzer/
├── csv_analyzer.py # メインの解析ロジック
├── cli.py # CLIコマンド定義(click)
├── conftest.py # テストの共通処理(フィクスチャなど)
├── pytest.ini # pytestの全体設定
├── requirements.txt # 依存パッケージ一覧
├── README.md
└── tests/
├── test_csv_analyzer.py
└── test_cli.py
移行前の各ファイル:
# requirements.txt
click>=8.0.0
pytest>=7.0.0
# pytest.ini
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = --cov=. --cov-report=term-missing
filterwarnings =
ignore::pytest.PytestCollectionWarning
# conftest.py
import sys
import os
# プロジェクトルートを sys.path に追加する
sys.path.insert(0, os.path.dirname(__file__))
pyproject.toml への移行
Step 1:[project] — 依存パッケージの整理
requirements.txt の内容を移行します。ポイントは本番依存と開発依存を分離することです。
-
clickはアプリ本体で使うのでdependencies(本番依存)へ -
pytestは開発時だけ必要なのでoptional-dependencies(開発依存)へ
[project]
name = "csv-analyzer"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"click>=8.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
]
インストールコマンドは以下のように変わります。
# 移行前
pip install -r requirements.txt
# 移行後(本番依存のみ)
pip install .
# 移行後(開発・テスト用依存も含む)
pip install -e ".[dev]"
Step 2:[tool.pytest.ini_options] — pytest設定の移行
pytest.ini の内容を移行します。TOMLでは値の型(配列・文字列)に注意が必要です。
pytest.ini との対応関係はこうなります。
| pytest.ini | pyproject.toml | 変更点 |
|---|---|---|
testpaths = tests |
testpaths = ["tests"] |
配列になる |
python_files = test_*.py |
python_files = ["test_*.py"] |
配列・クォートが必要 |
python_classes = Test* |
python_classes = ["Test*"] |
配列・クォートが必要 |
python_functions = test_* |
python_functions = ["test_*"] |
配列・クォートが必要 |
addopts = --cov=. --cov-report=term-missing |
addopts = ["--cov=.", "--cov-report=term-missing"] |
要素ごとに分割 |
filterwarnings = ignore::... |
filterwarnings = ["ignore::..."] |
配列になる |
また、conftest.py にあった sys.path.insert(...) は pythonpath 設定で代替できます。
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = ["--cov=.", "--cov-report=term-missing"]
filterwarnings = ["ignore::pytest.PytestCollectionWarning"]
pythonpath = ["."] # conftest.py の sys.path.insert(...) の代替
これにより conftest.py のパス操作は不要になります。
Step 3:[build-system] — ビルド設定の追加
pip install -e ".[dev]" を動かすために必要な設定です。
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
-
requires:ビルド時にpipが自動でインストールするツール -
build-backend:実際にビルドを担当するツールの指定
Step 4:[tool.setuptools] — フラットレイアウトへの対処
今回のプロジェクトは cli.py と csv_analyzer.py がルート直下に並んでいます。この構成(フラットレイアウト)では、setuptools がどちらをパッケージ本体にすべきか判断できずエラーになります。
error: Multiple top-level modules discovered in a flat-layout: ['cli', 'csv_analyzer'].
py-modules で明示的に列挙することで解決します。
[tool.setuptools]
py-modules = ["csv_analyzer", "cli"]
完成した pyproject.toml
[project]
name = "csv-analyzer"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"click>=8.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
]
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
py-modules = ["csv_analyzer", "cli"]
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = ["--cov=.", "--cov-report=term-missing"]
filterwarnings = ["ignore::pytest.PytestCollectionWarning"]
pythonpath = ["."]
移行後のファイル構成
csv-analyzer/
├── csv_analyzer.py # メインの解析ロジック
├── cli.py # CLIコマンド定義(click)
├── conftest.py # テストの共通処理(フィクスチャなど) ※sys.path操作は削除
├── pyproject.toml # 依存パッケージ・ツール設定(一元管理)
├── README.md
└── tests/
├── test_csv_analyzer.py
└── test_cli.py
pytest.ini と requirements.txt が消え、pyproject.toml 1ファイルに集約されました。
ハマりどころ
① build-backend の誤記
# NG:内部APIで直接使用を意図していない非推奨の旧形式
build-backend = "setuptools.backends.legacy:build"
# OK
build-backend = "setuptools.build_meta"
② フラットレイアウトエラー
ルート直下に .py ファイルが複数ある場合、[tool.setuptools] py-modules の指定が必要です。src/ 配下にソースをまとめる「srcレイアウト」にする方法もあります。
まとめ:移行チェックリスト
次のプロジェクトで pyproject.toml に移行するときの確認リストです。
□ [project] dependencies に本番依存パッケージを記載したか
□ [project.optional-dependencies] dev に開発・テスト依存を分離したか
□ [build-system] を追加したか(build-backend = "setuptools.build_meta")
□ ルート直下に .py ファイルが複数ある場合、[tool.setuptools] py-modules を追加したか
□ pytest.ini の内容を [tool.pytest.ini_options] に移行したか(値は配列で記載)
□ conftest.py の sys.path 操作を pythonpath 設定に置き換えたか
□ requirements.txt と pytest.ini を削除したか
おわりに
pyproject.toml は最初とっつきにくく感じましたが、各セクションの役割を理解すると「設定が1ファイルにまとまっている」ことの快適さがわかります。AIと一緒に開発する場合も、最初にこの構成を伝えておくと一貫したスタイルでコードを生成してもらいやすくなります。