uvを使ってPython環境を構築する

# 概要
uvが便利と薦められたので使ってみた。

https://github.com/astral-sh/uv

# 環境
- macOS 14.7.1

# インストール
[Installation document](https://docs.astral.sh/uv/getting-started/installation/)に従ってインストールする。
```shell-session:
% curl -LsSf https://astral.sh/uv/install.sh | sh
% uv --version
uv 0.4.29 (85f9a0d0e 2024-10-30)
```
```shell-session:
% echo 'eval "$(uv generate-shell-completion zsh)"' >> ~/.zshrc  # 補完スクリプト
```
下記を実行してuvをアップデートする。
```shell-session:
% uv self update
info: Checking for updates...
success: Upgraded uv from v0.4.29 to v0.5.1! https://github.com/astral-sh/uv/releases/tag/0.5.1
% uv --version                                                 
uv 0.5.1 (f399a5271 2024-11-08)
```

# Python管理
## list
インストールされているpythonを確認する。
```shell-session:
% uv python list 
cpython-3.13.0+freethreaded-macos-aarch64-none    <download available>
cpython-3.13.0-macos-aarch64-none                 /opt/homebrew/opt/python@3.13/bin/python3.13 -> ../Frameworks/Python.framework/Versions/3.13/bin/python3.13
cpython-3.12.7-macos-aarch64-none                 /opt/homebrew/opt/python@3.12/bin/python3.12 -> ../Frameworks/Python.framework/Versions/3.12/bin/python3.12
cpython-3.12.7-macos-aarch64-none                 <download available>
cpython-3.11.10-macos-aarch64-none                <download available>
cpython-3.10.15-macos-aarch64-none                <download available>
cpython-3.9.20-macos-aarch64-none                 <download available>
cpython-3.9.6-macos-aarch64-none                  /Applications/Xcode.app/Contents/Developer/usr/bin/python3 -> ../../Library/Frameworks/Python3.framework/Versions/3.9/bin/python3
cpython-3.8.20-macos-aarch64-none                 <download available>
pypy-3.10.14-macos-aarch64-none                   <download available>
pypy-3.9.19-macos-aarch64-none                    <download available>
pypy-3.8.16-macos-aarch64-none                    <download available>
```

## install
pythonをインストールする。ここでは3.12をインストールした。
```shell-session:
% uv python install 3.12
Installed Python 3.12.7 in 2.04s
 + cpython-3.12.7-macos-aarch64-none
```
3.12がインストールされているかを確認する。
```shell-session:
% uv python list
cpython-3.13.0+freethreaded-macos-aarch64-none    <download available>
cpython-3.13.0-macos-aarch64-none                 /opt/homebrew/opt/python@3.13/bin/python3.13 -> ../Frameworks/Python.framework/Versions/3.13/bin/python3.13
cpython-3.12.7-macos-aarch64-none                 /opt/homebrew/opt/python@3.12/bin/python3.12 -> ../Frameworks/Python.framework/Versions/3.12/bin/python3.12
cpython-3.12.7-macos-aarch64-none                 /Users/foo/.local/share/uv/python/cpython-3.12.7-macos-aarch64-none/bin/python3.12
cpython-3.11.10-macos-aarch64-none                <download available>
cpython-3.10.15-macos-aarch64-none                <download available>
cpython-3.9.20-macos-aarch64-none                 <download available>
cpython-3.9.6-macos-aarch64-none                  /Applications/Xcode.app/Contents/Developer/usr/bin/python3 -> ../../Library/Frameworks/Python3.framework/Versions/3.9/bin/python3
cpython-3.8.20-macos-aarch64-none                 <download available>
pypy-3.10.14-macos-aarch64-none                   <download available>
pypy-3.9.19-macos-aarch64-none                    <download available>
pypy-3.8.16-macos-aarch64-none                    <download available>
```

# プロジェクト管理
## init
プロジェクトを新規作成する。
```shell-session:
% uv init uv-sample-project  # フォルダを作成してプロジェクト作成
Initialized project `uv-sample-project` at `/Users/foo/uv-sample-project`
```
作成したフォルダ構成は下記のようになる。
```shell-session:
% cd uv-sample-project
% tree -a
.
├── .git
│   ├── HEAD
│   ├── config
│   ├── description
│   ├── hooks
│   │   ├── applypatch-msg.sample
│   │   ├── commit-msg.sample
│   │   ├── fsmonitor-watchman.sample
│   │   ├── post-update.sample
│   │   ├── pre-applypatch.sample
│   │   ├── pre-commit.sample
│   │   ├── pre-merge-commit.sample
│   │   ├── pre-push.sample
│   │   ├── pre-rebase.sample
│   │   ├── pre-receive.sample
│   │   ├── prepare-commit-msg.sample
│   │   ├── push-to-checkout.sample
│   │   ├── sendemail-validate.sample
│   │   └── update.sample
│   ├── info
│   │   └── exclude
│   ├── objects
│   │   ├── info
│   │   └── pack
│   └── refs
│       ├── heads
│       └── tags
├── .gitignore
├── .python-version
├── README.md
├── hello.py
└── pyproject.toml
```
プロジェクトの情報を見てみる。
```shell-session:
% cat .python-version
3.12
```
```toml:pyproject.toml
[project]
name = "uv-sample-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
```
Gitリポジトリの状態を見てみる。
```shell:
% git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.gitignore
	.python-version
	README.md
	hello.py
	pyproject.toml
	uv.lock

nothing added to commit but untracked files present (use "git add" to track)
```
生成された`.gitignore`は下記内容になっている。
```config:.gitignore
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv
```

## add
パッケージを追加してみる。
```shell-session:
% uv add ruff  # ruffパッケージを追加
Using CPython 3.12.7
Creating virtual environment at: .venv
Resolved 2 packages in 376ms
Prepared 1 package in 628ms
Installed 1 package in 2ms
 + ruff==0.7.3
```
```shell-session:
% uv pip list
Package Version
------- -------
ruff    0.7.3
```
`ruff`パッケージ追加前後の`pyproject.toml`の差分
```diff_toml:
--- pyproject.toml.old	2024-11-09 18:46:23
+++ pyproject.toml	2024-11-09 18:47:49
@@ -4,4 +4,6 @@
 description = "Add your description here"
 readme = "README.md"
 requires-python = ">=3.12"
-dependencies = []
+dependencies = [
+    "ruff>=0.7.3",
+]
```

## remove
インストールしたパッケージを削除してみる。
```shell-session:
% uv remove ruff  # ruffパッケージを削除
Resolved 1 package in 22ms
Audited in 0.32ms
```
`ruff`パッケージ削除前後の`pyproject.toml`の差分
```diff_toml:
--- pyproject.toml.old	2024-11-09 19:17:36
+++ pyproject.toml	2024-11-09 19:17:46
@@ -4,6 +4,4 @@
 description = "Add your description here"
 readme = "README.md"
 requires-python = ">=3.12"
-dependencies = [
-    "ruff>=0.7.3",
-]
+dependencies = []
```

## sync
パッケージなどのpython環境を同期する。
```shell-session:
% uv sync
Resolved 1 package in 14ms
Audited in 0.29ms
```
生成された`uv.lock`の内容
```config:uv.lock
version = 1
requires-python = ">=3.12"

[[package]]
name = "uv-sample-project"
version = "0.1.0"
source = { virtual = "." }
```

## run
作成したPython環境で実行する。
```python:hello.py
def main():
    print("Hello from uv-sample-project!")

if __name__ == "__main__":
    main()
```
```shell-session:
% uv run hello.py
Hello from uv-sample-project!
```
仮想環境を有効化して実行する。
```shell-session:
% source .venv/bin/activate
(uv-sample-project) % python --version
Python 3.12.7
(uv-sample-project) % python hello.py
Hello from uv-sample-project!
```

## tool
コマンドラインツールを管理する。
```shell-session:
% uv tool dir  # develop環境で使用するtoolの保存先
/Users/foo/.local/share/uv/tools
```
```shell-session:
% uv tool install ruff  # toolとしてruffをインストール
Resolved 1 package in 169ms
Installed 1 package in 4ms
 + ruff==0.7.3
Installed 1 executable: ruff
warning: `/Users/foo/.local/bin` is not on your PATH. To use installed tools, run `export PATH="/Users/foo/.local/bin:$PATH"` or `uv tool update-zsh`.
```
```shell-session:
% uv tool list  # インストールされているツールを列挙
ruff v0.7.3
- ruff
```
`ruff`パッケージ追加前後の`pyproject.toml`の差分
```diff_toml:
--- pyproject.toml.old	2024-11-09 20:25:16
+++ pyproject.toml	2024-11-09 20:26:48
@@ -5,3 +5,6 @@
 readme = "README.md"
 requires-python = ">=3.12"
 dependencies = []
+
+[dependency-groups]
+dev = []
```
インストールした`ruff`ツールを実行する。
```shell-session:
% uv tool run ruff check hello.py  # ruffを実行
All checks passed!
```

# おわり
まだ簡単に使い方を見てみただけなので、きちんと使い方を覚えたい。