TurboGears1.0.2.2をmod_pythonで起動する

このエントリは2007/06/22の再掲です

TurboGearsの1.0と0.9の境目、そして、CherryPyの2と3の境目で、TurboGearsmod_pythonで起動させる方法に違いがあり、どれが現在有効で、かつ標準的な方法なのか、どうにもよくわかりません。

いろいろと試した結果、 ここの記述が現行のリリースバージョンで実際に動くものにもっとも近いものでした。modpython_gateway.pyという橋渡しモジュールも、オリジナルのSVNリポジトリ参照と、スナップショットのコピーの両方を置いてあるので安心です。

http://docs.turbogears.org/1.0/mod_python

が、これとて、そのまま信用するといくつもの落とし穴があるので、やはりかなり手間がかかります。実際に私が動かしてみて、発見したことを列挙します。

本家のサンプルコードの嘘

サイトに紹介されている起動スクリプトの例、myapp_modpython.pyは、そのまま流用してパス等を書き換えただけではうまく動きません。turbogears.update_configの呼び出しは、configfileと modulename引数を同時に指定する必要があります。分割記述されたTurboGears設定ファイルは、相互に依存関係があるので、いちどにパースしないと矛盾なく読み込むことができないようです。tg-adminに生成されたstart-*.pyを参考にし、以下のように修正して使いましょう。

turbogears.update_config(
    configfile="/home/PUB/www/myserverorg/myapp/myapp.cfg",
    modulename="myapp.config")

また、このスクリプトがPythonのシステムパスで発見できるよう、setup.pyでインストールされていないといけないような書き方がなされていますが、mod_pythonのPythonPathディレクティブを使えば、「Pythonにモジュールインストールすることなく」Apacheにこのファイルを発見させることが可能です。

PythonPath sys.path+['/home/PUB/www/myserverorg/myapp']

自動リロードは禁止

TurboGearsを自動リロードにしていると、リクエスト処理中にいったんPythonインタプリタが強制終了してしまって、エラーとなります。自動リロードはかならずオフです。

autoreload.on = False

LocationでURIに割り当てるさいの注意

httpd.confで、TurboGearsアプリケーションを特定のURIに割り当てるには、以下のようなディレクティブ設定が必要です。

<Location /myapp>
    SetHandler python-program
    PythonHandler modpython_gateway::handler
    PythonOption wsgi.application cherrypy._cpwsgi::wsgiApp
    PythonFixupHandler myapp_modpython
    PythonPath sys.path+['/home/PUB/www/myserverorg/myapp']
    PythonDebug on
</Location>

さてこのとき、 mod_rewriteでもmod_proxyでも同様ですが、TurboGearsの設定ファイルには、以下の行が必要になるそうです。

base_url_filter.on = True

サイトでは何も触れていませんが、この設定の結果、アプリケーションのルートが/myappとなってしまうので、前提として、アプリケーションのベースディレクトリを設定ファイルで明示的に示しておかないといけません。そうしないと出力ページのハイパーリンクが全滅します。もちろん、Kidテンプレートに書くサイトの内部リンクはすべて、${tg.url('...')}で囲んである必要がありますよ。

server.webpath="/myapp"

カレントディレクトリの混乱

mod_pythonTurboGearsの起動者となった場合、他のあらゆる方法で起動された場合と、設定ファイル記述に関して、大きく条件が異なることがあります。それは、設定ファイルで使われる %(current_dir_uri)s が、TurboGearsを起動させた時点での、シェルのカレントディレクトリとなるということです。

開発用のスタンドアローン起動した場合も、mod_rewrite/mod_proxy でApacheの背後に隠す場合でも、どちらにしても、シェルの初期カレントディレクトリは起動者の意思で固定することができます。が、 mod_pythonに起動を任せてしまうと、ユーザの意思でカレントディレクトリを固定することができません。その結果、% (current_dir_uri)s が他の場合と変わってしまいます。

TurboGearsが設定ファイル用にカレントディレクトリを調べるのに使う、turbogears.config.config_defaults関数を、設定ファイルのパース前にオーバーライドしてしまうことで、仮想カレントディレクトリを与えておきます。便利な変数としてのcurrent_dir_uriが、どんな状況でも等価なものとして使えて欲しいものですね。というわけで、私が使っているmyapp_modpython.pyの雛形は、ざっとこんな形になりました。1.0.2.2以降、いつまで使えるかわかりませんが。

import pkg_resources
pkg_resources.require("TurboGears")

#Set this to absolute app home path
virtual_current_uri = '/home/PUB/www/myserverorg/myapp'

import cherrypy
import turbogears

#Override TurboGear's configuration function
original_config_defaults = turbogears.config.config_defaults
def overrided_config_default():
    config_default = original_config_defaults()
    config_default.update({'current_dir_uri' : virtual_current_uri})
    return config_default
turbogears.config.config_defaults = overrided_config_default

turbogears.update_config(
    configfile=virtual_current_uri+"/prod.cfg",
    modulename="wikitest.config"
)

from myapp.controllers import Root
cherrypy.root = Root()
cherrypy.server.start(initOnly=True, serverClass=None)

def fixuphandler(req):
    return 0

設定ファイルでcurrent_dir_uriが使えるということこだわらなければ、ここまでハックする必要はないでしょうけど。

国際化リソースのデフォルトパス問題

デフォルトのTurboGearsアプリケーションには、国際化文字列リソースの格納場所を設定してありませんが、なんとなく動きます。しかし、バージョン 1.0.2.2の時点では、TurboGearsが稼動している「本当のカレントディレクトリ」にlocalesディレクトリがないと、この国際化機能が働かないという実装仕様になっています。

(turbogears.i18n.tg_gettext参照)

def is_locale_supported(locale, domain=None):
    # :
    localedir = turbogears.config.get("i18n.locale_dir", "locales")
    return os.path.exists(os.path.join(localedir, locale, "LC_MESSAGES", "%s.mo" %domain))

たしかに、この実装によれば、デフォルト値は「本当のカレントディレクトリ」にlocalesがあることを期待しています。つまり、上述の current_dir_uri設定値のハッキングだけでは、解決しないのです。仕方がないのでデフォルトをあきらめ、設定ファイルに明示的に i18n.locale_dirを追加し、国際化文字列リソースの格納パスを設定しましょう。

i18n.locale_dir="%(current_dir_uri)s/locales"

あるいは、 %(current_dir_uri)s をフルパスで。

ログ出力先を標準出力からファイルに

これはもうサイトのドキュメントにあるとおり、Apacheというデーモンには標準出力がないので、取りたいログはファイルに出力するようにしておく必要があります。

[[[access_out]]]
# set the filename as the first argument below
args="('/usr/local/apache2/logs/myapp.log',)"
class='FileHandler'
level='INFO'
formatter='message_only'

複数のアプリケーションを共存させる場合に考えておくこと

これは未調査ですが、十分に考慮すべきポイントです。滅多にないことだとは思いますが、もし、ひとつのドメインの異なるサブパスに、複数のTurboGearsアプリケーションを配置するようなことがあった場合、お互いのアプリケーションが侵食し合うことが考えられます。­それぞれのLocationで PythonInterpreterディレクティブを使って、個別のインタプリタで動くようにするなどの工夫が有効かもしれません。