80
80

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonの開発用適当ツールの作成・実行はuvを使うのがオススメ

Last updated at Posted at 2025-03-24

はじめに

以前に Goを推奨する記事 の中で、「ツール用言語としてPythonの体験が依存性管理等で良くない」ということを書いたのですが、 uv (GitHub) というRust製のPythonパッケージマネージャーツールで体験がかなり改善しそうだったので、この記事で試していきたいと思います。

(この記事のソースコードは https://gitlab.com/ksaitou/2025-03-17_tryout_uv で入手可能です)

uvのインストール

インストール ページを見る限り、通常のパッケージマネージャに対応しているようです。

# macOS
$ brew install uv

# Windows
$ winget install --id=astral-sh.uv  -e
$ scoop install main/uv

# Cargo (Rustのパッケージマネージャ)
$ cargo install --git https://github.com/astral-sh/uv uv

シングルファイルPythonの実行にuvを使う

uvがインストールされた状態で以下の小さなツールのスクリプトを実行してみます。

my-weather-report-tool.py
# /// script
# requires-python = ">=3.13"
# dependencies = [
#     "datetime",
#     "requests",
# ]
# ///

import requests
import datetime

report_source = "https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json"

if __name__ == "__main__":
    response = requests.get(report_source)
    data = response.json()
    for area in data[0]['timeSeries'][0]['areas']:
        if area['area']['name'] == '東京地方':
            weather_tokyo = area['weathers']
            break

    today = datetime.datetime.now()
    tomorrow = today + datetime.timedelta(days=1)
    tomorrow_str = tomorrow.strftime("%Y-%m-%d")

    print(f"東京地方の{tomorrow_str}の天気は「{weather_tokyo[1]}」です。");
python3コマンドの代わりにuv runコマンドを使えばOK
$ uv run my-weather-report-tool.py
Installed 9 packages in 31ms

東京地方の2025-03-18の天気は「くもり 昼前 から 昼過ぎ 晴れ 所により 昼過ぎ から 雨か雪」です。

自動的に依存パッケージのインストールとスクリプトの実行が行われました。小さなツールの速やかな実行に最適そうです。

スクリプトのヘッダに記載されている /// script/// は何?

スクリプトのヘッダに書かれているPythonランタイムや依存性に関わるコメント /// script/// はuv特有のメタデータではなく、Python Packaging内の inline-script-metadata という仕様によるメタデータです。

# /// script
# requires-python = ">=3.13"
# dependencies = [
#     "datetime",
#     "requests",
# ]
# ///

各セクションを # /// セクション名# /// のコメントで挟んで表現します。

今回使っているscript セクションであれば、スクリプトの依存性やツールバージョンを示すことができます。

inline-script-metadataは以下のコマンドで追記できるほか、手で追記しても構いません。

# inline-script-metadata を追加する場合
$ uv init my-weather-report-tool.py

# inline-script-metadata 上に依存性を追加する場合(別にinitしていなくてもOK)
$ uv add --script my-weather-report-tool.py datetime requests

依存性をコントロールする

依存性について細かいバージョンを指定することも可能です。

inline-script-metadata部分
# dependencies = [
#     "datetime",
#     "requests >= 2.8.1",
# ]

単純に構成を保証するためにロックファイルが欲しい場合は以下のコマンドで スクリプト名.py.lock を得ることができます。

$ uv lock --script my-weather-report-tool.py
# => my-weather-report-tool.py.lock が生成される

Pythonバージョンをコントロールは .python-version で行う

仮に特定のバージョンのPythonで実行してほしい場合は以下のように指定したいところですが、単純にPythonランタイムに対する希望を指定するもので、Python(CPython)を自動インストールしてくれるようではないようです。

inline-script-metadata部分
# # ↓ これを指定しても別にCPython 3.11.11 がインストールされるわけではない
# requires-python = "==3.11.11"

こういった場合には inline-script-metadata ではなく .python-version ファイルを置いておくことにより uv run する前に自動的にインストールしてくれます。

.python-version
3.11.11
# uvで実行するだけで勝手に .python-version に基づいてCPythonがインストールされる
$ uv run my-weather-report-tool.py

普通のPythonプロジェクトにuvを使う

1ファイルで完結するスクリプトではなく、がっつり複数ファイルで構成された普通のPythonプロジェクトを構成したい場合にどうやって使うのか見ていきます。

まずプロジェクトを uv init コマンドで作成します。Pythonでは定番のファイルが作成されます。

Pythonプロジェクトを初期化する
$ uv init mypj
$ ls -a mypj
.python-version README.md       main.py         pyproject.toml

基本的には依存性などプロジェクトのメタデータは pyproject.toml に記述すればよいです。中身としては先ほどのシングルファイルのヘッダコメントの inline-script-metadata と同じようなものです。

dependencies に依存性を追記し main.py を実行してみます。

pyproject.toml
[project]
name = "mypj"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
# `uv add` コマンドでも追加できる
dependencies = ["datetime", "requests"]
main.py
import requests
import datetime

report_source = "https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json"

if __name__ == "__main__":
    response = requests.get(report_source)
    data = response.json()
    for area in data[0]['timeSeries'][0]['areas']:
        if area['area']['name'] == '東京地方':
            weather_tokyo = area['weathers']
            break

    today = datetime.datetime.now()
    tomorrow = today + datetime.timedelta(days=1)
    tomorrow_str = tomorrow.strftime("%Y-%m-%d")

    print(f"東京地方の{tomorrow_str}の天気は「{weather_tokyo[1]}」です。");
main.pyを実行してみる
$ uv run main.py 
Using CPython 3.13.2
Creating virtual environment at: .venv
Installed 9 packages in 28ms

東京地方の2025-03-18の天気は「くもり 昼前 から 昼過ぎ 晴れ 所により 昼過ぎ から 雨か雪」です。

以下を一度にやってくれました。

  • .python-version に基づき CPython をインストール
  • .venv ディレクトリ(エディタ等が補完などのために使います)を作成
  • uv.lock(依存性ロックファイル)を作成
  • main.py を実行
    • スクリプトの実行が不要で依存性だけ持ってきたい場合は uv sync コマンドを使えばOK

とにかく一度構成された通常プロジェクトでも、スクリプトの実行には uv run を使えば問題なさそうです。

DockerイメージやCI/CDでuvを実行してみる

それぞれガイドがあるので、それに従えばいいでしょう。(CI/CDはGitLabを例に貼っています)

一応概要を書いておくと、以下のようになります。

  • 公式のDockerイメージが ghcr.io/astral-sh/uv としてPythonバージョン等ごとに提供されています。
  • 依存性のキャッシングや最適化は各ガイドで考慮されています。以下の例を見るとポイントが分かりやすいと思います。
    • Docker
      Dockerfile
      # キャッシングありでの依存性のみのインストールレイヤーの作成
      RUN --mount=type=cache,target=/root/.cache/uv \
          --mount=type=bind,source=uv.lock,target=uv.lock \
          --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
          uv sync --frozen --no-install-project
      
      # あらためてプロジェクトソースを追加して依存性インストール
      ADD . /app
      RUN --mount=type=cache,target=/root/.cache/uv \
          uv sync --frozen
      
    • CI/CD
      .gitlab-ci.yml
      task-using-uv:
        variables:
          # uv の依存性をインストールするディレクトリを指定 (各uvコマンドで参照される)
          UV_CACHE_DIR: .uv-cache
        cache:
          - key:
              files:
                - uv.lock
            paths:
              - $UV_CACHE_DIR
        script:
          # 依存性をインストール
          - uv sync --frozen
          # プロジェクト内の検証タスク
          # - ...
          # 依存性ディレクトリをキャッシングのために最適化する (ビルドの最後でいいかも)
          - uv cache prune --ci
      

古いAnsibleしか使えないプロジェクトにuvを使う

塩漬けになっているサーバなど、サーバのOSやPythonバージョンが古すぎるせいで、新しいAnsibleバージョンが使えない場合があります。

たとえば、Python 2.7しか無いCentOS 7 (EOL)のサーバをAnsibleで保守することを考えた場合、 以下のバージョンを最後にAnsibleによるサポートが終了していることがわかります。

Ansibleバージョン: 2.16
クライアントマシン: Python 3.10 - 3.12
ターゲットマシン: Python 2.7, Python 3.6 - 3.12, Powershell 5.1
Releases and maintenance

クライアントマシンのプラットフォームでインストールされているAnsibleバージョンは2025-03現在であれば 2.18 ではないでしょうか? 2.17 以降でPython 2.7しかないサーバに繋ぐとエラーで終了してしまいます。

こういったケースのために uv を使って塩漬けPJのAnsible/Pythonのバージョンを下げる手段があります。

uvでプロジェクト固有バージョンのAnsibleをインストールして利用する

まずは uv init . コマンドで既存のAnsibleプロジェクトにPythonプロジェクトファイルを追加しましょう。(別に手でファイルを追加してもいいです)

既存プロジェクトの初期化
$ uv init .

# 不要ファイルは適宜消す
$ rm main.py

$ ls -a
 .python-version README.md       pyproject.toml  site.yml

各ファイルに必要事項を記入していきます。

pyproject.toml
[project]
name = "myansible"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"

# Ansible動作環境としての条件を指定
requires-python = ">=3.10"
dependencies = ["ansible==9.11.0"]
.python-version
3.12

uv venv / uv sync コマンドでAnsibleを実行可能にします。

Ansible用のvenvの準備
# .venv/ ディレクトリの作成
$ uv venv
Using CPython 3.12.9 interpreter at: /opt/homebrew/opt/python@3.12/bin/python3.12
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate

# .venv/bin 内に ansible 系のコマンドを準備
$ uv sync
Resolved 11 packages in 82ms
Prepared 6 packages in 613ms
Installed 10 packages in 347ms
 + ansible==9.11.0
 + ansible-core==2.16.14
 + cffi==1.17.1
 + cryptography==44.0.2
 + jinja2==3.1.6
 + markupsafe==3.0.2
 + packaging==24.2
 + pycparser==2.22
 + pyyaml==6.0.2
 + resolvelib==1.0.1

venv を有効にします。

venvの有効化
# `uv venv` で指定されたコマンドを実行
$ source .venv/bin/activate

いよいよ ansible 系コマンドにパスが通っている状態なので実行してみます。

(myansible) $ python --version
Python 3.12.9

(myansible) $ ansible --version
ansible [core 2.16.14]
  config file = None
  configured module search path = ['/Users/ksaitou/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /Users/ksaitou/rnd/2025-03-17_tryout_uv/myansible/.venv/lib/python3.12/site-packages/ansible
  ansible collection location = /Users/ksaitou/.ansible/collections:/usr/share/ansible/collections
  executable location = /Users/ksaitou/rnd/2025-03-17_tryout_uv/myansible/.venv/bin/ansible
  python version = 3.12.9 (main, Feb  4 2025, 14:38:38) [Clang 16.0.0 (clang-1600.0.26.6)] (/Users/ksaitou/rnd/2025-03-17_tryout_uv/myansible/.venv/bin/python3)
  jinja version = 3.1.6
  libyaml = True

(myansible) $ ansible-playbook site.yml 
TASK [Say hello] ********************************************
ok: [localhost] => {
    "msg": "Hello"
}

プロジェクトでAnsibleを実行する前にvenvの有効化など多少の儀式は必要ですが、古いAnsibleプロジェクトの環境整備にuvが使えることもわかりました。

まとめ

Pythonのパッケージマネージャツールである uv を入れておけば、事前準備無しで、シングルファイルの実行から通常のPythonプロジェクトのマネージメント、また古いAnsibleプロジェクトの実行まで幅広いタスクをこなせることを確認できました。

とにかくPython製の開発用適当ツールの実行については pip3/python3 コマンド (あるいは pip/python コマンド) の存在を一旦忘れて uv run だけを使う という具合にやってしまってもいいかもなあと思っています。

80
80
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
80
80

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?