Date: 2009-07-16
Tags: python, programming, web

buildoutで開発2: buildoutで環境を整える

eggの作り方が分からない, buildoutで開発1: WSGIアプリをeggで作る の続き。buildoutでテストまで。

現時点のファイル構成:

c:\Project\buildout\env1\
 +-- wsgiapp
     +-- setup.cfg
     +-- setup.py
     +-- wsgi.ini
     +-- wsgiapp.egg-info
     |   +-- *.*
     +-- wsgiapp
         +-- __init__.py
         +-- __init__.pyc
         +-- startup.py
         +-- startup.pyc

とりあえずコミットしておく。 *.pycと*.egg-info は自動生成されるファイルなのでコミットしない。 Subversion上に以下のようになるようにコミットして、trunkをチェックアウト。

リポジトリの内容はこんな感じ:

svn://
 +-- wsgiapp
     +-- trunk
         +-- setup.cfg
         +-- setup.py
         +-- wsgi.ini
         +-- wsgiapp
             +-- __init__.py
             +-- startup.py

以降、ソースコード管理に登録する手順を何度か書いてますが、以降の作業では、登録するべきファイルとそうでないファイルが混在し始めるため、必要なファイルを分かりやすくするためにコミットしてます。

閑話休題。buildoutを使うための環境を整える。

buildout.cfg:

[buildout]
develop = .
parts = wsgiapp
newest = true

[wsgiapp]
recipe = zc.recipe.egg
eggs =
    wsgiapp
    Paste
    PasteDeploy
    PasteScript

ここまでで一度コミット。

おもむろに違う環境を作ってみる。別のコンソールを開くと良いと思う。

この環境ではvirtualenv と setuptoolsが使える以外は何も入っていない。 bootstrap.py -> buildout -> setuptools... と関連して色々ダウンロードされて、環境が構築される。

ここで、元の環境にもどって、wsgiappに関連パッケージを定義する。 パッケージの関連づけはbuildoutとしてではなく、eggとして対応する。 このため setup.py を以下のように更新する。

setup.py:

install_requires=[
  'BeautifulSoup',
],

ここで、wsgiapp.egg-info/requires.txt を見ると、ちゃんとBeautifulSoupに依存しているという定義にUpdateされている。

これからBeautifulSoupを使うような実装を追加したいが、eggで追加されたパッケージの動作を確認したり、ヘルプを見たりするのにインタラクティブシェルからBeautifulSoupを呼び出したい。 でも、buildoutで関連づけられたeggパッケージはPythonにインストールされているわけではないので、そのままでは呼び出せない。

そこで、関連するeggを使える状態でPythonを起動するスクリプトを作成する。スクリプトの名前は適当にpyとしておきます。 スクリプトの用意は、以下のようにbuildout.cfgを書き換えれば、 あとはbuildoutがやってくれる。

buildout.cfg:

[buildout]
develop = .
parts = wsgiapp eggpy
newest = true

[wsgiapp]
recipe = zc.recipe.egg
eggs =
    wsgiapp
    Paste
    PasteDeploy
    PasteScript

[eggpy]
recipe = zc.recipe.egg
eggs = ${wsgiapp:eggs}
interpreter = py
scripts = py

[eggpy] セクションを追加して、そのセクションがbuild対象であることをbuildoutに伝えるために、 parts = にeggpyを追加。 eggpyの中で、利用したいeggの指定はwsgiappと同じ内容で良いけど、それをまた書くのは面倒なので、 ${wsgiapp:eggs} という感じで変数で指定。

この内容で環境を更新するために、buildoutコマンドを実行。

作られたpyコマンドでインタラクティブシェルを起動して、eggパッケージを呼び出せることを確認。

OK.

BeautifulSoupを使ったWSGIアプリの実装部分関数を作る。とりあえずWSGIとか関係なく、与えられたURLをGETして、hrefの値を書き換えて返す関数を実装。動作確認用に、コンソールから実行された場合の動作も実装しておく。

wsgiapp/scraper.py:

# -*- coding: utf-8 -*-
import urllib2
from BeautifulSoup import BeautifulSoup

def modifyLinks(url):
    bs = BeautifulSoup(urllib2.urlopen(url))
    for elem in bs.findAll('a'):
        if elem.has_key('href'):
            elem['href'] += "#foobar"

    return bs.prettify()


if __name__ == '__main__':
    import sys
    if len(sys.argv) > 1:
        url = sys.argv[1]
    else:
        url = "http://pypi.python.org/simple/BeautifulSoup/"
    print modifyLinks(url)

で、動作確認。

OK. ちゃんと#foobarが追加されてた。 これをwsgiappとして組み込む。

wsgiapp/startup.py:

# -*- coding: utf-8 -*-
import scraper

def application(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type', 'text/html')]
    start_response(status, response_headers)
    return [scraper.modifyLinks(
        "http://pypi.python.org/simple/BeautifulSoup/"
    )]

def application_factory(global_conf):
    return application

うまく動くか、pasterコマンドでrequestして確認したり、paster serve してブラウザで確認したり。

ここまでをとりあえず、コミット。

ここで、さっき作ったscraperのテスト方法が気に入らないので、書き換えてみる。

wsgiapp/scraper.py:

# -*- coding: utf-8 -*-
import urllib2
from BeautifulSoup import BeautifulSoup

def modifyLinks(url):
    """modifyLinks get content from given url and modify href attributes.

       >>> content = modifyLinks("http://pypi.python.org/simple/BeautifulSoup/")
       >>> '#foobar"' in content
       True
    """
    bs = BeautifulSoup(urllib2.urlopen(url))
    for elem in bs.findAll('a'):
        if elem.has_key('href'):
            elem['href'] += "#foobar"

    return bs.prettify()


if __name__ == '__main__':
    import doctest
    doctest.testmod()

で、改めてテスト。エラー無くテストが成功した場合は、 -v オプション無しだと何も表示されないので、心配なら-vを付けて動かしてみよう。

テストが通ったので、コミット。

最後に、buildoutで全モジュールを自動的にテストするためのスクリプトを用意する。まず、DocTestを外から呼び出すためにtests.pyを用意。

wsgiapp/tests.py:

# -*- coding: utf-8 -*-

import unittest
from doctest import DocTestSuite

def test_suite():
    return unittest.TestSuite((
        DocTestSuite('wsgiapp.scraper'),
    ))

if __name__ == '__main__':
    unittest.main()

次に、biuldout.cfgでテスト実行スクリプトを生成。 [test] セクションを追加して、partsにtestセクションの呼び出しを追加。使っているレシピが今までと違ってzc.recipe.testrunnerであることと、テスト対象にPaste等を含めたくなかったので、${wsgiapp:eggs}は使わなかったところがポイント。

buildout.cfg:

...
parts = wsgiapp eggpy test
...

[test]
recipe = zc.recipe.testrunner
eggs = wsgiapp
relative-paths = true

buildoutで環境を更新してテストする。

ZopeのTestRunnerが使われるけど、気にしない方向で。 bin/test -h でコマンドラインオプションもみれるよ。

今日はここまで。

参考