fanstatic による Python パッケージを使った静的リソース管理

最近 Kotti で Web アプリを作ってみようと調査しています。但し、今日は Kotti のお話ではなく、たまたま更新差分を見ていたら、fanstatic という静的リソース管理ツールが新規に使われているのを見つけました。ちょっと調べてみると、とても良さそうに見えたので紹介します。

fanstatic って何?

特にドキュメントでパッケージ名の由来を見つけられなかったのですが、fan + static と区切ってみると名前を覚えやすいです。個人的に fan- という係りが fancy, fantasy, fantasista などを連想させて言葉の響きが良いですね。static リソースの管理の煩雑さを解消してくれる夢のようなツールを連想します。

さて、fanstatic は、スマートな静的リソースパブリッシャーとあります。

テンプレートに静的リソースの記述をインジェクションしてくれるので、パブリッシャーという表現をしているのでしょうが、それよりも javascriptcss といった静的リソースを Python パッケージとして管理できる仕組みに私は驚きました。

パッケージ、ドキュメント、リポジトリはそれぞれ以下になります。

fanstatic で jquery を使ってみよう

fanstatic を使うと、どんなことができるか、クイックスタート を参考にしながら見てみましょう。

まずは fanstatic と js.jquery というパッケージをインストールします。

(fanstatic)$ pip install fanstatic js.jquery
(fanstatic)$ pip freeze
Paste==1.7.5.1
WebOb==1.2
distribute==0.6.24
fanstatic==0.11.4
js.jquery==1.7.1
wsgiref==0.1.2

簡単なサンプルコードから紹介します。

(fanstatic)$ vi quick.py 
# -*- coding: utf-8 -*-
from fanstatic import Fanstatic
from js.jquery import jquery

def app(environ, start_response):
    start_response('200 OK', [])
    jquery.need()
    return ['<html><head></head><body>Hello World</body></html>']

if __name__ == "__main__":
    from wsgiref.simple_server import make_server
    fanstatic_app = Fanstatic(app)
    server = make_server('0.0.0.0', 8080, fanstatic_app)
    server.serve_forever()

通常の WSGI アプリを作成して、2行だけ追加します。jquery.need() は、HTML の <head> セクションに <script> タグを埋め込みます。あとは FanstaticWSGI アプリをラップするだけです。

...
    jquery.need()
...
    fanstatic_app = Fanstatic(app)
...

実行結果を見てみましょう。

(fanstatic)$ python quick.py 

別のターミナルで
(fanstatic)$ telnet localhost 8080 
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /

<html><head>
    <script type="text/javascript" src="/fanstatic/jquery/jquery.js"></script>
</head><body>Hello World</body></html>

ちゃんと <head> セクションに jquery の <script> タグが埋め込まれていますね。

作業前に js.jquery を pip でインストールしただけで使えるのが便利です。jquery の最新版をダウンロードしてきて、どこそこに展開して、パス設定はどうしようかな、、、と悩まなくて済むのが凄いところです。また、jquery のような、汎用的なライブラリは、みんなで共有してバージョンアップもパッケージ管理ツール (今回は pip) で行えた方が楽で良いですね。

任意のアプリから静的リソースを管理する

もう少し現実的なアプリケーションについて、リソースライブラリの作成 から見てみましょう。

ここでは静的リソースを含む Python パッケージを作成してみます。まずパッケージディレクトリとひな形を作成します。

(fanstatic)$ mkdir myapp
(fanstatic)$ cd myapp
(fanstatic)$ mkdir -p foo/static
(fanstatic)$ touch foo/__init__.py
(fanstatic)$ touch foo/static/base.css
(fanstatic)$ touch foo/static/base.js

次にパッケージの setup.py を定義します。

(fanstatic)$ vi setup.py
# -*- coding: utf-8 -*-
from setuptools import setup

setup(
    name="myapp",
    version="0.1",
    install_requires=["fanstatic", "js.jquery"],
    entry_points={
        "fanstatic.libraries": [
            "foo = foo.static:lib_foo",
        ],
    },
)

install_requires に fanstatic と使いたい js ライブラリを記述します。ここでは js.jquery のみを記述します。fanstatic がパッケージ内の静的リソースの場所を見つけられるように entry_points を使って定義します。ここが1つのポイントです。

では、先ほど作成した static ディレクトリを fanstatic から見つけられるように static.py に定義します。サンプルコードから紹介します。

(fanstatic)$ vi foo/static.py
from fanstatic import Group, Library, Resource
from js.jquery import jquery

_resources = [jquery]

lib_foo = Library("foo", "static")
foo_js = Resource(lib_foo, "base.js", depends=[jquery], bottom=True)
_resources.append(foo_js)

foo_css = Resource(lib_foo, "base.css", bottom=True)
_resources.append(foo_css)

resources = Group(_resources)

大体見た感じで雰囲気は掴めますが、詳細に見て行きます。

lib_foo = Library("foo", "static")

Library オブジェクトは、名前と静的リソースの置き場所へのパスを引数に取ります。ここで lib_foo は、setup.py の entry_points で定義した名前を使う必要があるのに注意してください。

foo_js = Resource(lib_foo, "base.js", depends=[jquery], bottom=True)

Resource オブジェクトは、実際の静的リソース (js/css) を定義します。base.js は jquery を使うスクリプトなので depends=[jquery] を定義することにより、base.js より前に jquery.js が読み込まれるようにインジェクションされます。

resources = Group(_resources)

Group オブジェクトは、複数のリソースをまとめます。あとで紹介しますが、複数リソースのインジェクションを resources.need() のように実行できて便利です。

主要な点は紹介しました。実際に動かせるように残りのファイルも紹介します。

main プログラムです。せっかくなので css/js が適用されていることを確認できるように html を少し変更します。

(fanstatic)$ vi foo/main.py 
# -*- coding: utf-8 -*-
from fanstatic import Fanstatic
from static import resources as static_resources

def app(environ, start_response):
    start_response('200 OK', [])
    static_resources.need()
    html = """
    <html>
      <head></head>
      <body>
        <button type="button" id="sample_btn">Click Me!</button>
      </body>
    </html>
    """
    return [html]

if __name__ == "__main__":
    from wsgiref.simple_server import make_server
    fanstatic_app = Fanstatic(app)
    server = make_server('0.0.0.0', 8080, fanstatic_app)
    server.serve_forever()

base.css と base.js は、それぞれ以下の通りです。

(fanstatic)$ vi foo/static/base.css 
button {
    border-color: #666666;
    background-color: #E8E6E1;
    font-size: large;
    padding: 10px;
}
(fanstatic)$ vi foo/static/base.js 
$(function() {

    $("#sample_btn").click(function() {
        alert("Hello World");
    });

});

最終的なパッケージの構成です。

(fanstatic)$ tree myapp/
myapp/
├── foo
│&#160;&#160; ├── __init__.py
│&#160;&#160; ├── main.py
│&#160;&#160; ├── static
│&#160;&#160; │&#160;&#160; ├── base.css
│&#160;&#160; │&#160;&#160; └── base.js
│&#160;&#160; └── static.py
└── setup.py

2 directories, 6 files

それでは、パッケージをインストールして、実行してみましょう。

(fanstatic)$ python setup.py develop
(fanstatic)$ python foo/main.py 

ブラウザでアクセスして、ボタンをクリックすると、次のような画面を確認できます。

まとめ

fanstatic の強力さと利便性が分かる簡単なチュートリアルを紹介しました。

ここで紹介したソースは以下に置いてあります。

fanstatic リポジトリ を見ると、js. の名前空間で始まるライブラリがたくさんあります。ここに無ければ、自分でパッケージングして公開するのも良いですね。パッケージ管理の仕組みを、こんな用途にも使えるんだと再発見しました。おもしろいですね。