このブログの Python 環境を mise に移行した

カテゴリー:  Tech タグ:  blogging homebrew mise nikola python

Cover

このブログは Nikola というPython製の静的サイトジェネレーターで作っています。 そのPython環境を、HomebrewのPythonで作ったvenvから mise 管理に移行しました。 ついでに、何年も放置していた requirements.txt も整理しました。

移行前の状態

ブログのディレクトリーには venv/ があり、ビルドの前に source venv/bin/activate するという、ごく普通の運用でした。 改めて中身を確認すると、次のような問題がありました。

  • venvの元になっているPythonはHomebrewの python@3.13 でした。 venvは /opt/homebrew/opt/python@3.13 を参照しているので、brew upgrade するたびに中のPythonが勝手に入れ替わります。 作成時は3.13.5だったものが、いつの間にか3.13.16になっていました。
  • 一方、miseのグローバル設定では python = "3.13.7" を指定していて、Pythonが2系統混在していました。
  • requirements.txt は126行ありました。JupyterLabやipykernelなど、ビルドに使わないものまで並んでいます。 昔 pip install Nikola[extras] した環境を pip freeze した結果が、そのまま残っていたようです。
  • requirements.txt には Nikola==8.3.1 と書かれているのに、venvに入っているのは8.3.3でした。 venvを作り直すと古いバージョンに戻ってしまいます。

Python のバージョンを決める

まず、NikolaがどのPythonに対応しているかを確認しました。

  • Nikolaの最新版は8.3.3(2025年5月リリース)で、そこから新しいリリースは出ていません。
  • PyPI上の条件は requires_python >=3.8 で、動作確認済みとして挙がっているのは3.13までです。
  • GitHubには「nikola auto がPython 3.14で落ちる」というIssueがあります。 エラーは RuntimeError: There is no current event loop です。 修正はmasterにマージ済みですが、まだリリースされていません。

というわけで、Python 3.13で固定することにしました。3.14に上げるのはNikolaの次のリリースを待ってからにします。

mise はパッケージまで管理できるのか

miseでPythonのバージョンを管理するなら、パッケージもmiseに任せてvenvを不要にできないか調べてみました。 調べてみると、miseにはパッケージに関わる機能がいくつかあります。

pypiバックエンド("pypi:nikola" = "8.3.3")
CLIツールをツールごとの専用venvに入れる機能です。 with = [...] で追加パッケージを同じ環境に入れることはできます。 ただ、間接的な依存のバージョンを固定する手段はなく、requirements.txt も読めません。 ドキュメントにも「アプリケーションのライブラリーはuvやpipで管理するプロジェクト環境に置く」とあります。
mise deps([deps.pip] / [deps.uv])
requirements.txt などの変更時に pip install -r や uv sync を自動で実行します。 まだ実験的機能で、ドキュメントによると、pipプロバイダーは仮想環境の作成や選択をしません。 venvの存在が前提です。
_.python.venv
venvを作成して有効化する機能です。パッケージのインストールはしません。
.default-python-packages
Pythonのバージョンごとに全体へ入れるパッケージを指定する機能です。非推奨で、2027.11.0からは読み込まれなくなります。

結局、miseが受け持てるのは「venvを作る」「venvを有効にする」「venvにインストールする」ところまでで、パッケージを置く場所としてのvenvはなくせません。 役割を次のように分けることにしました。

  • mise: Python本体のバージョンを固定し、venvの作成と有効化を自動で行う
  • venv: Nikolaなどのパッケージをプロジェクトごとに分けて置く

mise.toml

ブログのリポジトリー直下に、次の mise.toml を置きました。

[tools]
python = "3.13"

[env]
# venv を自動で作成・有効化する(uv で作るため --seed で pip も入れる)
_.python.venv = { path = "venv", create = true, uv_create_args = ["--seed"] }

これで、ディレクトリーに cd するだけでvenvが有効になります。source venv/bin/activate は不要になりました。 venvの元になるPythonもmise管理のものに変わったので、Homebrewの更新に引きずられることもありません。

ひとつ引っかかったのは、uvがインストールされているとmiseがuvでvenvを作ることです。 uvで作ったvenvにはpipが入らないので、uv_create_args = ["--seed"] を指定してpipも入れるようにしました。 これで今までどおり pip install -r requirements.txt が使えます。

requirements.txt の整理

Nikola本体の依存パッケージは、Nikolaをインストールすれば自動で入ります。 そこで、直接必要なものだけを書くことにしました。 最初はNikola、MermaidのMarkdown拡張、nikola auto 用のaiohttpとwatchdogだけにしてビルドしてみましたが、エラーが2つ出ました。

  • ギャラリーの metadata.yml を読むのにruamel.yamlが必要でした。
  • テーマはMakoですが、Nikolaは起動時にjinjaテンプレートプラグインを読み込むのでJinja2も必要でした。

この2つを加えたのが最終版です。

# Nikola 本体(依存パッケージは自動で入る)
Nikola==8.3.3
# MARKDOWN_EXTENSIONS で使う Mermaid 拡張
markdown-inline-mermaid==1.0.4
# nikola auto(Nikola の extras のうち必要なものだけ)
aiohttp==3.14.5
watchdog==6.0.0
# galleries/*/metadata.yml の読み込み
ruamel.yaml==0.19.1
# Nikola が起動時に jinja テンプレートプラグインを読み込むため(テーマは Mako)
Jinja2==3.1.6
# Markdown 本体(Nikola の依存だが、出力に直接効くためバージョンを固定)
Markdown==3.11.1

venvに入るパッケージは134個から35個に減りました。

移行前後のビルド結果を比べる

移行前の環境と移行後の環境でそれぞれフルビルドし、出力を比べました。 ビルド時間はどちらも約3分で、変わりませんでした。 差分は約2800ファイルありましたが、中身を見るとほとんどは見た目に関係ないものでした。

  • rSTのコードブロックには、ビルドのたびにランダムなID(rest_code_<uuid>)が振られます。HTMLの差分の大半はこれでした。
  • 画像はPillowのバージョンが変わってバイナリーは変わりましたが、ピクセル単位では同じでした。

実際に表示が変わったのは、Markdownを3.8.2から3.11.1に上げた影響を受けた64記事でした。

  • 行末で閉じている ** の強調が太字にならず、記号のまま表示される
  • 閉じタグのない <ins datetime=...> がHTMLとして扱われず、エスケープされて表示される

どちらも、元の記事の書き方がゆるかったことによるものです。Markdownを古いバージョンに固定すれば元どおりになりますが、最新版を使うことにしました。 気になる記事は、見つけたときに直していくつもりです。

移行中に出会ったビルドエラー

移行後の確認中に、2回目以降の nikola build が AttributeError: 'str' object has no attribute 'task' で落ちることがありました。 最初は移行のせいかと疑いましたが、移行前の環境でも同じ状態から再現しました。 Nikolaの既知の問題(Issue #3406)で、2020年から未解決です。

原因は post-list ディレクティブです。 このブログの一部の記事では、末尾に post-list で同じタグの記事一覧を表示しています。 Nikolaは一覧に載せた記事を依存関係として登録するので、記事どうしが互いの一覧に載ると循環依存になります。 すると、ビルドツールのdoitが実行できるタスクを見つけられなくなり、このエラーで止まります。

根本的な対策は post-list の使い方を見直すことですが、今回は見送りました。 エラーが出たら nikola clean でキャッシュを消してビルドし直せば、正常に戻ります。

まとめ

  • miseはPython本体のバージョン管理とvenvの自動化を担当し、パッケージはvenvに置く、という分担に落ち着きました。
  • requirements.txt は pip freeze の結果ではなく、直接必要なパッケージだけを書く形にしました。
  • cd するだけで環境が整うようになり、Homebrewの更新を気にする必要もなくなりました。

コメント

Comments powered by Disqus