💬

npm感覚でPython環境を構築!非PythonエンジニアがuvでPythonプロジェクト(Talk to the City)を動かす

2025/03/19に公開2
9

uv を使った Python のパッケージ依存関係の解決方法を解説していきます。

業務やプライベートで扱う言語は Node.js がメインで、普段は npm や pnpm を使ってライブラリの依存関係を解決しています。

そんな私がある機会でTalk to the Cityを検証して欲しいと頼まれました。

Talk to the City は 2024 年の東京都知事選挙で安野たかひろ氏のチームが使ったことで注目された Python プロジェクトです。

Python の開発環境を作って README 通りに動作させてやれば良いかと進めていったところで絶望しました。

DevContainer を用いてクリーンな Python の実行環境を用意して試したのですが、2025 年 3 月 19 日現在、デモ通り動かしてもエラーが発生します。

動作に失敗したログ

ImportError: cannot import name 'cached_download' from 'huggingface_hub' (/workspaces/talk-to-the-city-reports/scatter/venv/lib/python3.10/site-packages/huggingface_hub/__init__.py)

エラーログの最終行にこの記述があり、どうやらbertopic==0.15.0sentence-transformers==2.2.2のどちらかが、requirements.txtに記載の無いhuggingface_hubパッケージの新しめのバージョンを参照し、既に削除されたメソッドを使用しているようです。

上記の再現手順
git clone https://github.com/AIObjectives/talk-to-the-city-reports.git

VS Code で/scatterをワークスペースとして開き、コマンドパレットから開発コンテナー: コンテナーでリビルドして再度開く Dev Containers: Rebuild and Reopen in Containerを実行して、Python3.11-bullseye のテンプレートを選択して DevContainer を作成

https://github.com/AIObjectives/talk-to-the-city-reports/blob/main/scatter/pipeline/inputs/example-polis.csv から csv ファイルを手動でダウンロードして/scatter/pipeline/inputsに入れる。(Git LFS を使うのがめんどくさかったため)

DevContainer でターミナルを開き以下を実行

# 環境の用意
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python -c "import nltk; nltk.download('stopwords')"
cd pipeline
OPENAI_API_KEY='OPEN_AIのAPIキー' && python main.py configs/example-polis.json

依存関係の解決(requirements.txt)は、現時点(2025 年 3 月 19 日)から見ると 2 年前がラストコミットです。LLM 分野のパッケージは更新が非常に早いでしょうから、動かなくなるのも仕方ありません。

uv を使って Python の実行環境をまるっと設定する

ここまでやってきて Python プロジェクトのめんどくさい点は、requirements.txtを使ったパッケージ解決の部分と、その元となる実行環境を整える部分にあると思います。

また、最初の例では、DevContainer を用意しましたが、これぐらいの動作検証だったら大げさに仮想環境を立てることなく使用しているホストマシン(自分の場合は Mac)から直接 Python を実行したいです。

したいですが...

Python ではパッケージ管理を行うpipが依存をローカルにインストールする機能を持っていないため狙った環境を揃えるのも一苦労です。

デフォルトで Python がvenvを使用し、pip をローカルに扱える仮想環境を用意してくれています。

しかし、それだけでは Python のバージョンを切り替えることはできません。

また、いちいちプログラムを実行するために仮想環境に入らなければいけないのは普段は Node.js を使ってローカルから直に開発している身としては大変に感じます。

そこで、uv という最近、市民権を得ている Python のパッケージマネージャーがオススメです。

現在話題の AI エージェントツール、OpenManus のインストールでも uv が推奨されているほどには市民権を得ています。

uv を使えば、Python バイナリも uv がマシンに合わせて適切に落としてきてくれますし、pyproject.tomlでバージョンを指定して切り替えることもできます。

また、npm の出力するpackage-lock.jsonのようにuv.lockという依存関係を全て明記したファイルも生成してくれるので、今回のような依存関係で時間が経って実行できないという体験も少なくなるでしょう。

uv は、Python 本体の仮想環境(venv)やパッケージ管理ツール(pip)を内部で呼び出し、ラッパー的に操作する仕組みになっています。(たぶん)

requirements.txt から uv に移行する

Talk to the City のように uv が使われておらず既にrequirements.txtのみでパッケージ管理がされている Python プロジェクトでしたら、新しく uv プロジェクトとしてセットアップします。

astral-sh/uv の READMEを参考に uvコマンドを使えるようにした後に

uv init

を実行するとpyproject.tomlが生成されます。$ uv initでは、サンプル用のmain.pyが一緒に作られますが、不要であれば削除して構いません。

今回の場合は、Python3.10 の最新バージョンを使って欲しいので、pyproject.tomlを開いてrequires-pythonのところを==3.10.*のように指定します。

そして、以下のコマンドを使えば、requirements.txtから読み取る形でpyproject.tomldependenciesにパッケージを転記してくれます。

uv add -r requirements.txt
pyproject.toml
[project]
name = "scatter"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = "==3.10.*"
dependencies = [
    "bertopic==0.15.0",
    "hdbscan==0.8.33",
    "langchain==0.0.308",
    "numpy==1.25.2",
    "openai==0.28.1",
    "pandas==2.1.1",
    "path==16.7.1",
    "scikit-learn==1.3.1",
    "sentence-transformers==2.2.2",
    "spacy==3.7.0",
    "tiktoken==0.5.1",
    "tqdm==4.66.1",
    "umap-learn==0.5.4",
]

$ uv syncというコマンドを実行するとホストマシンにpyconfig.tomlが使う依存をダウンロードしてきてくれます。

uv sync

これで、DevContainer で用意した環境と同じようにホストマシンでも実行できるようになりました。uv run main.pyのコマンドで uv 経由で venv の環境の中でプログラムを実行してくれます。

cd pipeline
OPENAI_API_KEY="OPEN_AIのAPIキー" uv run main.py configs/example-polis.json

uv で新しく依存関係を解決する

uv に移行はできましたが、Talk to the City の場合は参照元にしているrequirements.txtに不備があるので DevContainer の例と同様に動きませんでした。

そこで動くかどうかは運任せになりますが、パッケージをアップデートすることにしました。

uv を使ってpyproject.tomldependenciesにソースコードから参照されているパッケージのみを追加して、残りの依存解決はuv.lockに任せます。

まずは、requirements.txtを見てプロジェクトが直接 Import しているパッケージを特定します。

検索しながらチマチマ確認している画像

ここの手順は AI にワンライナースクリプトにしてもらいました
grep -F -x -f <(find . -name '*.py' -exec grep -rhoE "^(from|import)\s+([a-zA-Z0-9_]+)" {} \; | awk '{print $2}' | cut -d'.' -f1 | sort -u) <(cut -d= -f1 requirements.txt | sed 's/-/_/g' | sort -u)

以下のパッケージがプロジェクトの.pyファイルから直接 Import されていそうでした。

bertopic
hdbscan
langchain
numpy
openai
pandas
path
sentence_transformers
spacy
tiktoken
tqdm

pyproject.tomldependenciesuv.lockを一旦消して、これらをuv addでこれらの依存を解決しながらインストールしてもらいます。

uv add -n bertopic hdbscan langchain numpy openai pandas path sentence_transformers spacy tiktoken tqdm

ここでまたエラーが出てしまいました。

llvmliteのビルドに失敗する

RuntimeError: Cannot install on Python version 3.10.16; only versions >=3.6,<3.10 are supported.

とあるように Python3.10 だと依存するパッケージのビルドに失敗してしまうので、Python3.9 でトライしてみます。pyproject.tomlrequires-python = "==3.9.*"に変更すれば良いだけなので簡単です。

pyproject.toml
[project]

name = "scatter"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = "==3.9.*"
dependencies = []

変更後は$ uv syncを実行してマシンにバイナリをダウンロードします。

その後、Python3.9 で$ uv run main.py configs/example-polis.jsonを実行したらいくつかのエラーが出ました。

To install langchain-community run pip install -U langchain-community.

とエラーに出てきたので従います。

uv pip install -U langchain-community

次に

ModuleNotFoundError: No module named 'nltk'

と出てきて、Talk to the City の求めるセットアップを DevContainer に変えてから行ってなかったので行います。

uv pip install nltk
uv run python -c "import nltk; nltk.download('stopwords')"

このようにuvコマンド越しにvenvの仮想環境でpipコマンドが使えるのは楽で良いです。

最終的に、$ uv run main.py configs/example-polis.jsonの処理を最後まで成功させることができました。

まとめ

以上になります。

もし、「普段は Python をあまり触らないけれど、ちょっとした検証で Python プロジェクトを動かす必要がある」という方がいたら、ぜひ uv を使って環境構築をしてみてください。

(この記事のテキストは、ほぼ 100% 人間の手によって書かれています。)

GitHubで編集を提案
9

Discussion

lucidfrontier45lucidfrontier45

uv は、Python 本体の仮想環境(venv)やパッケージ管理ツール(pip)を内部で呼び出し、ラッパー的に操作する仕組みになっています。(たぶん)

uvはRustで実装されていてPythonを直接は利用しません。uvから使えるvenvやpipコマンドはPython版と同様のインターフェースをしているだけで実際にはRustで実装されています。以下の両者は異なります。

# uv自体の機能でインストール
uv pip install numpy

# uvからPython実装のpipを使ってインストール
uv run pip install numpy

また、理解したうえでやってらっしゃるかもしれませんが、uv add/syncとuv pipは混ぜて使用せずに基本的にはuv add/syncのみを使用するのがパッケージ管理としては正しく、npmやcargoなどと同じ挙動になります。uv pipはあくまでpipとの互換性のためだけにある機能です。

Junsei NagaoJunsei Nagao

uvはRustで実装されていてPythonを直接は利用しません。uvから使えるvenvやpipコマンドはPython版と同様のインターフェースをしているだけで実際にはRustで実装されています。

こちらの補足ありがとうございます!リンクする形で本文の方にも引用しておきますね。

uv add/syncとuv pipは混ぜて使用せずに基本的にはuv add/syncのみを使用するのがパッケージ管理としては正しく、npmやcargoなどと同じ挙動になります。
uv pipはあくまでpipとの互換性のためだけにある機能です。

こちらも補足ありがとうございます!

uvの仕組みを把握してプロジェクトを全てuv add/uv syncに移行せずとも、一時的に解決するだけならハック的にuv pipでpipを使った情報に沿って行えるのもPythonianでない方にuvをオススメしたくなった動機です😊。(pipと別物としてuvを学習するのではなくpipの延長線上という理解の上で操作できるインターフェースが魅力的ですね。)