1. はじめに
チーム開発を進める中で、各開発者の Python バージョンやインストールされているパッケージが異なり、開発環境の統一が難しいという問題に直面することがあります。この問題を解決し、Python のバージョン管理だけでなく、使用するライブラリ群のバージョンもプロジェクトごとに統一し、かつディレクトリ移動に応じて自動的に開発環境を切り替える方法を、uv
と direnv
を用いて実現します。
2. 前提
本記事の手順は、以下の環境を前提としています。
- macOS であること
- Homebrew が利用可能であること
3. 本題
3-1. 必要なツールのインストール
まず、Python パッケージインストーラー兼バージョマネージャーである uv
と、ディレクトリベースで環境変数を管理する direnv
を インストールします。
brew install uv
brew install direnv
3-2. direnv をシェルに設定
direnv
を利用するためには、お使いのシェルに応じた設定をシェルの設定ファイル(例: .zshrc
, .bashrc
など)に追加します。
zsh の場合 (.zshrc):
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
source ~/.zshrc
bash の場合 (.bashrc):
echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
source ~/.bashrc
設定後、ターミナルを再起動するか、source
コマンドで設定を読み込んでください。
3-3. プロジェクトごとの環境構築
ここでは、例として sample_project
という名前のプロジェクトを作成し、Python 3.13.3 の環境を構築します。
プロジェクトディレクトリの作成と uv init
による初期化
まずプロジェクトディレクトリを作成し、uv init
コマンドでプロジェクトを初期化します。これにより、pyproject.toml
ファイルが生成されます。
mkdir sample_project
cd sample_project
uv init
uv init
を実行することで以下ファイルが生成されます。
注意: uv init
が生成する内容は uv
のバージョンによって変わることがあります。下記は一例です。
生成される pyproject.toml
の例:
[project]
name = "sample-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12.2"
dependencies = []
pyproject.toml
の編集
次に、pyproject.toml
を編集して、プロジェクトに必要な依存関係を定義します。
例として、fastapi
と uvicorn
を通常依存、ruff
と pytest
を開発用依存として追加します。
[project]
name = "sample_project"
version = "0.1.0"
description = "A FastAPI project managed with uv and direnv."
readme = "README.md"
requires-python = ">=3.12.2"
dependencies = [
"fastapi>=0.100.0",
"uvicorn[standard]>=0.20.0",
]
# 開発用依存関係 (uv がサポートする形式)
[project.optional-dependencies]
dev = [
"ruff",
"pytest",
"black",
]
requires-python
は、このプロジェクトが要求する Python のバージョンを指定します。使用する Python バージョンに合わせて適切に設定してください。
pyproject.toml
が編集できたらライブラリを同期します。
uv pip sync pyproject.toml
uv による仮想環境の作成
uv venv
コマンドを使用して、プロジェクト用の仮想環境を作成します。pyproject.toml
の requires-python
で指定したバージョンと互換性のある Python バージョンを指定します(例: Python 3.13.3)。
uv venv --python 3.13.3
成功すると、プロジェクトディレクトリ直下に .venv
という名前の仮想環境が作成されます。
.envrc
ファイルの作成と編集
次に、ディレクトリに入ったときに自動的に仮想環境が有効になり、依存関係が同期されるように .envrc
ファイルを作成・編集します。
# 0. uv コマンドの存在確認
# `has` は direnv の標準関数で、コマンドの存在をチェックします。
if ! has uv; then
echo "uv が見つからないためパッケージの同期をスキップします" >&2
return 1 # .envrc の処理を中断し、エラーコード 1 を返すが、シェルは終了しない
fi
# 1. 仮想環境のアクティベート
# プロジェクトローカルの .venv を有効化します。
source .venv/bin/activate
# 2. pyproject.toml と uv.lock の変更を監視
# これらのファイルが変更された場合、direnv は .envrc を再ロードします。
watch_file pyproject.toml
watch_file uv.lock # uv.lock は uv pip sync/add などで生成・更新されます
# 3. 依存関係の自動同期
# uv.lock に変更があった場合のみ実行されます。
# 開発環境を想定し、開発用依存関係も含めて同期します (--extra-index-url dev)。
# `uv pip sync` は `pyproject.toml` と `uv.lock` (あれば) に基づいて環境を同期します。
if [ -f uv.lock ]; then
uv pip sync pyproject.toml --extra-index-url dev
fi
上記の内容を sample_project/.envrc
として保存します。
解説
-
has uv
:uv
コマンドが利用可能かチェックします -
source .venv/bin/activate
:uv
で作成したローカルの仮想環境を有効にします -
watch_file pyproject.toml
/watch_file uv.lock
: これらのファイルが変更されると、.envrc
が再評価され、後続のコマンド(ここではuv pip sync
)が再実行されるトリガーとなります -
uv pip sync pyproject.toml --extras dev
:pyproject.toml
に記述された通常依存と、[project.optional-dependencies]
のdev
グループに記述された開発用依存関係を仮想環境に同期する。uv.lock
ファイルが存在すれば、ロックファイルに基づいて同期し、なければpyproject.toml
から解決してuv.lock
を生成/更新する
direnv に設定の読み込みを許可
セキュリティのため、direnv
は .envrc
ファイルの内容を自動的には実行しません。
以下のコマンドで、このディレクトリの .envrc
ファイルの実行を許可します。
direnv allow .
初回許可後、sample_project
ディレクトリに入るたびに、自動的に .venv
仮想環境が有効になり、依存関係が pyproject.toml
に基づいて同期されます。
依存関係の確認
.envrc
が正しく設定されていれば、direnv allow .
を実行した際、または一度ディレクトリ外に出て再度入った際に、uv pip sync
が実行され、pyproject.toml
に記述した依存関係がインストールされます。
uv pip list
でインストールされたパッケージを確認できます。
uv pip list
4. 開発ワークフロー
4-1. 新しいパッケージの追加
プロジェクトに新しいパッケージを追加する場合は、uv add
コマンドを使用します。これにより、pyproject.toml
が自動的に更新され、uv.lock
も更新されます。direnv
が変更を検知し、自動的に uv pip sync
を実行して仮想環境にパッケージをインストールします。
通常依存の追加:
uv add requests
pyproject.toml
の [project].dependencies
に requests
が追加されます。
開発用依存の追加 (optional-dependencies の 'dev' グループへ):
uv add "ruff" --group dev
uv add "black" --optional dev
pyproject.toml
の [project.optional-dependencies.dev]
に ruff
や black
が追加されます。
(uv add
の --dev
や --group
オプションの挙動は uv
のバージョンにより若干異なる場合があるため、公式ドキュメントをご確認ください。[project.optional-dependencies]
を使うのが標準的です。)
uv add
実行後、.envrc
の watch_file
機能により direnv
が pyproject.toml
の変更を検知し、自動的に uv pip sync
を実行して新しいパッケージが仮想環境にインストールされます。手動で同期したい場合は、ディレクトリに入り直すか、direnv reload
を実行します。
4-2. 依存関係の更新
既存のパッケージを最新バージョンに更新したい場合も、pyproject.toml
を直接編集するか、uv add package_name@latest
のようにして更新後、direnv
が自動で同期します。
もし手動ですべてのパッケージを最新バージョンに更新する場合は以下コマンドを実行します。
# すべてのパッケージを最新バージョンに更新
uv pip sync pyproject.toml --upgrade
# 特定のパッケージのみを更新
uv pip sync pyproject.toml --upgrade-package requests
# 開発用依存関係も含めて更新
uv pip sync pyproject.toml --upgrade --group=dev
5. 動作確認
設定が正しく行われていれば、以下の動作が確認できます。
5-1. sample_project
へ移動
ターミナルで sample_project
ディレクトリに移動すると、.envrc
がロードされ、uv pip sync
が実行されます。プロンプトに (.venv)
が表示され、仮想環境が有効になります。
python --version
や which python
で仮想環境の Python が使われていることを確認します。
pyproject.toml
に記述した fastapi
や開発用ツールの ruff
などが import
できるか(またはコマンドとして利用できるか)確認します。
5-2. sample_project
から移動:
他のディレクトリに移動すると、.envrc
がアンロードされ、仮想環境が無効になります。
5-3. pyproject.toml
の変更:
pyproject.toml
の依存関係を手動で変更・保存すると、direnv
が変更を検知し、再度 uv pip sync
を実行して環境を更新します。
6. まとめ
uv
と direnv
用いてプロジェクトごとの Python 環境を動的に切り替える方法を紹介しました。
チームメンバー内における Python バージョンやライブラリのバージョン統一に向けて少しでも参考になれば幸いです。
7. 【推奨】 .gitignore
の設定
プロジェクトの .gitignore
ファイルには、uv
が作成する仮想環境ディレクトリと direnv
が使用する状態管理ディレクトリを追加しておくことを推奨します。
# uv
.venv/
.direnv/
参考