Jinja を使うほどじゃない場合に使えるかもしれない string.Template クラスの紹介 - サーバーワークスエンジニアブログ

Jinja を使うほどじゃない場合に使えるかもしれない string.Template クラスの紹介

記事タイトルとURLをコピーする

こんにちは。自称ソフトウェアエンジニアの橋本 (@hassaku_63)です。

Python のテンプレートエンジンと言えば、おそらく Jinja が最もポピュラーでしょう。

ただ、要件によっては「そこまでリッチなものはいらないんだけどな・・・」という場合もあると思います。例えば if, for のような制御構造や、フィルタのような式が不要な場合です。

そういった場合は Python 標準ライブラリだけで用事を満たすことができます。この記事では、標準ライブラリでも少々マイナー寄り(主観)な Template クラスを紹介します。ついでに、Template クラス以外の選択肢として envsubst にも触れます。

結論

string に定義されている Template クラスが使えます。

docs.python.org

ただ、簡単な変数展開だけができればよい程度であれば、まずはシェルで envsubst を使う方法で代替できないかも検討してみるとよいでしょう。

実装例

次の Gist の通りです。

gist.github.com

commands.template がテンプレートで、 main.py が CLI 形式でテンプレートへの変数展開をするための簡易 CLI になります。

なお、テンプレート中のプレースホルダは環境変数と同じように ${variable} の表記でも記述可能です。

これらのファイルがカレントディレクトリに配置してある前提で、次のようなコマンドを実行すると展開された結果が得られます。

# Example usage

$ python -m main --account 1234567879012 --region ap-northeast-1 --image-name foo-image --image-tag v0.1.0

aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 1234567879012.dkr.ecr.ap-northeast-1.amazonaws.com
docker build -t foo-image .
docker tag foo-image:v0.1.0 1234567879012.dkr.ecr.ap-northeast-1.amazonaws.com/foo-image:v0.1.0
docker push 1234567879012.dkr.ecr.ap-northeast-1.amazonaws.com/foo-image:v0.1.0

substitute メソッドは dict か kwargs を引数に取りますが、テンプレートに使える変数名が補完されず見えづらく感じる場合は委譲を利用するとよいでしょう。Template を所有する委譲元のクラスで、埋め込む変数を明示的に宣言してあげる感じでインタフェースを実装すると良いと思います。

また、Python 3.11 からは get_identifiers というメソッドが使えます。このメソッドを使うことで、そのテンプレートで定義されているプレースホルダが一覧できますので、こちらもあると便利です。独自の CLI でラップする場合に、デバッグ情報やエラー出力の補助的情報として使ったりすると、よいかもしれません。

(余談)それ、envsubst で間に合うかも?

想定利用者の動作環境に Python を要求するのがそもそもイマイチな場合は、Template クラスを使った実装よりは envsubst で変数展開を行うようにした方が便利かもしれません。

例えば「ソースコードを clone する機会はあるがメインの開発担当ってわけじゃない」こんな立場の人が想定利用者に含まれる場合...などでしょうか? そのツールを誰がいつ、どのような動作環境で使うのか?といった背景事情を鑑みつつ判断していきましょう。まあ、そのあたりは要求分析・要件定義の世界です。

ここでは envsubst を使う場合どうなるかを紹介します。

さっき提示したテンプレートファイルはそのまま同じものが使えます。プレースホルダの表記方法は変えなくてOKです。

envsubst の場合は注入する値を環境変数で与える必要があります。先の実行例を envsubst バージョンで置き換えた場合は次のようになります。

$ cat << EOF > .env
aws_region=1234567879012
aws_region=ap-northeast-1
aws_account_id=1234567879012
image_name=foo-image
image_tag=v0.1.0
EOF

$ export $(cat .env)

$ envsubst < commands.template

aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 1234567879012.dkr.ecr.ap-northeast-1.amazonaws.com
docker build -t foo-image .
docker tag foo-image:v0.1.0 1234567879012.dkr.ecr.ap-northeast-1.amazonaws.com/foo-image:v0.1.0
docker push 1234567879012.dkr.ecr.ap-northeast-1.amazonaws.com/foo-image:v0.1.0

変数展開だけができればいいのであれば、envsubst で十分な場合も多いと思います。CLI の体裁として、いい感じにラップしたインタフェースが欲しいなら Template の使用も検討してみるとよいでしょう。

まとめ

Python を採用したプロジェクトでちょっとした補助ツールが欲しくなった場合などでは、こうした簡易ツールの出番があるかもしれませんね。

今回の例で示したような、環境固有の値を埋めた状態でなんらかのコマンドを実行する必要がある用途は、そこそこ適した事例のひとつだと考えています。とはいえ、そのような用途に該当するケースってだいたい Config 的な性質が強いもの(ソフトウェアの設定ファイルや、IaC テンプレートなど)が主要だと思いますので、それらを扱うツール側が似たような機能をサポートしている場合も多いように思います。一例を挙げるなら、ecspresso でも採用されている jsonnetServerless FrameworkAWS SAMCloudFormation がテンプレートファイル内でサポートしている変数 or パラメータの記法、AWS CDK の Props & Context などなど・・・。このように別のツールの仕組みで無理なく目的が達成できる場合も多いと思いますので、適切と思われる場面で使ってあげてください。

橋本 拓弥(記事一覧)

マネージドサービス部

内製開発中心にやってます。普段はサーバーレス関連や CDK を触ることが多いです