箱が…

Amazon箱ストラクチャーが崩れてきそうです。ダンボー作ろうかな。

Buildoutを使う 設定ファイル編

前回のセットアップ編ではBuildoutをインストールし buildout コマンドで環境を自動構築するところまで書きました。
ここまで読めば、誰かが書いたBuildoutの設定ファイル(buildout.cfg)を貰ってきて(Zopeとかで)Webアプリケーションの開発ができるかもしれません。

今回は buildout.cfg についての説明と、Buildout環境下でのパッケージの開発について書きます。

前回出てきた buildout.cfg の解説

まずは、セットアップ編の最後に出てきた buildout.cfg の解説をします。

buildout.cfg の構造は前回書きましたが、軽く説明しておきます。
[](大括弧)でくくられた部分が「セクション」の始まりで、セクションは次のセクションが始まるまで続きます。
セクション内には「key = value」の形で値を設定します。また、keyの種類によっては値を改行(またはスペース)区切りで列挙できるものと、1つしか設定できないものがあります。

さて、ここからが今回の内容です。

コメント

多くの設定ファイルと同じように、buildout.cfg にもコメントが書けます。

# 前回貼った buildout.cfg
[buildout]
parts =
    codehl

# このセクションは今回は使わないので後で消えます
[codehl]
recipe = zc.recipe.egg
eggs = 
    pygments

「#」の後がコメントになります。
ただし「#」は行頭に書かないといけないようで、例えば以下のように書くと

[codehl]
recipe = zc.recipe.egg # download and install eggs
eggs = 
    pygments

Buildoutは「zc.recipe.egg # download and install eggs」という名前のEggをPyPIに探しに行こうとするのでアウトです。

buildoutセクション

前回でも少し触れましたが buildout.cfg において「buildoutセクション」は特別で、Buildout全体の設定を記述するためのセクションです。
サンプルに書かれた「parts」というkey(ラベル?)はbuildoutセクションにのみ存在する設定項目で、Buildoutはここに列挙されたセクションだけを実行対象(Buildoutによる環境構築に参加できる)とします。
言い換えれば、たとえ buildout.cfg にセクションが記述されていても、partsに列挙されていなければBuildoutには見つけてもらえないということです。

[buildout]
parts =
    codehl

parts = の後に改行とスペースによるインデントが入っていますが、これは単に見やすいからそうしているだけです。
parts = の後にそのままセクション名を書いても何ら問題はありません。

# 上記と意味は同じ
[buildout]
parts = codehl

なお parts はbuildoutセクションに必須です。

レシピ

Buildoutにおけるレシピとは、セクションの「eggs」に書かれたEggをどう料理するのかを指示するMakefileみたいなものです。
レシピの実体はBuildout専用のPythonのパッケージで、buildout.cfg にレシピの識別子を書いておけば Buildout がPyPIからレシピを自動でダウンロード・インストールし、その指示に従ってセクションをいい感じに料理してくれるというわけです。

レシピは以下のサンプルのように、「recipe = [レシピの識別子]」の形で記述します。

# buildout セクションは略
[the-sample]
recipe = zc.recipe.egg
eggs =
    pyyaml
    pytz

zc.recipe.egg」はPythonのパッケージ(配布物)をダウンロード・インストールするレシピです。できることはそれだけではないんですが、今はこの理解でいいと思います。
このセクションをpartsに追加してbuildoutコマンドを実行すると、zc.recipe.egg によってpyyaml(YAMLを扱うライブラリ)とpytz(Pythonの超煩雑なタイムゾーン定義を代わりに提供してくれる素晴らしいライブラリ)がBuildout環境にインストールされます。

レシピは各セクションに1つ、必ず設定してください。書き忘れると Error: Missing option: the-sample:recipe などと言われてBuildoutが止まります。

recipe には値を1つだけ設定できます。

まだ出てきていない buildout.cfg の要素

ここからが本番です。

開発中のEgg

Buildoutは、開発中でまだ .egg として固めていないパッケージであっても扱うことができます。
それには、Egg基礎編の最後に書いたEggのdevelop installの仕組みを使います。
setup.py を使ってPythonのsite-packagesディレクトリにdevelop installするときは以下のようにしましたが

$ python setup.py develop

Buildoutで(意味的に)同じことをするには、buildout.cfg にこのように書きます。

[buildout]
parts = 
develop = /home/stackbox/code/python/deepblue/deepblue_sources

特に何もしていないので実用性はありませんが、簡単のためにこれで勘弁してください。
上記の例では、ディレクトリ・ファイル構成はこうなっています。

(以下は /home/stackbox/code/python/deepblue/ から見た構成)
+ buildout
    + bin
        - buildout
    - buildout.cfg
    + (多いので他のディレクトリは略)
+ deepblue_sources
    - setup.py
    + deepblue
        - __init__.py
        - answer.py

Buildout環境に開発中のEggをdevelop installするには、buildoutセクションの「develop」に開発中のEggの入っているディレクトリ(直下にsetup.pyを含む)へのパスを指定します。
これによって、普通のセクションの「eggs」に、このパッケージの識別子(setup.pyで定義した)である「deepblue」を指定できるようになりました。

develop には値が複数列挙できるので、開発中のEggが複数必要なときにはそれぞれのディレクトリを指定することになるでしょう。

上記の例ではdevelopをフルパスで指定しましたが、相対パスでも問題はないようです。
ただ、どのディレクトリを基準とした相対パスなのかは私は知りません。

他のセクションの値の参照

実は、partsに列挙しないセクションを buildout.cfg に書く意味はちゃんとあります。
以下の buildout.cfg を見てください。

[buildout]
parts =
    twitter-bot
    irc-bot
develop-basedir = /home/stackbox/code/python/bots
develop = 
    ${buildout:develop-basedir}/twitter
    ${buildout:develop-basedir}/irc

[libs]
recipe = zc.recipe.egg
eggs =
    pyyaml
    pytranslate

[twitter-bot]
recipe = zc.recipe.egg
eggs =
    ${libs:eggs}
    my.twitterbot

[irc-bot]
recipe = zc.recipe.egg
eggs =
    ${libs:eggs}
    my.ircbot

えーと、まず my.twitterbot と my.ircbot は私のでっち上げなのでこの buildout.cfg は嘘っぱちです。
ですが、ちゃんと似たような用途のBOTを2つ作るつもりで考えてみました。以下に解説を書きます。

まず、my.twitterbotとmy.ircbotは開発中のEggです。そのためbuildoutセクションのdevelopに、それぞれのパッケージの入っているディレクトリを指定しています。
その際に、私が勝手に作った「develop-basedir」にパスの共通部分を書いて、「develop」から「develop-basedir」を参照し、2つのBOTのパスを記述しました。値の参照は「${セクション名:参照したいkey名}」と書きます。

2つのBOTは「TwitterまたはIRC(の特定のチャンネル)を監視して『pytranslate』で日本語に翻訳し『pyyaml』でYAMLに出力する」という想定なので、共通するライブラリをlibsセクションに記述して、それぞれのBOTのセクション(partsに列挙されている)から参照しています。

自信満々な感じで書きましたが、libsセクションにまとめるところは「一応こんな使い方もある」という程度に覚えておいて頂ければと思います。
Buildoutの想定外の使い方かもしれないですからね!

解説の途中にさらっと書いてしまいましたが、あるセクション内の値を参照したいときは

foobar = ${セクション名:参照したいkey名}

というフォーマットで記述します。

buildoutセクションの特別な値

buildoutセクションには、参照および値の変更が可能な隠された(?)key名があります。
なんだか公式ドキュメントにもちゃんと載っていないようですし、Undocumentedなんだと思いますが、多用している buildout.cfg を見たことがあるのでここに載せてしまいます。

参照方法 内容の説明
${buildout:directory} buildout.cfgのあるディレクトリ(※1)
${buildout:bin-directory} bin ディレクトリ(コマンド等が生成されるところ)
${buildout:parts-directory} parts ディレクトリ(partsに列挙された各セクションのレシピが使う)
${buildout:eggs-directory} eggs ディレクトリ(DLされたEggがキャッシュされるところ)
${buildout:develop-eggs-directory} develop-eggs ディレクトリ(開発中のEggへのリンクを入れるところ)

※1:Buildoutのコードを読んだところ、buildout.cfgが読み込まれてから値が(Buildout内部で)設定されているようなので、Read Onlyなkeyとして見るべきだと思います。

特に理由がない限り、これらの値を変更しない方がきっと無難でしょう。

また、こういう隠蔽されている(?)keyは他のセクションには存在しないと思われます。

私がこれらのkeyを見つけたときは、buildout.cfg に書かれた値がレシピによって変更(設定した相対パス絶対パスに置き換わる等)されることもあるのでは、などと色々と疑心暗鬼になりましたが、そういうことをするレシピはまだ見たことがありません。
変更されないと信じたいです。

ちゃんとした例

最後に、ちゃんと動き、ちゃんと意味のあるコードと buildout.cfg の例を示します。
もちろん解説付きで。

buildout.cfg

[buildout]
parts =
    run-test
    make-interpreter
# ここだけdeepblueのsetup.pyが入っているディレクトリに書き換えてください
develop = /path/to/deepblue_sources

# deepblue が import できるように調整されたPythonインタープリタを作る
[make-interpreter]
recipe = zc.recipe.egg:script
eggs =
    deepblue
# Pythonインタープリタのファイル名を指定する
# これがないとセクション名が流用されてしまう
interpreter = earth

# Pythonのテストフレームワーク nose のコマンド
# nosetests が deepblue にアクセスできるようにする
# Buildout環境の特殊な事情により、nosetests を使うにはこのレシピが必要
[run-test]
recipe = pbp.recipe.noserunner
eggs =
    deepblue
# nosetests を実行するときに、ここに一時的にcdする
working-directory = ${buildout:develop}

この buildout.cfg の意味は以下のとおりです

インストール手順:

  1. テストや新機能を追加した deepblue 0.1.0 のアーカイブをダウンロードして展開しましょう
  2. Buildout環境を用意(新規でも使い回しでも可)したら、上記の buildout.cfg をローカルの buildout.cfg に上書きします
  3. ローカルの buildout.cfg の「develop」のところだけ修正してください
  4. 「Buildoutディレクトリ」で、コマンド bin/buildout を実行し環境を構築

環境ができたら、とりあえずテストを実行してみましょう。

$ ls
bin          buildout.cfg eggs
bootstrap.py develop-eggs parts
$ bin/run-test
..
----------------------------------------------------------------------
Ran 2 tests in 0.381s

テストは2つしか書いていないので、全てのテストが成功しました。

いよいよPythonインタープリタ(ファイル名:earth)から実行します。

$ bin/earth

>>> import deepblue
>>> deepblue.get_answer()
'The Answer to life, the universe, and everything: 42'
>>> deepblue.get_answer(from_google=True)
'The Answer to life, the universe, and everything: 42'

2つめの関数呼び出しはGoogle電卓にクエリを投げて、帰ってきたHTMLから『答え』をBeautifulSoupで切り出し、文字列にして返しています。
あまり連続で実行すると怒られそうなのでほどほどに。

おわりに

ここまでで私が知っている Buildout の情報とノウハウはほとんど出し尽くしました。
過剰なくらいに Why 分をマシマシにしたので、Buildoutがさっぱりわからない方でも何とかなるんじゃないでしょうか?

余談ですが、半年前の自分にプレゼントしたらテンション上がって走り回るくらいの内容になったと思います。
タイムマシンをお持ちの方はご連絡お待ちしております。


# それにしても…今回は長すぎるだろ…常識的に考えて…