Python Monthly Topics

さらなる進化を遂げた「uv」新機能

福田@JunyaFffです。本連載Python Monthly Topicsで2024年3月に公開したRust製のPythonパッケージ管理ツール「uv」を使ってみよう で紹介した「uv」が、さらなる進化を遂げました。今回は、その新機能を紹介します。

はじめに

Astral社が開発するRust製の高速なpipの代替ツールuvがパッケージマネージャーとして8月にアップデートされました。pipの代替ツールとしてだけでなく、Pythonプロジェクト、コマンドラインツール、単一ファイルスクリプトさらにPython自体を管理できるようになりました。uvは、pippipxvenvpoetrypyenvのような機能を包括していると言え、そしてそのすべてが非常に高速に動作します。

本記事では、アップデートした「uv」の新機能を中心に紹介します。 基本的な使い方は Rust製のPythonパッケージ管理ツール「uv」を使ってみよう を合わせてお読みください。

uvの公式ドキュメント、GitHubは以下になります。

インストールと自動補完の設定前回の記事でも紹介していますが、まずは、インストールと自動補完の設定を行いましょう。Linux、macOS、WIndowsに対応しています。

Linux or macOS
$ curl -LsSf https://astral.sh/uv/install.sh | sh
Windows
$ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

上記でインストールした場合には、self updateコマンドにより最新版への更新ができます。

$ uv self update

また、HomebrewやCargo、pipx、Wingetでもインストールができます。

Homebrew
$ brew install uv
Cargo
$ cargo install --git https://github.com/astral-sh/uv uv
pipx
$ pipx install uv
Winget
$ winget install --id=astral-sh.uv  -e

次にコマンドの入力補完を設定します。zshの場合は以下を実行してください。

$ echo 'eval "$(uv generate-shell-completion zsh)"' >> ~/.zshrc
$ source ~/.zshrc

そのほかのシェルについては、shell-autocompletionを参照してください。

なお、本記事で紹介するバージョンは0.4.13です。

$ uv --version
uv 0.4.13 (Homebrew 2024-09-19)

機能紹介

はじめにuvコマンドを実行してみましょう。ヘルプが表示され、多くの機能が備わっていることが分かります。前回の記事ではvenvpipだけが主な機能でしたが、今回はさらに多くのコマンドが追加されています。

本記事ではこの中から、いくつかの機能を紹介します。

$ uv
An extremely fast Python package manager.

Usage: uv [OPTIONS] <COMMAND>

Commands:
  run      Run a command or script
  init     Create a new project
  add      Add dependencies to the project
  remove   Remove dependencies from the project
  sync     Update the project's environment
  lock     Update the project's lockfile
  export   Export the project's lockfile to an alternate format
  tree     Display the project's dependency tree
  tool     Run and install commands provided by Python packages
  python   Manage Python versions and installations
  pip      Manage Python packages with a pip-compatible interface
  venv     Create a virtual environment
  build    Build Python packages into source distributions and wheels
  cache    Manage uv's cache
  version  Display uv's version
  help     Display documentation for a command
...

それぞれのサブコマンドについてもヘルプが表示されます。

$ uv run
Run a command or script

Usage: uv run [OPTIONS] <COMMAND>
...

また、入力補完を設定していると、コマンドの入力途中でタブキーを押下することでサブコマンドやオプションが参照できます。

$ uv python 
dir        -- Show the uv Python installation directory
find       -- Search for a Python installation
install    -- Download and install Python versions
list       -- List the available Python installations
pin        -- Pin to a specific Python version
uninstall  -- Uninstall Python versions

プロジェクト管理と依存関係の追加⁠管理

プロジェクト管理機能について紹介します。uvを利用し、pyproject.tomlで外部ライブラリの依存関係やPythonのバージョンなど、プロジェクトの情報を一元管理できます。またuv.lockというロックファイルにより依存関係を厳密に管理し、クロスプラットフォームでの再現性を向上させます。

公式ドキュメント
概要:Working on projects | uv
詳細:Projects | uv

以下のコマンドを利用し、プロジェクト管理の概要を説明します。

  • uv init:プロジェクトの初期化
  • uv run:Pythonファイルの実行
  • 依存関係
    • uv add:追加
    • uv remove:削除
    • uv sync:同期

まず、uv initコマンドでプロジェクト作成します。pyproject.tomlには、プロジェクトの最小限の情報が記述されます。

$ uv init hello-world
Pythonバージョンを指定する場合
$ uv init hello-world --python 3.12

ディレクトリ構造とpyproject.tomlの内容は以下のようになります。

$ ls hello-world
README.md       hello.py        pyproject.toml

$ cd hello-world
$ cat pyproject.toml
[project]
name = "hello-world"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

uv initコマンドには、プロジェクトの目的ごとに指定するオプションがあります。必要に応じて指定してください。

  • uv init --app project_name:アプリケーション全般(デフォルト)
  • uv init --lib project_name:配布するライブラリ
  • uv init --package project_name:配布するコマンドラインツール

続いて、作成されたPythonファイルを実行してみましょう。uv runコマンドでプロジェクトのPythonバージョンで実行できます。また仮想環境.venvも自動で作成されます。uv runコマンドの詳細は本記事の後半で紹介します。

$ uv run hello.py
Using Python 3.12.6
Creating virtual environment at: .venv
Hello from hello-world!

依存関係の追加には、uv addコマンドを使用します。uv addは、pyproject.tomlに依存関係を追加し、仮想環境にインストールも行います。仮想環境がない場合には自動で作成されます。

ここでは、httpxライブラリを追加してみます。

$ uv add httpx
Resolved 8 packages in 3ms
Installed 7 packages in 6ms
 + anyio==4.6.0
 + certifi==2024.8.30
 + h11==0.14.0
 + httpcore==1.0.5
 + httpx==0.27.2
 + idna==3.10
 + sniffio==1.3.1

# pyproject.toml に依存関係が追加されていることを確認
$ cat pyproject.toml 
[project]
name = "hello-world"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "httpx>=0.27.2",
]

除外する場合には、uv removeコマンドを使用します。uv removeは、pyproject.tomlから依存関係を削除し、仮想環境からも削除します。

$ uv remove httpx
uv remove httpx
Resolved 1 package in 2ms
Uninstalled 7 packages in 15ms
 - anyio==4.6.0
 - certifi==2024.8.30
 - h11==0.14.0
 - httpcore==1.0.5
 - httpx==0.27.2
 - idna==3.10
 - sniffio==1.3.1

# pyproject.toml から依存関係が削除されていることを確認
$ cat pyproject.toml
[project]
name = "hello-world"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

手動でpyproject.tomlを更新した場合や、クローンしてきたリポジトリがuvで管理されている場合には、uv syncコマンドを実行し環境を同期しましょう。uv.lockファイルと仮想環境を更新します。

$ uv sync
Using Python 3.12.6
Creating virtual environment at: .venv
Resolved 1 package in 10ms
Audited in 0.06ms

なお、uvで管理しているプロジェクトの場合、uv pip installではなく、uv adduv syncを使うことが公式ドキュメントで推奨されていますProjects | uv⁠。

新しく作成したプロジェクトの基本的な利用方法を紹介しました。プロジェクト全体をuvで管理することで、依存関係の管理やPythonのバージョン管理が容易になります。プロジェクト全体の管理が難しい場合には、仮想環境やpipの代替として、あるいは後述するコマンドラインツールやスクリプトの実行、Pythonバージョンの管理だけを行うのにフォーカスしても良いでしょう。

また、既存のプロジェクトをuvで管理することもできます。例として公式ドキュメントにFastAPIのプロジェクトをuvで管理する方法が紹介されています。

uvでのPythonの管理

uv python installコマンドでPythonをインストールできます。マイナーバージョンの指定も可能です。uvではpython-build-standaloneプロジェクトで公開されているPythonをインストールします[1]

公式ドキュメント
概要:Installing Python | uv
詳細:Python versions | uv
$ uv python install 3.13
Searching for Python versions matching: Python 3.13
Installed Python 3.13.0rc2 in 1.91s
 + cpython-3.13.0rc2-macos-aarch64-none

# マイナーバージョン指定も可能
$ uv python install 3.12.6
Searching for Python versions matching: Python 3.12.6
Installed Python 3.12.6 in 1.50s
 + cpython-3.12.6-macos-aarch64-none

# 複数バージョンのインストールも可能
$ uv python install 3.9 3.10 3.11

ただ、現時点では、uvでPythonをインストールしても、ターミナルでpythonコマンドを実行しグローバルに利用することはできません。この機能のサポートは将来のリリースで予定されてるようです。uv runを使用するか、uv venvで仮想環境を作成して利用しましょう。

$ uv venv --python 3.12.6

なお、すでにインストール済みのPythonバージョンを指定した場合、uvはそのバージョンを再インストールしません。

uv python listコマンドで、インストール済みのPythonのバージョンとそのPathの一覧を確認できます。

$ uv python list
cpython-3.13.0rc2-macos-aarch64-none    /Users/jrfk/.local/share/uv/python/cpython-3.13.0rc2-macos-aarch64-none/bin/python3 -> python3.13
cpython-3.13.0b1-macos-aarch64-none     /Library/Frameworks/Python.framework/Versions/3.13/bin/python3 -> python3.13
cpython-3.12.6-macos-aarch64-none       /Users/jrfk/.local/share/uv/python/cpython-3.12.6-macos-aarch64-none/bin/python3 -> python3.12
cpython-3.12.4-macos-aarch64-none       /opt/homebrew/opt/[email protected]/bin/python3.12 -> ../Frameworks/Python.framework/Versions/3.12/bin/python3.12
...

$ uv venv --python 3.12.4
Using Python 3.12.4 interpreter at: /opt/homebrew/opt/[email protected]/bin/python3.12
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate

uv tool runによるコマンドラインツールの実行

ruffmypyなどのPythonコマンドラインツールを明示的にインストールすることなく利用できる機能です。 プロジェクトの仮想環境とは別に、一時的な仮想環境にインストールされるため、プロジェクト内のパッケージとは依存関係なく利用できます。

公式ドキュメント
概要:Using tools | uv
詳細:Tools | uv
$ uv tool run mypy

# uv tool run のエイリアスは uvx
$ uvx mypy

もちろん、明示的にインストールしシステム全体で利用も可能です。

$ uv tool install mypy
$ mypy

uv runでのスクリプトの実行

uv runコマンドでPythonのスクリプトを実行できます。Pythonのバージョンを指定した実行が可能です。 また、依存関係のあるスクリプトも容易に実行できます。

公式ドキュメント
概要:Running scripts | uv
詳細:uv run | Commands | uv
hello.py
print("Hello world")
$ uv run hello.py
Hello world

Pythonのバージョンを指定して実行するには以下のようにします。

version.py
import sys

print(".".join(map(str, sys.version_info[:3])))
$ uv run --python 3.10 version.py
3.10.10

$ uv run --python 3.12 version.py
3.12.6

次に依存関係のあるスクリプトを実行してみましょう。以下のスクリプトでは、richライブラリを使用しています[2]

dep.py
import time
from rich.progress import track

for i in track(range(20), description="For example:"):
    time.sleep(0.05)

そのまま実行しようとするとエラーになります。

$ uv run dep.py
Traceback (most recent call last):
  File "dep.py", line 2, in <module>
    from rich.progress import track
ModuleNotFoundError: No module named 'rich'

--withオプションで依存関係のあるライブラリ(この場合richを指定することで、依存関係を解決して実行できます。

$ uv run --with rich dep.py
For example: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:01

さらに、スクリプト内に依存関係を記述することで、依存関係を自動で解決できます。依存関係の追加は、uv add --scriptコマンドで行います。 インラインでの定義は、PEP 723 – Inline script metadata | peps.python.orgによって提案されています。

$ uv add --script dep.py "rich" 
Updated `dep.py`

$ cat dep.py 
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "rich",
# ]
# ///
import time
from rich.progress import track

for i in track(range(20), description="For example:"):
    time.sleep(0.05)

スクリプト内に依存関係を追加することで、依存関係を解決し実行できます。スクリプトを配布する際に、requirements.txtを用意して…… といった手間が省けます。

$ uv run dep.py
Reading inline script metadata from: dep.py
For example: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:01

また、スクリプトの再現性の向上のため、タイムスタンプの指定をexclude-newerオプションで行うことができます。 いくつかの複雑な依存関係を持つスクリプトにして、実行してみましょう。Pythonバージョンを指定することで、より再現性を向上させます。

asksanyioライブラリを使用して、非同期でポケモンの名前を取得するスクリプトです。コメントにPythonバージョンと依存関係、exclude-newerを指定しています。

fetch.py
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "asks",
#     "anyio",
# ]
# [tool.uv]
# exclude-newer = "2024-09-22T00:00:00Z"
# ///
import importlib.metadata
from time import time

import anyio
import asks

URL = "https://pokeapi.co/api/v2/pokemon/"

async def fetch_pokemon(num: int):
    r = await asks.get(f"{URL}{num}")
    print(r.json()["name"])

async def main_poke():
    start = time()
    async with anyio.create_task_group() as tg:
        for num in range(1, 151):
            tg.start_soon(fetch_pokemon, num)
    print(f"Time: {time() - start}s")

if __name__ == "__main__":
    anyio.run(main_poke)
    print(f"asks={importlib.metadata.version("asks")}")  # ライブラリのバージョンを表示
    print(f"anyio={importlib.metadata.version("anyio")}")

このスクリプトを実行すると、依存関係の解決とタイムスタンプの指定が行われます。タイムスタンプが指定されている場合、タイムスタンプより新しいバージョンはインストールされなくなります。⁠あの時は動いていたのに...」ということを回避しやすくなります。

$ uv run fetch.py
Reading inline script metadata from: fetch.py
ivysaur
...
kingler
Time: 1.3258390426635742s
asks=3.0.0
anyio=3.7.1

まとめ

8月の記事バージョン1.0リリース記念:Rust製データフレームライブラリ、Polarsの進化した機能を試す | gihyo.jpに続き、ライブラリのアップデート情報をお届けしました。

わたし自身、2024年5月に別のプロジェクト管理ツール「Hatch」を紹介しましたが、⁠uv」の進化を見ていると、Pythonの開発環境がどんどん進化し管理しやすくなっていると感じました。

今回は紹介しませんでしたが、uv pipも強化されており、個人的にはuv pip compileでプラットフォームごとの出力やPythonバージョンの指定が有効になった点が個人的に少し嬉しかったです。また、toxを利用している場合は、tox-dev/tox-uvを使うことで影響範囲を最小限に抑えながらCIを高速化できますので、ぜひ試してみてください。

高速なPython linterの「Ruff」から始まった「high-performance developer tools」の提供をミッションに掲げるAstral社は、その信念にあるように「We believe that a great tool can have an outsized impact.」の実現を実感できるツールを提供しています。

ぜひ、みなさまも「uv」をお試しください。

おすすめ記事

記事・ニュース一覧