PEP723ではPythonスクリプトの先頭に 依存関係などを記述するInline script metadataを規定している。

これを使うと、スクリプトを配置して実行権限をつけるだけでどこでも実行できるPythonスクリプト が作れるようになる。

スクリプトを共有したりAIに吐かせるときに単一ファイルだと扱いやすいというメリットがある。

実行環境

  • *nix(e.g. ubuntu, MacOS)
  • uvがインストールされた環境

PEP723について

以下の例では、Pythonのバージョンと依存パッケージのバージョンを記述している。

PEP723に対応していないツールだと単に無視され、uvなどの対応するツールを使用すると、 インタプリタとパッケージを自動で使用する。

# /// script
# requires-python = ">=3.11"
# dependencies = [
#   "requests<3",
#   "rich",
# ]
# ///

import requests
from rich.pretty import pprint

resp = requests.get("https://peps.python.org/api/peps.json")
data = resp.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])

PEP723 with uv

uv runでスクリプトを実行するとInline script metadataを解釈する。

$ uv run script_with_pep723.py

Inline script metadataが含まれないPythonコードに対しては以下のコマンドで 付与することができる。

$ uv add --script=script_with_pep723.py --python 3.11 'request<3' rich

shebangをつけてコマンド名単体で実行する

shebangを加え、シェルスクリプトのように実行できるようにする。 シェルスクリプトは/usr/bin/bashなどに渡せば済むが、 uvとshebangの仕様上少し捻る必要がある。

結論から言うと先頭の行に以下の行を追加すれば良い。

#!/usr/bin/env -S uv run -s
# /// script
# requires-python = ">=3.11"
# dependencies = [
#   "requests<3",
#   "rich",
# ]
# ///

# 省略

これがあれば./script-with-pep723のようなCLIとして動作させることが可能。

uv runの仕様について

uv runは直後に指定されたコマンドを仮想環境のパスを通した上で実行する。 つまり、uv run script.pyはおおよそ./.venv/bin/python3 ./script.py という形と等価である。

しかし、実行するファイルの拡張子が.pyでない場合はそのまま実行される。 つまり、./scriptのように実行される。そうすると./script内のshebang が再解釈され、無限にuvの再起呼び出しが発生してしまう。

エラー文
error: `uv run` was recursively invoked 101 times which exceeds the limit of 100.

hint: If you are running a script with `uv run` in the shebang, you may need to include the `--script` flag.

uvは優しいのでちゃんと教えてくれる。

この問題はuv run-sをつけることで解決する。-sは後続のコマンドをPython スクリプトとして解釈することを強制する。

shebangの仕様について

一般にshebangは以下の制約がある。

  • コマンドは絶対パスで指定する。
  • 1つ以上のコマンド引数を処理することはできない。

そこでenvを使ってshebangを拡張する方法がよく使用される。1 shebangは歴史が深く環境によって微妙に動作が異なるので、詳しい説明は割愛する。

env経由で実行することでenvを絶対パスで指定する代わりに実行したいコマンドを 環境変数PATHを用いて解決することができる。

また、-Sオプションを用いることでコマンド引数のパースをよしなにやってくれる。

つまり、上記の問題を解決するには#!/usr/bin/env -S ...という形にしておけばよい。

結論

PEP273でないスクリプトを対応させるには:

uv add --script=script-with-pep723 --python 3.11 'request<3' rich

.py拡張子無しで直接CLI的に実行するには#!/usr/bin/env -S uv run -sを追加する。