よちよちpython

独習 python/Qpython/Pydroid3/termux/Linux

テンプレートエンジンとは何か?Jinja2の基本4

前回テンプレートエンジンとは何か?Jinja2の基本3 - よちよちpython の続き。Jinja2の単体機能を見ていくシリーズ4回目。

前回はテンプレートファイルをロードする側のPythonコードによるAPIの基本形を、公式サイトのサンプルコードを使って見ていこうとした。
これが大失敗(笑)。というのは、テンプレートファイルをロードする際に使うクラスの一つであるPackageLoader()は、アプリケーション全体をパッケージとして作っている場合に使うみたいなんだが、吾が輩はそうとも知らず、しかも自分がパッケージを作る方法を知らない事を完全に無視していた。入門書は「初心者の分際がパッケージを作ろうなんざ100年早ぇえんだよ、てやんでぇ!」ってな感じか、パッケージ作成法をあまり詳しく載せていない。 んなこたない ┐(-。-;)┌
パッケージ化できるということは自作のライブラリをローカルでインストールしたりPYPIgithubで配布したりできるんだろう?おお、これは熱いじゃないですか!シビレる。街を歩けなくなるぞ!



今回はPackageLoaderとは別の方法でテンプレートファイルをロードするやり方を見ていく。1,2回目でチラリと使ってるので復習。

目次


実行環境


Androidスマホ
termux
Python3.7
JupyterNotebook



前回つまずいたポイント


次のようなコードでつまずいた。jinja2の公式サイトのAPI項目の先頭に載っている罠。

from jinja2 import Environment, PackageLoader

env = Environment(loader=PackageLoader('yourapplication', 'templates'))
template = env.get_template()
print(template.render(name='なにがし', membership_num='777'))

↑のコードのPackageLoader()。くせ者。テンプレートファイルが保存されたフォルダを参照する箇所。パッケージ化されていないとエラーが出る。__init__.pyの使い方だとかを知っていなければならないようだ。



FileSystemLoaderによるロード


FileSystemLoader()はパッケージ化なしでテンプレートファイルの保存フォルダを参照させることができて分かりやすい。

適当にフォルダとテンプレートファイルを作成します。



APIファイル作成


注 : %%writefileの行はJupyterNotebookのマジックコマンドです。ファイルを作成しています。

%%writefile Jinja2Sample/app_sample.py

from jinja2 import Environment, FileSystemLoader
import datetime

env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template('sample_template.j2')

#本日の日時を取得
today = datetime.datetime.now()
year,month,day = today.year, today.month, today.day

print(template.render(year=year, month=month,day=day))
Writing Jinja2Sample/app_sample.py



ディレクトリ構造


またJupyterNotebookでコマンド実行します。

カレントディレクトリの下の階層にJinja2Sampleフォルダ以下を作成した。
後にこのカレントディレクトリで引っ掛かるので意識しておくこと。

!tree Jinja2Sample
Jinja2Sample
├── app_sample.py
└── templates
    └── sample_template.j2

1 directory, 2 files



テンプレートファイルの中身


前もって作っておきました。

!cat Jinja2Sample/templates/sample_template.j2
本日は{{year}}年{{month}}月{{day}}日

実行(要注意)


app_sample.pyを実行
カレントディレクトリをJinja2Sampleフォルダに移してから、実行する。
何度も書くが、カレントディレクトが重要!

!cd Jinja2Sample;python app_sample.py
本日は2019年10月26日

おー。素晴らしい。

説明。env = Environment(loader=FileSystemLoader('templates'))にあるtemplatesフォルダは、実行するカレントディレクトリからの相対パスを書く。

カレントディレクトリ! カレントディレクトリ!

↑のenvで取得したフォルダ内に置いたテンプレートをtemplate = env.get_template('sample_template.j2')で取得する。get_template()メソッドにテンプレートのファイル名を渡す。

today =からの2行は変数を定義している。datetimeモジュールで年月日を取得。



最後にrender()に変数を渡してテンプレート内の変数を置き換えてprint()で出力。



要注意


カレントディレクトリ!をいくども叫んで来たのは次のような訳がある。

カレントディレクトリの下の階層に、Jinja2Sample以下を作った。
現在のカレントディレクトリはJinja2Sampleの上にいる。
↓の図の外に居ます。はっはっは

!tree Jinja2Sample
Jinja2Sample
├── app_sample.py
└── templates
    └── sample_template.j2

1 directory, 2 files



実行時にcdコマンドで先にカレントディレクトリを移したが、移動せず相対パスで実行ファイルを実行するとどうなるか?



こうなる

!python Jinja2Sample/app_sample.py
Traceback (most recent call last):
  File "Jinja2Sample/app_sample.py", line 6, in <module>
    template = env.get_template('sample_template.j2')
  File "/data/data/com.termux/files/home/python3.7/lib/python3.7/site-packages/jinja2/environment.py", line 830, in get_template
    return self._load_template(name, self.make_globals(globals))
  File "/data/data/com.termux/files/home/python3.7/lib/python3.7/site-packages/jinja2/environment.py", line 804, in _load_template
    template = self.loader.load(self, name, globals)
  File "/data/data/com.termux/files/home/python3.7/lib/python3.7/site-packages/jinja2/loaders.py", line 113, in load
    source, filename, uptodate = self.get_source(environment, name)
  File "/data/data/com.termux/files/home/python3.7/lib/python3.7/site-packages/jinja2/loaders.py", line 187, in get_source
    raise TemplateNotFound(template)
jinja2.exceptions.TemplateNotFound: sample_template.j2

env = Environment(loader=FileSystemLoader('templates'))で指定するテンプレートを収めたフォルダは、「カレントディレクトリからの相対パスでなければならない。

変なカレントディレクトリから相対パスで実行ファイルを実行しても、そのディレクトリ内にFileSystemLoader()で指定したフォルダ(この場合'templates')が無いので、その中にある筈のテンプレートファイルがありませんよとエラーが出る。



対処としては、さきにやったようにカレントディレクトリを移動させて実行するか、Pythonコード中にカレントディレクトリを移動させるコードを書いておくか、 またはJinja2は作業ディレクトリとは別の場所からテンプレートをロードします - コードログ (英語。翻訳して読んで) のようにテンプレートフォルダを絶対パスで指定する。これはフォルダごと移動させたりするとパスが狂うので使えなくなるけど。
そこで再び注目を浴びるのがパッケージ化とPackageLoader()の使用(笑)



ここからは解説サイトで頻出の高いものを幾つかサラッと触れておこう。



encodingの指定


Environment()のオプション。

env = Environment(loader=FileSystemLoader(フォルダ名, encoding='utf-8')
テンプレートの文字エンコード。こういうのを指定せずにWindowsでやると大概文字化けするでしょ。知らんけど。



trim_blocksとlstrip_blocks 改行と空白の処理


Environment()のオプション。

env = Environment(loader=FileSystemLoader('.'), trim_blocks=True) のように使う。デフォルトで共にFalse。



テンプレート内で使う{% if ... %}や{% for ... %}等は出力されると空白に置き換わる。空白行が出来てしまう。それらの空白をなくし詰めて出力したい場合に使う。

  • trim_blocks:{}ブロックの後ろの改行を削除
  • lstrip_blocks:{}ブロックの前のスペースを削除


また、テンプレート内のブロックに{%- if ... %}や{% if ...-%}という風に-をつけても同じ効果が出る。こちらの方がより細やかな制御が可能。

こちらで実験されています。
Jinja2のWhitespace Controlを理解して、きれいなファイルを出力する - grep Tips *



select_autoescape


Environment()のオプション。
自動エスケープ機能。htmlはデフォルトでTrue。

env = Environment(loader=FileSystemLoader('templates'),autoescape=select_autoescape(['html', 'xml'])) のように使う。



参考


API — Jinja Documentation (2.10.x)



今回得たものは、
「FileSystemLoader()を使うときはアプリケーション実行のカレントディレクトリに気を付けろ!」
でした。

以上。