🤖

uv run 徹底解説

に公開

免責

この記事は自作コーディングエージェントShaftを使用し執筆された生成AI記事です。

https://gitlab.com/tkithrta/shaft

なぜ今回ほぼ全て生成AIで執筆した内容を投稿したのかというと、以下の理由があります。

  • 現在のShaftのプログラマーペルソナがテクニカルライティングとしてどのぐらい優れているかのベンチマークテスト(テクニカルライターペルソナを追加する予定のため)
  • ShaftをPython Scriptとして動かすようになったため動作確認(ソースコードは後述)
  • uv公式ドキュメントのRunning scriptsでは最新の情報を含めたオプションに関する高度な説明がないため
  • ググって出てくるuv runに関する記事も公式ドキュメントの域を出ていないため

https://docs.astral.sh/uv/guides/scripts/

以上のことから、uv 0.8.8のソースコードとhelpコマンドをコンテキストファイルとして読み込み、複数のMarkdownファイルを生成する手法を行っています。

https://github.com/astral-sh/uv/blob/0.8.8/crates/uv/src/commands/project/run.rs

curl -LO https://raw.githubusercontent.com/astral-sh/uv/refs/tags/0.8.8/crates/uv/src/commands/project/run.rs
uv help run > help_run.txt
uv run shaft_script.py
Shaft Scriptソースコード
shaft_script.py
#!/usr/bin/env python
import os
import subprocess
import sys
from subprocess import CompletedProcess
from typing import Optional

shaft_command = "task"
shaft_prompt = """
uv runの仕様について徹底解説
"""
shaft_files = """
articles/uv_run_1.md
articles/uv_run_2.md
articles/uv_run_3.md
articles/uv_run_4.md
articles/uv_run_5.md
articles/uv_run_6.md
articles/uv_run_7.md
articles/uv_run_8.md
articles/uv_run_9.md
"""
shaft_refs = """
run.rs
help_run.txt
"""


def play_sound(file_path: str) -> None:
    """Plays a sound file on Windows systems."""
    if sys.platform == "win32":
        import winsound

        if os.path.exists(file_path):
            sound_file_path = file_path
        else:
            script_dir = os.path.dirname(os.path.abspath(__file__))
            sound_file_path = os.path.join(script_dir, file_path)
            if not os.path.exists(sound_file_path):
                print(f"Sound file not found: {file_path}")
                return
        winsound.PlaySound(sound_file_path, winsound.SND_FILENAME)


def run_shaft_command(command: str, prompt: str, files: str, refs: str, timeout: int) -> Optional[CompletedProcess]:
    """Builds and runs the shaft command and returns the result."""
    command_list = ["shaft", command]
    filelist = [f.strip() for f in files.splitlines() if f.strip()]
    for f in filelist:
        command_list.extend(["-f", f])
    reflist = [r.strip() for r in refs.splitlines() if r.strip()]
    for r in reflist:
        command_list.extend(["-r", r])
    command_list.extend(["--", prompt])
    try:
        result = subprocess.run(command_list, timeout=timeout)
        return result
    except subprocess.TimeoutExpired:
        return None


def main() -> None:
    """Main function for standalone script execution."""
    result = run_shaft_command(shaft_command, shaft_prompt.strip(), shaft_files.strip(), shaft_refs.strip(), 300)
    if result is not None:
        play_sound(r"C:\Windows\Media\Windows Background.wav")
    else:
        print("TimeoutExpired")
        play_sound(r"C:\Windows\Media\Windows Foreground.wav")


if __name__ == "__main__":
    main()

Gemini 2.5 Proを使用しており、生成には3分近くかかりました。
それではどうぞ。

uv run 徹底解説 (1/9): はじめに

uv run は、Python環境でコマンドやスクリプトを実行するための強力なツールです。このコマンドは、プロジェクトの依存関係を自動的に管理し、一貫性のある実行環境を提供します。uv のエコシステムの中核をなす機能であり、開発、テスト、本番環境でのスクリプト実行など、様々なシナリオで役立ちます。

このシリーズでは、uv run の基本的な使い方から、高度なオプション、ベストプラクティスまでを網羅的に解説していきます。

主な機能:

  • コマンド/スクリプトの実行: uv run <command>uv run <script>.py のように、任意のコマンドやPythonスクリプトを実行できます。
  • 自動的な環境管理: プロジェクト (pyproject.toml) が存在する場合、そのプロジェクトの仮想環境を自動で作成・更新し、コマンドを実行します。
  • 一時的な依存関係: --with オプションを使うことで、プロジェクトの設定を変更せずに、一時的なパッケージをインストールしてコマンドを実行できます。
  • 分離された環境: --isolated フラグにより、完全にクリーンな環境でコマンドを実行でき、再現性を高めます。
  • PEP 723 サポート: Pythonスクリプトファイル内にインラインで依存関係を記述する PEP 723 をサポートしています。

このガイドを通じて、uv run を使いこなし、Python開発のワークフローをより効率的で信頼性の高いものにしましょう。

uv run 徹底解説 (2/9): コマンドとスクリプトの実行

uv run の最も基本的な使い方は、コマンドやPythonスクリプトを実行することです。uv は、実行対象に応じて最適な方法を自動で選択します。

1. 任意のコマンドの実行

uv run の後ろにコマンドを指定すると、uv はそれを現在のPython環境で実行します。

# プロジェクトの仮想環境、またはアクティブな仮想環境で `pytest` を実行
uv run pytest

# 引数も渡せる
uv run pytest tests/ --verbose

uv はまずプロジェクト(pyproject.toml)を探し、もし見つかれば、そのプロジェクトの仮想環境(例: .venv)を準備してコマンドを実行します。プロジェクトが見つからない場合、親ディレクトリを遡って仮想環境を探したり、アクティブな仮想環境を使用したりします。

2. Pythonスクリプトの実行

.py で終わるファイル名を指定すると、uv はそれをPythonスクリプトとして実行します。これは uv run python <script>.py と同等です。

# my_script.py を実行
uv run my_script.py

3. リモートスクリプトの実行

HTTP(S) URLを渡すことで、リモートのPythonスクリプトをダウンロードして実行することもできます。スクリプトは一時的にダウンロードされ、実行後に削除されます。

# GitHub上のスクリプトを実行
uv run https://raw.githubusercontent.com/user/repo/main/script.py

4. 標準入力からの実行

- を指定すると、標準入力からPythonスクリプトを読み込んで実行します。

echo "import sys; print(sys.version)" | uv run -

5. モジュールとして実行 (-m, --module)

python -m <module> と同じように、Pythonモジュールを実行するには -m または --module フラグを使用します。

# http.server モジュールを起動
uv run -m http.server 8000

uv run は、uv のオプションと実行するコマンドの引数を明確に区別します。uv へのオプションは必ずコマンドの前に置く必要があります。曖昧さをなくすために -- を使うこともできます。

# 正しい例: --verbose は uv のオプション
uv run --verbose pytest

# 正しい例: -- を使って明確に分離
uv run --python 3.12 -- python -c "import sys; print(sys.version)"

uv run 徹底解説 (3/9): プロジェクトと依存関係の管理

uv run の真価は、プロジェクトの依存関係をシームレスに管理する能力にあります。pyproject.toml があるディレクトリで実行すると、uv は自動的にそのプロジェクトのコンテキストを認識します。

1. プロジェクト環境での実行

pyproject.toml を持つプロジェクト内で uv run を実行すると、uv は以下の手順を踏みます。

  1. プロジェクト用の仮想環境(通常は .venv)が存在するか確認します。なければ作成します。
  2. pyproject.tomluv.lock (存在する場合)を基に、仮想環境が最新の状態か確認します。必要であれば、パッケージのインストールや更新(同期)を行います。
  3. 準備が整った仮想環境で、指定されたコマンドを実行します。

これにより、開発者全員が同じ依存関係を持つ環境でコマンドを実行できるため、"私のマシンでは動いたのに"問題を回避できます。

2. 一時的な依存関係の追加 (--with)

特定のコマンドを実行するためだけに、一時的にパッケージをインストールしたい場合があります。例えば、ruff でコードをフォーマットしたいが、プロジェクトの依存関係には追加したくない、というケースです。このような場合、--with オプションが非常に便利です。

# `ruff` を一時的にインストールして実行
uv run --with ruff -- ruff check .

# 複数のパッケージも指定可能
uv run --with "requests==2.31.0" --with "beautifulsoup4" -- python my_scraper.py

--with で指定されたパッケージは、プロジェクトの環境の上に隔離されたレイヤーとしてインストールされます。これにより、プロジェクトの依存関係と競合することなく、ツールを安全に実行できます。

3. requirements.txt からの一時的な依存関係 (--with-requirements)

--with と似ていますが、requirements.txt ファイルからパッケージを読み込むこともできます。

# tools.txt に書かれたツールをインストールして pytest を実行
uv run --with-requirements tools.txt -- pytest

4. 依存関係グループの選択 (--group, --extra)

pyproject.toml で定義されたオプショナル依存関係 ([project.optional-dependencies]) や、uv 独自の依存関係グループ ([tool.uv.dev-dependencies]) を使って実行環境をカスタマイズできます。

# "test" グループの依存関係を含めて実行
uv run --group test -- pytest

# "docs" エクストラを含めて実行
uv run --extra docs -- sphinx-build -b html docs/source docs/build
  • --all-extras: 全てのオプショナル依存関係をインストールします。
  • --no-dev: dev グループを除外します (デフォルトで含まれることが多い)。
  • --group <name>: 指定したグループを含めます。
  • --no-group <name>: 指定したグループを除外します。

これらの機能により、uv run は単なるコマンドランナーではなく、柔軟で信頼性の高い依存関係管理ツールとして機能します。

uv run 徹底解説 (4/9): 環境の制御

uv run は、コマンドが実行されるPython環境をきめ細かく制御するためのオプションを提供します。

1. 隔離された環境での実行 (--isolated)

デフォルトでは、uv run はパフォーマンスのためにプロジェクトの既存の仮想環境(.venv)を再利用します。しかし、CI/CDパイプラインやクリーンな状態でのテストなど、毎回完全に新しい環境で実行したい場合があります。その場合は --isolated フラグを使用します。

# 毎回新しい仮想環境を作成してコマンドを実行
uv run --isolated -- pytest

--isolated を使うと、uv は一時的な仮想環境を作成し、そこにプロジェクトの依存関係をインストールしてからコマンドを実行します。これにより、キャッシュや既存の環境の状態に影響されない、高い再現性が保証されます。

2. アクティブな仮想環境の優先 (--active)

システム上で既に特定の仮想環境をアクティベートしている(例: source .venv/bin/activate)状態で uv run を実行した場合、uv は通常、プロジェクトの .venv を優先します。

もし、アクティブな仮想環境を強制的に使いたい場合は --active フラグを指定します。

# 現在アクティブな仮想環境を優先して使用
uv run --active -- python --version

このオプションは、プロジェクトの環境とは異なる環境で一時的に作業したい場合に便利です。

3. プロジェクトの無視 (--no-project)

uv run を実行したディレクトリや親ディレクトリに pyproject.toml があっても、それを無視してコマンドを実行したい場合があります。--no-project を使うと、uv はプロジェクトを探索せず、グローバルなPythonやアクティブな仮想環境をコンテキストとして使用します。

# `pyproject.toml` があっても無視する
uv run --no-project -- python -c "print('Hello')"

--with と組み合わせることで、プロジェクトとは無関係に、完全にアドホックな環境でコマンドを実行できます。

# プロジェクトを無視し、`requests` のみが入った環境でスクリプトを実行
uv run --no-project --with requests -- python my_script.py

4. 環境変数の読み込み (--env-file)

.env ファイルから環境変数を読み込んでコマンドを実行するには --env-file を使用します。

# .env ファイルを読み込んでからアプリケーションを起動
uv run --env-file .env -- uvicorn my_app:app

複数のファイルを指定した場合、後のファイルが前のファイルで定義された値を上書きします。--no-env-file.env ファイルの自動読み込みを無効にすることもできます。

uv run 徹底解説 (5/9): ロックファイルと同期の制御

uv run は、uv.lock ファイルと連携して、依存関係の解決とインストールのプロセスを厳密に制御する機能を提供します。これにより、再現性とパフォーマンスを両立させることができます。

1. 同期処理のスキップ (--no-sync)

通常、uv run はコマンド実行前に、pyproject.toml と仮想環境の状態を比較し、必要であればパッケージのインストールや削除(同期)を行います。この同期処理をスキップしたい場合は --no-sync を使います。

# 仮想環境の同期を行わずにコマンドを実行
uv run --no-sync -- pytest

このオプションは、環境が最新であることがわかっており、起動時間を短縮したい場合に便利です。--no-sync は自動的に --frozen(後述)を意味します。つまり、ロックファイルが更新されることもありません。

2. ロックファイルの厳格なチェック (--locked)

--locked フラグは、uv.lock が最新の状態であり、変更が不要であることを表明します。もし pyproject.toml の変更などによりロックファイルが古くなっている場合、uv はエラーを出して終了します。

# uv.lock が最新であることを確認してから実行
uv run --locked -- uvicorn main:app

これは、CI環境などで、意図しない依存関係の更新が発生するのを防ぐのに非常に役立ちます。uv lock コマンドでロックファイルを更新してから uv run --locked を使うのが一般的なワークフローです。

3. ロックファイルの固定 (--frozen)

--frozen フラグは、pyproject.toml に変更があったとしても、それを無視して uv.lock に書かれているバージョンを正として環境を同期します。ロックファイルの更新は一切行われません。

# pyproject.toml の変更を無視し、uv.lock の内容で環境を構築して実行
uv run --frozen -- pytest

--locked との違いは、--frozen はロックファイルが古くてもエラーにしない点です。pyproject.toml で依存関係を試行錯誤しているが、テストは安定したロック済みのバージョンで行いたい、といったシナリオで使えます。もし uv.lock が存在しない場合はエラーになります。

4. 厳密な同期 (--exact)

uv run のデフォルトの同期動作は、必要なパッケージを追加・更新するだけで、不要になったパッケージを削除しません。環境から不要なパッケージを削除し、ロックファイルと完全に一致させたい場合は --exact を使用します。

# 不要なパッケージを削除し、厳密に環境を同期
uv run --exact -- ruff .

--exact をつけることで、よりクリーンで予測可能な環境を維持できます。

uv run 徹底解説 (6/9): ワークスペースのサポート

uv は、モノレポ(複数のパッケージを単一のリポジトリで管理する構成)を効率的に扱うための「ワークスペース」機能をサポートしており、uv run もこれに対応しています。

ワークスペースは、リポジトリのルートにある pyproject.toml[tool.uv.workspace] テーブルで定義されます。

1. ワークスペース全体のパッケージをインストールして実行 (--all-packages)

デフォルトでは、uv run はカレントディレクトリのパッケージ(またはルートパッケージ)のみをインストールします。ワークスペース内の全てのパッケージを仮想環境にインストールしてからコマンドを実行したい場合は --all-packages を使用します。

# ワークスペース内の全てのパッケージをインストールしてから pytest を実行
uv run --all-packages -- pytest

これにより、パッケージ間の依存関係を含む統合テストなどを簡単に行うことができます。

2. 特定のパッケージのコンテキストで実行 (--package)

ワークスペース内の特定のパッケージのコンテキストでコマンドを実行したい場合は --package <package-name> を使用します。

例えば、packages/package-a にいるが、package-b のスクリプトを実行したい、といったケースです。

# `package-b` のコンテキストでコマンドを実行
uv run --package package-b -- python -m package_b.cli

このコマンドは、package-b とその依存関係が正しくインストールされた環境で実行されます。これにより、ワークスペースのルートや他のパッケージのディレクトリからでも、特定のパッケージにスコープを絞ったタスクを実行できます。

ワークスペースと依存関係グループ

--group--extra といったオプションは、--all-packages--package と組み合わせて使用できます。

# 全てのパッケージについて、"test" グループの依存関係を含める
uv run --all-packages --group test -- pytest

# `package-a` の "s3" エクストラを含める
uv run --package package-a --extra s3 -- python ...

--all-packages と共に使われた場合、指定された extras や groups はワークスペースの全てのメンバーに適用されます。

uv のワークスペース機能は、複雑なモノレポ構造を持つプロジェクトの開発効率を大幅に向上させます。uv run のこれらのオプションは、その強力な機能を手軽に利用するためのインターフェースを提供します。

uv run 徹底解説 (7/9): Pythonインタープリタの選択

uv run は、コマンドの実行に使用するPythonインタープリタを柔軟に指定する機能を持っています。

1. Pythonバージョンの指定 (-p, --python)

-p または --python オプションを使うことで、使用するPythonのバージョンやパスを明示的に指定できます。

# Python 3.12 を使って実行
uv run -p 3.12 -- python --version

# 特定のメジャーバージョンを指定
uv run -p 3 -- python --version

# インタープリタへのフルパスを指定
uv run -p /usr/bin/python3.11 -- python --version

uv は指定された要求に合うPythonインタープリタを探します。もし uv が管理するPython(uv python install でインストールしたもの)があればそれを優先的に使用し、なければシステムにインストールされているPythonを探します。

この機能は、プロジェクトで特定のPythonバージョンが要求される場合(pyproject.tomlrequires-python)や、複数のPythonバージョンでテストを行いたい場合に非常に強力です。

2. uv管理下のPythonの利用制御

  • --managed-python: uvが管理するPythonのみを使用するように強制します。システムPythonは使用されません。
  • --no-managed-python: uvが管理するPythonを無効にし、システムのPythonのみを探すようにします。

3. Pythonの自動ダウンロード

要求されたバージョンのPythonが見つからない場合、uv はデフォルトでそれを自動的にダウンロードしようとします。この挙動を無効にするには --no-python-downloads フラグを使用します。

# Pythonの自動ダウンロードを無効にする
uv run -p 3.13 --no-python-downloads -- ...

4. .python-version ファイルとの連携

pyproject.toml がないディレクトリでも、.python-version ファイルが存在すれば、uv はそのファイルに記述されたバージョンを尊重します。これにより、pyenv などのツールと連携したワークフローを構築できます。

実行の優先順位

uv がどのPythonインタープリタを使用するかは、以下の優先順位で決定されます。

  1. -p/--python オプションでの明示的な指定。
  2. プロジェクトの仮想環境(例: .venv)内のインタープリタ。
  3. .python-version ファイルでの指定。
  4. pyproject.tomlproject.requires-python の指定。
  5. uv が管理するPython。
  6. システムPATH上のPython。

この柔軟なインタープリタ解決機構により、uv run は様々な開発環境やCI/CD環境にスムーズに対応できます。

uv run 徹底解説 (8/9): 高度なトピックとスクリプト実行

uv run には、より高度なユースケースやスクリプト実行を便利にするための機能が備わっています。

1. PEP 723 - スクリプト内での依存関係メタデータ

PEP 723 は、単一ファイルのPythonスクリプト内に、piprequirements.txt のような形式で依存関係を直接記述するための標準仕様です。uv run はこの仕様をネイティブでサポートしています。

my_script.py:

# /// script
# requires-python = ">=3.10"
# dependencies = [
#   "requests",
#   "rich",
# ]
# ///

import requests
from rich.pretty import pprint

response = requests.get("https://httpbin.org/json")
pprint(response.json())

このスクリプトを uv run で実行すると、uv はヘッダーブロックを解析し、requestsrich を一時的な隔離環境にインストールしてからスクリプトを実行します。

uv run my_script.py

これにより、スクリプトファイル単体で実行環境が完結するため、共有や配布が非常に簡単になります。

2. スクリプトとしての実行の強制 (--script)

通常、uv.py 拡張子を持つファイルなどをスクリプトとして扱います。拡張子に関わらず、任意のファイル(例えば、拡張子なしの実行ファイル)をPEP 723スクリプトとして解析させたい場合は --script フラグを使います。

# `my_tool` という名前のファイルをPythonスクリプトとして実行
uv run --script my_tool

3. シェバン (Shebang) の問題

スクリプトの1行目に #!/usr/bin/env python のようなシェバンが書かれている場合、スクリプトを直接実行 (./my_script.py) すると、システムのPythonが使われてしまい、uv の仮想環境が利用されません。

これを解決するのが uv run です。

# uvが管理する環境でスクリプトを実行
uv run ./my_script.py

uv run を使うことで、シェバンの記述に関わらず、常に uv が準備した正しいPython環境でスクリプトが実行されることが保証されます。

再帰呼び出しの検出:
もしシェバンで #!/usr/bin/env uv run のように uv run を呼び出している場合、無限再帰に陥る可能性があります。uv はこれを検出し、エラーメッセージを表示します。この場合、#!/usr/bin/env -S uv run --script のように --script フラグをシェバンに追加する必要があるかもしれません。

4. WindowsでのGUIスクリプト (--gui-script)

Windowsには、コンソールウィンドウを開かずにPythonスクリプトを実行するための pythonw.exe があります。--gui-script フラグは、pythonw.exe を使ってスクリプトを実行します。これは、GUIアプリケーションを作成する際に便利です。

# GUIスクリプトとして実行 (Windowsのみ)
uv run --gui-script my_gui_app.py

これらの高度な機能により、uv run は単純なコマンド実行から、ポータブルなスクリプト配布、OS固有の要件への対応まで、幅広いニーズに応えることができます。

uv run 徹底解説 (9/9): まとめとベストプラクティス

このシリーズでは、uv run の多岐にわたる機能について解説してきました。uv run は単なるコマンド実行ツールではなく、Python開発における環境管理と再現性を劇的に向上させるための、洗練されたインターフェースです。

機能のまとめ

  • 基本: コマンド、ローカル/リモートのPythonスクリプト、モジュールを簡単に実行できます。
  • プロジェクト連携: pyproject.toml を自動で認識し、依存関係を管理された仮想環境でコマンドを実行します。
  • 一時的な環境: --with--with-requirements を使うことで、プロジェクトを汚さずにツールやライブラリを一時的に利用できます。
  • 環境制御: --isolated でクリーンな環境を、--active で現在アクティブな環境を、-p で特定のPythonバージョンを選択できます。
  • 再現性の確保: --locked--frozen を使うことで、uv.lock に基づいた厳密な依存関係管理が可能です。
  • ワークスペース: --all-packages--package で、モノレポ内の複数パッケージを横断した操作を簡単に行えます。
  • 高度なスクリプティング: PEP 723をサポートし、スクリプト単体での依存関係定義と実行を実現します。

ベストプラクティス

  1. 日常的なタスクに uv run を使う: python, pip, pytest, ruff などのコマンドを実行する際は、直接呼び出す代わりに uv run を経由する習慣をつけましょう。これにより、常に正しい仮想環境と依存関係で実行されることが保証されます。

    # 良くない例
    # pytest
    
    # 良い例
    uv run pytest
    
  2. ツールは --with で実行する: ruff, black, mypy といったリンターやフォーマッター、その他のユーティリティは、プロジェクトの依存関係に含めず、--with を使って実行するのがクリーンです。

    uv run --with ruff -- ruff format .
    
  3. CIでは --locked を使う: 継続的インテグレーション(CI)のパイプラインでは uv run --locked を使用して、意図しない依存関係の更新を防ぎ、ビルドの再現性を高めましょう。

  4. スクリプト共有にはPEP 723を活用する: ちょっとしたユーティリティスクリプトをチームで共有する際は、PEP 723形式で依存関係をファイル内に記述すると便利です。受け取った側は uv run <script> を実行するだけで、環境構築の手間なくスクリプトを試せます。

  5. エイリアスを活用する: よく使うコマンドにはシェルのエイリアスを設定すると、さらに効率が上がります。

    # 例: ~/.bashrc や ~/.zshrc に
    alias ur="uv run"
    alias urw="uv run --with"
    

uv run をマスターすることで、あなたのPython開発体験はよりスムーズで、信頼性の高いものになるでしょう。uv の他のコマンド(uv pip, uv venv, uv python など)と組み合わせることで、Pythonのパッケージングと環境管理に関するほぼ全ての問題を、高速かつモダンな方法で解決できます。

Discussion