site-packages に悩まされず”に buildout で GoogleAppEngine の環境を作る


2010年 12月 05日

みなさん、GoogleAppEngine (GAE) で開発をしてますか?
僕も最近 Python をよく使うようになったので、GAE 用のアプリをいくつか作っています。

Python の開発環境を作る際は buildout を使うと非常に便利です。
必要なeggパッケージのインストールや svn からのチェックアウト、Apache/MySQL のセットアップなどの作業を
設定に基づいて自動的に行ってくれます。

GAE 用のアプリを作る際の設定は @shimizukawa の?Google App Engine の開発をbuildoutで行う?に細かく書いてあります。

しかし、この設定では私の環境ではそのままでは動かなくてハマってしまいました。
この設定で利用している appfy.recipe.gae:app_lib というレシピは、
指定したライブラリをGAEで利用しやすいようZIP ファイルにまとめる仕組みを提供しているのですが、
この「指定したライブラリ」が開発環境に既にインストールされていると ZIP ファイルには含まれなくなります。
そのため、ローカル環境で動作確認すると正しく動くのですが GAE 上ではライブラリが足りずにちゃんと動作しません。


これを防ぐために allowed-eggs-from-site-packages というオプションを用います。
allowed-eggs-from-site-packages は名前の通り、ローカルの site-packages の中から利用して良いパッケージを指定するものです。
このオプションを使って、ZIP ファイルに必要な全てのパッケージを含めるようにします。

※ appfy.recipe.gae のドキュメントには載っていないオプションですが、
  ベースにしている zc.recipe.egg.Scripts (がベースにしている z3c.recipe.scripts) が提供している機能です。


最終的に設定を加えた buildout.cfg はこうなります。
※ flask 用の初期設定も含めるようにしました。

[buildout]
parts = prepare debug app_lib gae_sdk gae_tools test

[prepare]
recipe = iw.recipe.cmd:py
on_install = true
cmds =
   >>> buildout_dir = buildout.get(‘directory’, ‘.’)
   >>> path = os.path.join(buildout_dir, ‘app’)
   >>> if not os.path.exists(path):
   …     os.makedirs(os.path.join(buildout_dir, ‘app’))
   …     open(os.path.join(path, ‘app.yaml’), ‘at’).write(
   …     ‘application: appname\n’
   …     ‘version: 1\n’
   …     ‘runtime: python\n’
   …     ‘api_version: 1\n’
   …     ‘handlers:\n’
   …     ‘- url: /remote_api\n’
   …     ‘  script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py\n’
   …     ‘  login: admin\n’
   …     ‘- url: .*\n’
   …     ‘  script: main.py\n’
   …     )
   …     open(os.path.join(path, ‘main.py’), ‘at’).write(
   …     “””# -*- coding: utf-8 -*-\n”””
   …     “””\n”””
   …     “””import sys; sys.path.insert(0, ‘./distlib.zip’)\n”””
   …     “””from google.appengine.ext.webapp.util import run_wsgi_app\n”””
   …     “””import werkzeug\n”””
   …     “””from flask import Flask\n”””
   …     “””app = Flask(__name__)\n”””
   …     “””\n”””
   …     “””@app.route(‘/’)\n”””
   …     “””def index():\n”””
   …     “””    return ‘hello world’\n”””
   …     “””\n”””
   …     “””if __name__ == ‘__main__’:\n”””
   …     “””    run_wsgi_app(app)\n”””
   …     )

[debug]
recipe = zc.recipe.egg:script
eggs = ipython
extra-paths =
    ${gae_tools:extra-paths}
    ${gae_tools:sdk-directory}/lib/django
    ${gae_tools:sdk-directory}/lib/webob
    ${gae_tools:sdk-directory}/lib/yaml/lib
    ${buildout:directory}/app
interpreter = py

[app_lib]
recipe = appfy.recipe.gae:app_lib
lib-directory = app/distlib
use-zipimport = true

allowed-eggs-from-site-packages =
    .
eggs =
    flask

ignore-globs =
    *.c
    *.pyc
    *.pyo
    */test
    */tests
    */testsuite
    */django
    */sqlalchemy
    simplejson/_speedups.py

ignore-packages =
    distribute
    setuptools
    easy_install
    site
    pkg_resources


[gae_sdk]
recipe = appfy.recipe.gae:sdk
url = http://googleappengine.googlecode.com/files/google_appengine_1.4.0.zip
clear-destination = true

[gae_tools]
recipe = appfy.recipe.gae:tools
sdk-directory = ${gae_sdk:destination}/google_appengine
extra-paths =
    app/lib
    app/distlib.zip
    app


[test]
recipe = pbp.recipe.noserunner
eggs =
    ${app_lib:eggs}
    nose
    nosegae
extra-paths = ${debug:extra-paths}
environment = nose-environment
defaults = –gae-application=${buildout:directory}/app

[nose-environment]
NOSE_WITH_GAE = true
NOSE_WHERE = ${buildout:directory}/app