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

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

SVNに登録

> cd c:Projectbuildoutenv1wsgiapp
> svn mkdir file:///svnpath/wsgiapp -m "mkdir"
> svn mkdir file:///svnpath/wsgiapp/trunk -m "mkdir"
> svn import . file:///svnpath/wsgiapp/trunk -m "import"

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

bootstrap.py を取得

> cd c:Projectbuildoutenv1wsgiapp
> dir /b
bootstrap.py
setup.cfg
setup.py
wsgi.ini
wsgiapp

buildout.cfg:

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

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

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

buildoutの初期ファイルをsvnに登録

> svn add buildout.cfg bootstrap.py
> svn ci -m "initial buildout files"

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

新しい環境で動作確認

> cd c:Projectbuildout
> virtualenv env2
> cd env2
> Scripts/activate
> cd wsgiapp
> python bootstrap.py
> bin/buildout
...
> bin/paster request wsgi.ini /
It's Python!
...

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

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

setup.py:

install_requires=[
  'BeautifulSoup',
],

buildoutで環境を更新する(依存パッケージを取得する)。

> buildout
...
Updating wsgiapp.
Getting distribution for 'BeautifulSoup'.
...

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

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

eggパッケージをimport出来ない

> python
Python 2.4.4 (#71, Oct 18 2006, 08:34:43) [MSC v.1310 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> import BeautifulSoup
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ImportError: No module named BeautifulSoup
>>>

そこで、関連する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コマンドを実行。

buildoutで環境を更新

> bin/buildout
Develop: 'c:\Project\buildout\env1\wsgiapp\.'
unrecognized .svn/entries format; skipping .
unrecognized .svn/entries format in
Updating wsgiapp.
Installing eggpy.
Generated interpreter 'c:\Project\buildout\env1\wsgiapp\bin\py'.

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

eggパッケージをimport出来る

> bin/py

>>> import BeautifulSoup
>>> help(BeautifulSoup)
Help on module BeautifulSoup:

NAME
BeautifulSoup
...

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)

で、動作確認。

コンソールで実行

> bin/py wsgiapp/scraper.py
<html>
...
...
</html>

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 してブラウザで確認したり。

pasterで動作確認

> bin/paster request wsgi.ini /
...
> bin/paster serve wsgi.ini
...

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

scraperをコミット

> svn add wsgiappscraper.py
> svn ci -m "add and use scraper"

ここで、さっき作った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を付けて動かしてみよう。

テストする

> bin/py wsgiapp/scraper.py
> bin/py wsgiapp/scraper.py -v
...
Test passed.

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

最後に、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で環境を更新してテストする。

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

> bin/buildout
...
Installing test.
Generated script 'c:\Project\buildout\env1\wsgiapp\bin\test'.

> bin/test
Running zope.testing.testrunner.layer.UnitTests tests:
Set up zope.testing.testrunner.layer.UnitTests in 0.000 seconds.
Ran 1 tests with 0 failures and 0 errors in 0.757 seconds.
Tearing down left over layers:
Tear down zope.testing.testrunner.layer.UnitTests in 0.000 seconds.

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

今日はここまで。

コミット

> svn add wsgiapptests.py
> svn ci -m "add test framework"