Pythonのパッケージ管理ツール「uv」を試す
GitHubレポジトリ
uv
Rustで書かれた、非常に高速なPythonパッケージおよびプロジェクトマネージャー。
ハイライト
- 🚀
pip
、pip-tools
、pipx
、poetry
、pyenv
、virtualenv
など、複数のツールを置き換える単一のツール。- ⚡️
pip
よりも10~100倍高速。- 🐍 Pythonのバージョンをインストールおよび管理。
- 🛠️ Pythonアプリケーションを実行およびインストール。
- ❇️ インライン依存メタデータのサポートにより、単一ファイルのスクリプトを実行。
- 🗂️ ユニバーサルロックファイルによる包括的なプロジェクト管理機能を提供。
- 🔩 馴染みのある CLI によるパフォーマンス向上のための pip 互換インターフェースを搭載。
- 🏢 拡張可能なプロジェクトのための Cargo スタイルのワークスペースをサポート。
- 💾 依存関係の重複排除のためのグローバルキャッシュにより、ディスクスペースを効率的に使用。
- ⏬
curl
またはpip
を使用して、Rust や Python なしでインストール可能。- 🖥️ macOS、Linux、Windowsをサポート。
ドキュメント
Getting Startedに従って進める。今回はローカルのMac上で。
インストール
uvのインストール方法は、インストールスクリプト、Pythonパッケージ、Cargoなど複数あるが、MacなのでHomebrewで。
$ brew install uv
バージョン確認。なお、uvxというのは、ruffやmypyなどの「ツール」を実行するための専用のコマンドらしい。
$ uv --version
uv 0.4.19 (Homebrew 2024-10-07)
$ uvx --version
uv-tool-uvx 0.4.19 (Homebrew 2024-10-07)
$ uv --help
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
publish Upload distributions to an index
cache Manage uv's cache
version Display uv's version
help Display documentation for a command
Cache options:
-n, --no-cache Avoid reading from or writing to the cache, instead using a temporary directory for the
duration of the operation [env: UV_NO_CACHE=]
--cache-dir <CACHE_DIR> Path to the cache directory [env: UV_CACHE_DIR=]
Python options:
--python-preference <PYTHON_PREFERENCE> Whether to prefer uv-managed or system Python installations [env:
UV_PYTHON_PREFERENCE=] [possible values: only-managed, managed, system,
only-system]
--no-python-downloads Disable automatic downloads of Python. [env: "UV_PYTHON_DOWNLOADS=never"]
Global options:
-q, --quiet Do not print any output
-v, --verbose... Use verbose output
--color <COLOR_CHOICE> Control colors in output [default: auto] [possible values: auto, always, never]
--native-tls Whether to load TLS certificates from the platform's native certificate store [env:
UV_NATIVE_TLS=]
--offline Disable network access
--no-progress Hide all progress outputs
--directory <DIRECTORY> Change to the given directory prior to running the command
--project <PROJECT> Run the command within the given project directory
--config-file <CONFIG_FILE> The path to a `uv.toml` file to use for configuration [env: UV_CONFIG_FILE=]
--no-config Avoid discovering configuration files (`pyproject.toml`, `uv.toml`) [env:
UV_NO_CONFIG=]
-h, --help Display the concise help for this command
-V, --version Display the uv version
Use `uv help` for more details.
$ uvx --help
Run a command provided by a Python package.
Usage: uvx [OPTIONS] [COMMAND]
Options:
--from <FROM> Use the given package to provide the command
--with <WITH> Run with the given packages installed
--with-editable <WITH_EDITABLE> Run with the given packages installed as editables
--with-requirements <WITH_REQUIREMENTS> Run with all packages listed in the given `requirements.txt` files
--isolated Run the tool in an isolated virtual environment, ignoring any
already-installed tools
Index options:
-i, --index-url <INDEX_URL>
The URL of the Python package index (by default: <https://pypi.org/simple>) [env: UV_INDEX_URL=]
--extra-index-url <EXTRA_INDEX_URL>
Extra URLs of package indexes to use, in addition to `--index-url` [env: UV_EXTRA_INDEX_URL=]
-f, --find-links <FIND_LINKS>
Locations to search for candidate distributions, in addition to those found in the registry indexes [env:
UV_FIND_LINKS=]
--no-index
Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those provided via
`--find-links`
--index-strategy <INDEX_STRATEGY>
The strategy to use when resolving against multiple index URLs [env: UV_INDEX_STRATEGY=] [possible values:
first-index, unsafe-first-match, unsafe-best-match]
--keyring-provider <KEYRING_PROVIDER>
Attempt to use `keyring` for authentication for index URLs [env: UV_KEYRING_PROVIDER=] [possible values:
disabled, subprocess]
--allow-insecure-host <ALLOW_INSECURE_HOST>
Allow insecure connections to a host [env: UV_INSECURE_HOST=]
Resolver options:
-U, --upgrade Allow package upgrades, ignoring pinned versions in any existing output file.
Implies `--refresh`
-P, --upgrade-package <UPGRADE_PACKAGE> Allow upgrades for a specific package, ignoring pinned versions in any existing
output file. Implies `--refresh-package`
--resolution <RESOLUTION> The strategy to use when selecting between the different compatible versions
for a given package requirement [env: UV_RESOLUTION=] [possible values:
highest, lowest, lowest-direct]
--prerelease <PRERELEASE> The strategy to use when considering pre-release versions [env: UV_PRERELEASE=]
[possible values: disallow, allow, if-necessary, explicit,
if-necessary-or-explicit]
--exclude-newer <EXCLUDE_NEWER> Limit candidate packages to those that were uploaded prior to the given date
[env: UV_EXCLUDE_NEWER=]
--no-sources Ignore the `tool.uv.sources` table when resolving dependencies. Used to lock
against the standards-compliant, publishable package metadata, as opposed to
using any local or Git sources
Installer options:
--reinstall Reinstall all packages, regardless of whether they're already installed.
Implies `--refresh`
--reinstall-package <REINSTALL_PACKAGE> Reinstall a specific package, regardless of whether it's already installed.
Implies `--refresh-package`
--link-mode <LINK_MODE> The method to use when installing packages from the global cache [env:
UV_LINK_MODE=] [possible values: clone, copy, hardlink, symlink]
--compile-bytecode Compile Python files to bytecode after installation [env:
UV_COMPILE_BYTECODE=]
Build options:
-C, --config-setting <CONFIG_SETTING>
Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs
--no-build-isolation
Disable isolation when building source distributions [env: UV_NO_BUILD_ISOLATION=]
--no-build-isolation-package <NO_BUILD_ISOLATION_PACKAGE>
Disable isolation when building source distributions for a specific package
--no-build
Don't build source distributions
--no-build-package <NO_BUILD_PACKAGE>
Don't build source distributions for a specific package
--no-binary
Don't install pre-built wheels
--no-binary-package <NO_BINARY_PACKAGE>
Don't install pre-built wheels for a specific package
Cache options:
-n, --no-cache Avoid reading from or writing to the cache, instead using a temporary directory
for the duration of the operation [env: UV_NO_CACHE=]
--cache-dir <CACHE_DIR> Path to the cache directory [env: UV_CACHE_DIR=]
--refresh Refresh all cached data
--refresh-package <REFRESH_PACKAGE> Refresh cached data for a specific package
Python options:
-p, --python <PYTHON> The Python interpreter to use to build the run environment. [env:
UV_PYTHON=]
--python-preference <PYTHON_PREFERENCE> Whether to prefer uv-managed or system Python installations [env:
UV_PYTHON_PREFERENCE=] [possible values: only-managed, managed, system,
only-system]
--no-python-downloads Disable automatic downloads of Python. [env: "UV_PYTHON_DOWNLOADS=never"]
Global options:
-q, --quiet Do not print any output
-v, --verbose... Use verbose output
--color <COLOR_CHOICE> Control colors in output [default: auto] [possible values: auto, always, never]
--native-tls Whether to load TLS certificates from the platform's native certificate store [env:
UV_NATIVE_TLS=]
--offline Disable network access
--no-progress Hide all progress outputs
--directory <DIRECTORY> Change to the given directory prior to running the command
--project <PROJECT> Run the command within the given project directory
--config-file <CONFIG_FILE> The path to a `uv.toml` file to use for configuration [env: UV_CONFIG_FILE=]
--no-config Avoid discovering configuration files (`pyproject.toml`, `uv.toml`) [env:
UV_NO_CONFIG=]
-h, --help Display the concise help for this command
-V, --version Display the uv version
Use `uv help tool run` for more details.
シェルの補完を有効化しておく。
$ echo 'eval "$(uv generate-shell-completion bash)"' >> ~/.bash_profile
$ echo 'eval "$(uvx --generate-shell-completion bash)"' >> ~/.bash_profile
反映
$ source ~/.bash_profile
Pythonのインストール
まず、uvでpython本体のバージョン管理およびインストールもできるらしい。
なお、自分の環境では、グローバルなpythonはmiseによってインストールされているものを使っていて、pyenvを使っていたりもしてる。
$ which python
/Users/kun432/.local/share/mise/installs/python/3.12/bin/python
$ python --version
Python 3.12.6
uvでPythonをインストール。
$ uv python install
事前にインストールされているものなどを探しているようだが、新しいバージョンがインストールされた。
Searching for Python installations
Installed Python 3.12.7 in 2.77s
+ cpython-3.12.7-macos-aarch64-none
ただしPATHなどを変更していないので、シェルではまだmiseでインストールしたpythonになっている。
$ which python
/Users/kun432/.local/share/mise/installs/python/3.12/bin/python
uvでインストールしたpythonを実行するにはuv run
で実行。
$ uv run python --version
Python 3.12.7
別のバージョンをインストールしてみる。
$ uv python install 3.11
Searching for Python versions matching: Python 3.11
Installed Python 3.11.10 in 2.94s
+ cpython-3.11.10-macos-aarch64-none
uv run
でバージョン指定しつつ実行もできる
$ uv run --python 3.11 python --version
Python 3.11.10
uvでインストール可能・インストール済みのPythonの一覧を見てみる。
$ uv python list
cpython-3.13.0rc3-macos-aarch64-none <download available>
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 .local/share/uv/python/cpython-3.12.7-macos-aarch64-none/bin/python3 -> python3.12
cpython-3.12.6-macos-aarch64-none .local/share/mise/installs/python/3.12/bin/python3.12
cpython-3.12.6-macos-aarch64-none .local/share/mise/installs/python/3.12/bin/python3 -> python3.12
cpython-3.12.6-macos-aarch64-none .local/share/mise/installs/python/3.12/bin/python -> .local/share/mise/installs/python/3.12.6/bin/python3
cpython-3.11.10-macos-aarch64-none /opt/homebrew/opt/python@3.11/bin/python3.11 -> ../Frameworks/Python.framework/Versions/3.11/bin/python3.11
cpython-3.11.10-macos-aarch64-none .local/share/uv/python/cpython-3.11.10-macos-aarch64-none/bin/python3 -> python3.11
cpython-3.11.9-macos-aarch64-none .pyenv/versions/3.11.9/bin/python3.11
cpython-3.11.9-macos-aarch64-none .pyenv/versions/3.11.9/bin/python3 -> python3.11
cpython-3.11.9-macos-aarch64-none .pyenv/versions/3.11.9/bin/python -> python3.11
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 /Library/Developer/CommandLineTools/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>
もともとMacOSのシステムでインストール済みのもの、Homebrewでインストールされているもの、pyenvやmiseなど他の管理ツールでインストールしたものなどがすべて表示される。
Pythonスクリプトの実行
uvによるpythonスクリプトの実行には、上でも少し出てきたけど、uv run
を使う。
とりあえず作業ディレクトリを作成。
$ mkdir uv-test && cd uv-test
以下のスクリプトを作成
import sys
v = sys.version_info
print(f"Hello, Python-{v.major}.{v.minor}.{v.micro}!")
普通に実行するとシステムでパスが通っているpythonになる。
$ python example.py
Hello, I'm Python-3.12.6!
uv run
で実行すると、uvでインストールしたpythonで実行されているのがわかる。
$ uv run example.py
Hello, Python-3.12.7!
違うバージョンのpythonで実行する場合
$ uv run --python 3.11 example.py
Hello, Python-3.11.10!
コマンドライン引数の処理
import sys
v = sys.version_info
greeting = " ".join([a.capitalize() for a in sys.argv[1:]])
print(f"I'm Python-{v.major}.{v.minor}.{v.micro}! {greeting}!")
$ uv run example.py hello
I'm Python-3.12.7! Hello!
$ uv run example.py good morning
I'm Python-3.12.7! Good Morning!
パイプで渡す
$ echo 'print("こんにちは!")' | uv run -
こんにちは!
ヒアドキュメントで渡す
$ uv run - <<EOF
print("こんにちは!")
EOF
こんにちは!
依存パッケージが必要な場合。ドキュメントでは例としてrich
パッケージを使用している。
import time
from rich.progress import track
for i in track(range(20), description="例えば:"):
time.sleep(0.05)
なにも考えずに実行するとこうなる。
$ uv run example.py
Traceback (most recent call last):
File "/Users/kun432/work/uv-test/example.py", line 2, in <module>
from rich.progress import track
ModuleNotFoundError: No module named 'rich'
通常ならば仮想環境などを用意してパッケージインストールすることになるが、uvではオンデマンドな環境を作成して実行してくれる。この場合は--with パッケージ名
で明示的に指定する必要がある。
$ uv run --with rich example.py
Installed 4 packages in 9ms
例えば: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:01
パッケージがインストールされているのがわかる。このパッケージは何処にインストールされているのか?というと多分この辺なのだろう。
$ tree ~/.cache/uv
/Users/kun432/.cache/uv
├── CACHEDIR.TAG
├── archive-v0
│ ├── 4MadhXi-Ruh6C3BGzJxbq
│ │ ├── rich
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── _cell_widths.py
│ │ │ ├── _emoji_codes.py
(snip)
通常、プロジェクト内の場合はそちらでインストールするだろうし、もしくは仮想環境を用意することになるのが一般的だと思うが、この動きは少し認識しておく必要がありそう。
なお、キャッシュはuv cache clear
で全部消える。
$ uv cache clear
$ tree ~/.cache/uv
/Users/kun432/.cache/uv [error opening dir]
0 directories, 0 files
もう一つ、依存するパッケージを「インラインメタデータ」として定義するという方法がある。以下のようにuv add --script
で対象のスクリプトに依存するパッケージのメタデータを追記・更新ができる
$ uv add --script example.py 'rich'
Updated `example.py`
中身を見ると、Pythonバージョンや依存パッケージがコメントとして記載されている。これが「インラインメタデータ」。
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "rich",
# ]
# ///
import time
from rich.progress import track
for i in track(range(20), description="例えば:"):
time.sleep(0.05)
これで再度実行してみる。今度は--with
なしで
$ uv run example.py
Reading inline script metadata from: example.py
Installed 4 packages in 9ms
例えば: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:01
必要なパッケージが再度インストールされて実行されている。
ツールを使う
ruffやmypyなどコマンドを含むパッケージは「ツール」という特別な扱いになる。
例としてruff
を使う。ツールの実行はuvx
コマンドを使う。
$ uvx ruff
以下のようにインストールされて実行される。
Installed 1 package in 4ms
Ruff: An extremely fast Python linter and code formatter.
Usage: ruff [OPTIONS] <COMMAND>
(snip)
引数はそのまま渡せる。
$ uvx ruff check
All checks passed!
バージョン指定したり、コマンドとパッケージ名が異なる場合、GitHubレポジトリからインストールする場合などもオプションで使える様子だが、ここは割愛。
uvx
だと、なければ実行時にキャッシュにインストール、あればキャッシュから読み出されて実行されるが、恒久的にインストールすることもできる。
例えば先ほどuvx
系でruffが使えたが、パスには含まれていないので、以下のように直接は見えない。
$ which ruff
$
uv tool install
を使う。
$ uv tool install ruff
どうやら~/.local/bin
に入る模様。で、~/.local/bin
がPATHに含まれてないので更新するためのコマンドが出力される。
Resolved 1 package in 13ms
Installed 1 package in 2ms
+ ruff==0.6.9
Installed 1 executable: ruff
warning: `/Users/kun432/.local/bin` is not on your PATH. To use installed tools, run `export PATH="/Users/kun432/.local/bin:$PATH"` or `uv tool update-shell`.
$ uv tool update-shell
Updated configuration file: /Users/kun432/.bash_profile
Created configuration file: /Users/kun432/.bashrc
Restart your shell to apply changes
別シェルで確認してみると、パスが通っているので、uvxなしでも実行できるし、実体はシンボリックリンクになっていることがわかる。
$ which ruff
/Users/kun432/.local/bin/ruff
$ ruff --version
ruff 0.6.9
$ ls -lt /Users/kun432/.local/bin/ruff
lrwxr-xr-x@ 1 kun432 staff 49 10 9 11:59 /Users/kun432/.local/bin/ruff -> /Users/kun432/.local/share/uv/tools/ruff/bin/ruff
ツールのアップグレード。ツール個別でもできるし、全部まるっともできる。
$ uv tool upgrade ruff
Nothing to upgrade
$ uv tool upgrade --all
Nothing to upgrade
個人的にはちょっとここは検討が必要かなあ。こういうツールにバージョン管理はそれほど必要ないような気がしないでもないけども、複数人で共有するようなプロジェクトの場合はこれプロジェクト外の管理になっちゃう気がする。(まあプロジェクトはpyproject.tomlで管理するだろうし、あとはdevcontainer用意しとけばいいだけの話かもしれない)
プロジェクト
Pythonのプロジェクトを管理できる。
プロジェクトの作成
$ uv init hello-world
Initialized project `hello-world` at `/Users/kun432/work/hello-world`
もしくは先にディレクトリがある場合
$ mkdir hello-world2 && cd hello-world2
$ uv init
Initialized project `hello-world2`
プロジェクトのディレクトリ構成はこんな感じ。
$ tree -a -L 1
.
├── .git
├── .gitignore
├── .python-version
├── README.md
├── hello.py
└── pyproject.toml
2 directories, 5 files
[project]
name = "hello-world2"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
3.12
スクリプトを実行する場合はuv run
で。
$ uv run hello.py
pythonのバージョンが設定されて、仮想環境の作成も行われる。
Using CPython 3.12.7
Creating virtual environment at: .venv
Hello from hello-world2!
$ tree -a -L 1
.
├── .git
├── .gitignore
├── .python-version
├── .venv
├── README.md
├── hello.py
├── pyproject.toml
└── uv.lock
3 directories, 6 files
pyproject.tomlとは別にロックファイルが作成され、ここにパッケージのバージョンなどが記録される。
version = 1
requires-python = ">=3.12"
[[package]]
name = "hello-world2"
version = "0.1.0"
source = { virtual = "." }
プロジェクトで使用するパッケージを追加する場合はuv add
を使う。
$ uv add requests
Resolved 6 packages in 197ms
Prepared 5 packages in 126ms
Installed 5 packages in 5ms
+ certifi==2024.8.30
+ charset-normalizer==3.3.2
+ idna==3.10
+ requests==2.32.3
+ urllib3==2.2.3
pyproject.tomlとuv.lockが更新される。uv.lockには追加したパッケージに依存するパッケージのバージョンなども記録される。
[project]
name = "hello-world2"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"requests>=2.32.3",
]
version = 1
requires-python = ">=3.12"
[[package]]
name = "certifi"
version = "2024.8.30"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 },
]
[[package]]
name = "charset-normalizer"
version = "3.3.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 },
{ url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 },
{ url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 },
{ url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 },
{ url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 },
{ url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 },
{ url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 },
{ url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 },
{ url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 },
{ url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 },
{ url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 },
{ url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 },
{ url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 },
{ url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 },
{ url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 },
{ url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 },
]
[[package]]
name = "hello-world2"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "requests" },
]
[package.metadata]
requires-dist = [{ name = "requests", specifier = ">=2.32.3" }]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
]
[[package]]
name = "requests"
version = "2.32.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
]
[[package]]
name = "urllib3"
version = "2.2.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 },
]
削除はuv remove
$ uv remove requests
Resolved 1 package in 1ms
Uninstalled 5 packages in 10ms
- certifi==2024.8.30
- charset-normalizer==3.3.2
- idna==3.10
- requests==2.32.3
- urllib3==2.2.3
それにあわせてpyproject.tomlとuv.lockも更新される。
プロジェクト環境でインストールしたパッケージのコマンドもuv run
できる。(以下はエラーになるが、実行はできている)
$ uv add flask
$ uv run -- flask run
Usage: flask run [OPTIONS]
Try 'flask run --help' for help
(snip)
直接スクリプトを実行。以下のファイルを作成。
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'こんにちは!'
if __name__ == '__main__':
app.run(debug=True)
$ uv run app.py
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
uv sync
してvenvをactivateするやり方もあるらしい。uv sync
は環境を手動で更新する、とあるがちょっと意味がわかっていない。
$ uv sync
Resolved 9 packages in 0.77ms
Audited 7 packages in 0.32ms
$ flask run
# または
$ python app.py
パッケージ化はuv build
(上でapp.pyを作ってる場合は削除しておく必要がある)
$ uv build
Building source distribution...
running egg_info
creating hello_world2.egg-info
writing hello_world2.egg-info/PKG-INFO
(snip)
adding 'hello_world2-0.1.0.dist-info/top_level.txt'
adding 'hello_world2-0.1.0.dist-info/RECORD'
removing build/bdist.macosx-11.0-arm64/wheel
Successfully built dist/hello_world2-0.1.0.tar.gz and dist/hello_world2-0.1.0-py3-none-any.whl
$ tree -a -L 2
.
├── .git
(snip)
├── .gitignore
├── .python-version
├── .venv
│ ├── .gitignore
│ ├── CACHEDIR.TAG
│ ├── bin
│ ├── lib
│ └── pyvenv.cfg
├── README.md
├── __pycache__
│ └── app.cpython-312.pyc
├── dist
│ ├── .gitignore
│ ├── hello_world2-0.1.0-py3-none-any.whl
│ └── hello_world2-0.1.0.tar.gz
├── example.py
├── hello.py
├── hello_world2.egg-info
│ ├── PKG-INFO
│ ├── SOURCES.txt
│ ├── dependency_links.txt
│ ├── requires.txt
│ └── top_level.txt
├── pyproject.toml
└── uv.lock
12 directories, 22 files
作成したパッケージの公開。自分はやったことがないのでわからないけど、認証情報の指定が別途必要っぽい。
$ uv publish
パッケージ仮想環境
シンプルに仮想環境を作る場合、例えばGitHubレポジトリからクローンしたOSSのプロジェクトで仮想南疆を作るようなケース。
作業ディレクトリを作成
$ mkdir uv-venv-test && cd uv-venv-test
uv venv
で仮想環境を作成
$ uv venv
Using CPython 3.12.7
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate
.venv
が作成される。パッケージの追加はuv pip install
$ uv pip install streamlit
ファイルを作成
import streamlit as st
st.title("こんにちは!")
uv run
の場合はこう
$ uv run streamlit run app.py
仮想環境内に入って実行する場合はこう
$ source .venv/bin/activate
$ streamlit run app.py
$ deactivate
activate/deactivateが面倒なので、direnvといっしょに使いたいなぁと思うのだけど、この辺が参考になりそう。
まとめ
自分は以下のような感じでpyenv+venv+direnvで作り込んでいて、何も困っていないのだけども、最近はmiseを使いだしているところ。
で、uv今回触ってみて、mise+uvでほぼ同じことは実現できそう+Pythonプロジェクトも作れる、ならば、そろそろpyenvはお役御免にできそうである。