KAKEHASHI Tech Blog

カケハシのEngineer Teamによるブログです。

Musubi バックエンドの Python 開発環境を mise + uv へ移行しました(思ったより簡単)

Musubi 開発チームおよびサーバサイド Python 研究会の加藤です。最近は冷えますね。

私のチームで開発している Musubi のバックエンドは Python で実装されていますが、そのパッケージおよびランタイム管理の変遷を追ってみると

となっていて、ここ4年ほどは変わっていません。

最近は miseuv が流行っており 1、 しかも高速と聞いているので CI/CD の高速化も狙って導入することにしました。

Musubi バックエンドの構成

イメージが湧きやすいように Musubi バックエンドのリポジトリの中身を説明します。

# Musubi バックエンドリポジトリ中のファイル

# CDK で Lambda 関数をデプロイするためのコード
cdk/
  bin/*.ts
  lib/*-stack.ts
  package.json
package.json
pnpm-lock.yaml
pnpm-workspace.yaml
.node-version

# Lambda 関数にデプロイされる Python コード
musubi_backend/
   *.py
tests/
   test_*.py
requirements.txt # Poetry と pip のバージョンをロックするため
pyproject.toml
poetry.lock
.python-version

# リポジトリセットアップ用スクリプト
setup-repository

Python バックエンドのリポジトリですが、 AWS CDK のために Node も使用しているのが特徴ですね。

mise の導入

mise のインストール方法は以下のページで説明されています。

Musubi 開発チームでは、チーム共通の開発用ツールを一括インストールするスクリプトをメンテナンスしており、 mise 導入時はその中にある Brewfile に1行付け加えるのと、~/.zshrcmise activate コマンドの実行を付け加えるようにしました。

echo "Setup mise"
if ! grep -qE '^# mise$' ~/.zshrc; then # 既存コードがあればスキップ
  echo '# mise' >> ~/.zshrc
  echo 'eval "$(mise activate zsh)"' >> ~/.zshrc # mise を有効にするコマンド
else
  echo "Skip setup mise"
fi

Musubi 開発チームへの新規参画メンバーは必ずこれを実行してもらうので、後は残りのメンバーに各々インストールしてもらうように依頼しました。

Tip

(記事の趣旨から外れますが)チームサイズが大きくなってくるとこのような共通スクリプトのありがたみが増えますね

uv の導入

次に mise を使って uv をインストールするようにします。今回はバージョンを指定して uv のリリースで CI が壊れないようにしておきます。

$ mise use --pin uv
uv@0.9.13
mise ~/work/musubi-backend/mise.toml tools: uv@0.9.13

mise.toml ができましたね。こんな内容です。2

[tools]
uv = "0.9.13"

カケハシではサービス開発に Renovate を全面的に導入しており、 mise もアップデートの対象になっています。 そのため、新しい uv がリリースされると Renovate がアップデート用の PR を作ってくれるため、ツールの最新バージョンへの追従が楽になります。

Tip

mise.toml があるディレクトリへ初めて移動すると警告が出るので、 mise trust を実行して許可しましょう。

Poetry から uv への移行

マイグレーションスクリプトを実行して、 Poetry の設定を uv 向けに変換します。

uvx migrate-to-uv

これを実行すると pyproject.toml の Poetry 向けセクションが uv 向けに置換され、uv.lock の作成と poetry.lock の削除が行われます。

マイグレーションスクリプトで置き換えられないものもあり、これは git grep -i poetry で探して1つ1つ置き換えていきました。

  • poetry run -> uv run
  • poetry export -> uv export
  • poetry version -s -> uv version --short
  • 環境変数の切り替え poetry-plugin-dotenv をインストールしたうえで POETRY_PLUGIN_DOTENV_LOCATION=$(dirname $0)/.env.dev poetry run CMD を実行すると、 .env.dev で定義した環境変数が適用されたうえで CMD が実行されるので、これを利用して、開発と本番の環境変数を切り替えていたのですが、これはシンプルに MISE_ENV_FILE=$(dirname $0)/.env.dev mise exec -- CMD として置き換えることが出来ました。文字数も少し短くなってお得ですね

Python, Node のインストール

Python, Node のインストールも、 pyenv, nodenv から mise へ置き換えていきます。

pyenv, nodenv をまだ使用しているプロジェクトもあるので、 このリポジトリだけ mise で Python と Node のインストールをするようにします。

まず環境変数で nodenv, pyenv が管理下にあるバージョンを使用させないようにします。

mise set PYENV_VERSION=system
mise set NODENV_VERSION=system

次に、idiomatic_version_file_enable_tools を設定して、.python-version, .node-version に記載のランタイムバージョンをインストールするようにします。

mise settings add -l idiomatic_version_file_enable_tools python
mise settings add -l idiomatic_version_file_enable_tools node
mise install

無事に mise でインストールしたバージョンに置き換わっているようです。

$ which python
/Users/my_name/.local/share/mise/installs/python/3.14.3/bin/python
$ which node
/Users/my_name/.local/share/mise/installs/node/24.13.1/bin/node
Tip

mise 以外にバージョンマネージャーを使用する見込みがなければ、 idiomatic_version_file_enable_tools を使う代わりに mise use python@VERSION として完全に mise 管理にしても良いです。

リポジトリのセットアップスクリプトも、 mise を使うように直しておきましょう。

--- a/setup-repository
+++ b/setup-repository
@@ -1,19 +1,11 @@
 #!/bin/bash -ue

-pyenv install -s || (echo "anyenv install pyenv を実行してから再度実行してください" && exit 1)
-pyenv rehash
-nodenv install -s || (echo "anyenv install nodenv を実行してから再度実行してください" && exit 1)
-nodenv rehash

+mise install

 # Node
 corepack enable
 corepack install
 pnpm install

 # Python
+uv sync
-pip install -r requirements.txt # 指定したバージョンの pip, poetry をインストール
-poetry install

これで、./setup-repository を実行したときに Node, Python のインストールが mise で、 Python パッケージのインストールが uv で行われるようになります。

試してみたのですが、これまで「ぐーーーーーーん🚚」だったのが「スッ🚀」ぐらいに感じられるようになりました。いいですね!

不要になったので requirements.txt は削除します。

rm requirements.txt

One more thing ... (GitHub Actions)

これで終わり…と思ったら CI がまだ残っていました。

Musubi バックエンドの GitHub Actions ワークフローでは、以下の Action を Composite Action にラップして使用していますが、

- name: Setup Node.js
  uses: actions/setup-node@v6
  with:
    node-version-file: .node-version
    cache: pnpm
- name: Setup Python
  uses: actions/setup-python@v6
  with:
    python-version-file: .python-version
    cache: "pip"

これを mise-action で置き換えました。

- uses: jdx/mise-action@v3

スッキリしましたね!

Caution

記事の締め切りの都合で uvpnpm のキャッシュをまだ適切に設定出来ていません。その点では元の構成のままにしておいて、 uv別の Action でインストールしたほうが良さそうです。

あれ、思ったより簡単!?🤔

この記事、実は殆どが作業しながら行ったことを書いているのですが、ほとんど手戻りがなく進んでしまいました。

なぜだろうと考えてみたのですが、それは miseuvnodenv&pyenv や Poetry の機能を完全に受け継いでいて、置き換えるだけで動くレベルだったことと、 Musubi バックエンドが環境設定のとき以下のようにツールを直接呼び出す箇所を減らす設計から来ていると思います。

  • macOS 環境は共通開発ツールリポジトリにある setup スクリプト一本で mise, uv, それ以外にも ghjq 等のツールをインストール
  • リポジトリ毎のランタイムやパッケージインストールは ./setup-repository の実行のみ
  • CI も環境を初期化する処理はComposite ActionReusable Workflow で共通化してワークフローが増えても共通のまま

ツールの互換性とリポジトリの設計の両方がスムーズな移行の要因だったのですね。

まとめ

Musubi バックエンドのリポジトリへ miseuv を導入して、 ランタイムやパッケージのインストールをモダンにしました。

単に移行しただけなら「ちょっと早くなった」ぐらいですが、このリポジトリには他にも複雑な処理が詰まっているので、今後は mise のタスク機能を使ってそれをまとめていこうと思っています。

ここまで読んでくださってありがとうございました。

この記事を見て「私も miseuv を使ってみよう」と思ってくれる方がいたら嬉しいです。

参考

以下のサイトを参考にさせていただきました。


  1. 個人の感想です。
  2. この記事を書いている間に 0.10.2 が出ていました。