Zipimortを使ってApp EngineでDjango1.0を利用する

"Using Django 1.0 on App Engine with Zipimport"の日本語訳。訳がおかしいところはコメントください。

はじめに

App EngineのアプリケーションでPython Webアプリケーションフレームワークを使うことは、アプリケーションフレームワークで使っているコードを取り込むぐらい簡単です。しかし、アプリケーションがアップロードできるファイルには制限があり、いくつかのフレームワークで制限を超えてしまう場合や、アプリケーションコードを書く余裕がない場合があります。この場合、App Engine リリース 1.1.3からサポートされているPythonの"zipimport" で解決することができます。

この記事では、Google App Engineに"zipimport"を使ってDjango 1.0を使用するための解説をします。

Zipimortの導入

アプリケーションにモジュールをインポートする際、Pythonはいくつかのディレクトリーの中からそのモジュールのコードを探します。PythonコードからPythonチェックするディレクトリーのリストを変えるにはsys.pathを使用します。App Engineがインクルドするパスは、App Engine APIとアプリケーションのルートディレクトリーです。

sys.pathのアイテムでZipフォーマットのアーカイブを指定することで、Pythonはそのアーカイブをディレクトリーとして扱います。アーカイブは1つ以上のモジュールを含む.pyソースコードを含みます。この機能はzipimportという標準ライブラリーのモジュールでサポートされており、このモジュールはデフォルトのインポートプロセスの1つであるため、このモジュールを使うために直接このモジュールをインポートする必要はありません。zipimportに関する詳しい情報に関しては、zipimportドキュメントを参照してください。

App Engineでモジュールアーカイブを使う方法:

  • バンドルしたいモジュールのZipフォーマットのアーカイブを作る
  • アプリケーションディレクトリーにアーカイブを入れる
  • 必要な場合はハンドルしたスクリプトをsys.pathに追加する

以下のようなファイルが入っている、django.zipというアーカイブの場合:

django/forms/__init__.pydjango/forms/fields.pydjango/forms/forms.pydjango/forms/formsets.pydjango/forms/models.py...

ハンドルしたスクリプトは以下のようにしてアーカイブからインポートします:

import syssys.path.insert(0, 'django.zip')import django.forms.fields

注意:このzipimportの解説例では、App EngineでDjango 1.0をロードするためには十分ではありません。より詳細な例は以下のとおりです。

zipimportとApp Engine

App Engineでは標準実装のzipimport機能ではなくカスタムバージョンを使用します。通常の動作: sys.pathにZipアーカイブを追加し、通常通りインポートします。

これはカスタム実装であるため、いくつかのドキュメントに記載されていない機能正式書類のない機能は動作しません。たとえば、App Engineはアーカイブから.pyはロードできますが、標準バージョンのように.pycファイルはロードできません。SDKでは標準版が使われているので、zipimportの正式書類のない機能を使用する場合は、App Engineで必ずテストしてください。

現在、App Engineのキャッシュロジックには、リクエストの度にsys.pathがリセットされるというバグが含まれているため、インポートした後にキャッシュしてください。ハンドラスクリプトが最初にロードされた時に、アプリケーションがすべてのモジュールをインポートしていることが確実でない場合、ハンドラーのmain()ルーチンはsys.pathの回復が必要です。

import sysdjango_path = 'django.zip'sys.path.insert(0, django_path)def main():  if django_path not in sys.path:    sys.path.insert(0, django_path)
Django 1.0のアーカイブ

App Engineは2008年夏にローンチし、スタートが簡単になるように環境の一部としてDjangoアプリケーションフレームワークを含んでいます。当時Djangoの最新リリースが0.96であったので、それがPythonランタイム環境のバージョン"1"となっています。それ以降、Djangoプロジェクトはバージョン1.0をリリースしました。互換性の問題で、App EngineはPythonランタイム環境にこのバージョンのDjangoにアップデートできません。1.0をApp Engineのランタイム環境のバージョン"1"で利用するためには、アプリケーションのディレクトリーに1.0をインクルードしなければなりません。

Django 1.0ディストリビューションには、1,582ファイルが含まれています。App Engineのアプリケーションのファイル数は1,000個に制限されているため、直接このディストリビューションをインクルードすることはできません。もちろん、ディストリビューション内の全てのファイルが必要というわけではありません。ファイル数を減らすために、ドキュメントファイル、未使用のロケール、データベースのインターフェース、その他App Engine上で動作しない(例えばAdminアプリケーション)コンポーネントをディストリビューションから除くことができます。zipimportを使うと、たった1つのファイルでDjango 1.0をアプリケーションで使うことができます。しかし、Django 1.0の1つのZipアーカイブファイルは3 MB以上ありますが、各アプリケーションのファイルは1MBをを超えることはできません。最も簡単な解決策は、未使用のロケールとコンポーネント以外でDjangoのアーカイブを作成することです。

Django 1.0をダウンロードし、以下を含んだZipアーカイブを再作成:

  1. DjangoウェブサイトからDjango 1.0ディストリビューションをダウンロードしてください。OSの適切なツールを使ってこのアーカイブを解凍してください(.tar.gzを解凍できるツール)。LinuxもしくはMac OS Xのコマンドラインでの例:
    tar -xzvf Django-1.0.tar.gz
  2. .../conf/.../contrib/のサブディレクトリーを除く(bin/test/を除くことも可能)、django/ディレクトリー内のすべてのファイルを含むZipアーカイブを作成する。Zipファイル内のパスはtest/で始まっている必要があります。
    cd Django-1.0zip -r django.zip django/__init__.py django/bin django/core \                  django/db django/dispatch django/forms \                  django/http django/middleware django/shortcuts \                  django/template django/templatetags \                  django/test django/utils django/views
  3. confパッケージには多くのローカライズファイルが含まれています。全てのファイルをアーカイブに追加すると、アーカイブのサイズが1MBの限界を超えてしまいます。しかし、いくつかのファイルは追加できる余裕があり、多くのDjangoパッケージがconfのうちいくつかが必要です。localeフォルダを除くすべてのconfファイルをアーカイブに追加してください。もし、必要なロケールファイルを追加する場合は、必ずファイルサイズが1MB未満であることを確認してください。

    以下のコマンドで、conf/locale以外のすべてのconfをアーカイブに追加できます。

    zip -r django.zip django/conf -x 'django/conf/locale/*'
  4. 同様に、.../contrib/内の必要なものだけを追加してください。contrib内の最大のコンポーネントはDjango Adminアプリケーションですが、これはApp Engineでは動作しないため、adminadmindocsディレクトリは削除してください。例えば、formtoolsを追加します。
    zip -r django.zip django/contrib/__init__.py \                  django/contrib/formtools
  5. アプリケーションディレクトリにアーカイブファイルを入れてください。
    mv django.zip your-app-dir/
モジュールアーカイブを使う

チップス:Django App Engine Helperの最新版(バージョン "r64" 以降)は、Django 1.0のZipimportをサポートしています。アーカイブ名がdjango.zipにし、アプリケーションのルートディレクトリに配置してください。manage.pyで生成されたすべての新規プロジェクトが自動的にそれを利用します。既存のプロジェクトをアップグレードする場合、まず新しいプロジェクトを生成し、既存プロジェクトからプロジェクトのmain.pyをアップデートしてください。詳細はUsing the Google App Engine Helper for Djangoを参照してください。

あなたがHelperなしでDjangoを使用するか、またはあなたが別のモジュールアーカイブを用いる場合にだけ、以下の解説が適用されます。

モジュールアーカイブを使用する場合、.zipファイルをPythonモジュールのロードパスに配置する必要があります。これを実現する最も簡単な方法は、各ハンドラースクリプトの先端でパスをロードし、main()ルーチンでにおける各操作者の主な()ルーチンでハンドルします。アーカイブ内のモジュールを使用する他のすべてのファイルは変更なしで動作します。App EngineはすべてのPythonアプリケーションにDjango 0.96をプリロードするため、Django 1.0を使うためには、djangoパッケージがプリロードされたバージョンではなく1.0を利用することを確実にするために多くのステップを必要とします。Running Django on App Engineに記述されてるように、Django 1.0をインポートする前に、sys.modulesからDjango 0.96を削除しなければなりません。

以下のコードのテクニックを利用することで、django.zipアーカイブからDjango 1.0を起動できます:

import sys# Uninstall Django 0.96.for k in [k for k in sys.modules if k.startswith('django')]:  del sys.modules[k]# Add Django 1.0 archive to the path.django_path = 'django.zip'sys.path.insert(0, django_path)# Django imports and other code go here...import django.core.handlers.wsgidef main():  # Re-add Django 1.0 archive to the path, if needed.  if django_path not in sys.path:    sys.path.insert(0, django_path)  # Run Django via WSGI.  application = django.core.handlers.wsgi.WSGIHandler()  util.run_wsgi_app(application)if __name__ == '__main__':  main()

適切なapp.yaml、settings.py、およびurls.pyファイルにより、このハンドラーはDjangoの"It worked!"ページを表示します。App EngineでDjangoを使用するための詳細については、Running Django on App Engineを参照してください。

単一パッケージに複数のアーカイブファイルを使用する

Django 1.0のすべては単一のアーカイブファイルに収めることができないので、これを複数のアーカイブファイルに分割し、sys.pathに配置することができます。Pythonを別の場所にナビゲートするために、いくつかのブートストラップコードを記述します。

Pythonはモジュールをインポートする際に、sys.pathに記述された各場所をチェックします。もしロケーションが最初のパッケージにない場合は、Pythonは次のsys.pathエントリーをチェックし、最初のパッケージを発見するまでロケーションをチェックし続けます。

Pythonがモジュールパスから最初のパッケージを発見した際、それを最終的に指定されたロケーションと仮定し、他のロケーションは探しに行きません。もしPythonがパッケージ内からモジュールを発見できなかった場合、インポートエラーが発生し、停止します。Pythonはパッケージ内でモジュールを発見した場合、sys.pathのエントリーをチェックしません。

最初のアーカイブ

最初のアーカイブを複数のアーカイブに分割したパッケージをインポートすることにより、その複数の場所にあるパッケージを探索できるようPythonに伝えます。パッケージ(モジュール)オブジェクトの__path__メンバー

は、パッケージコンテンツのロケーションのリストです。例えば、djangoパッケージがdjango1.zip、django2.zipという2つのアーカイブに分割した場合、以下のコードにより両方のアーカイブを見るようPythonに伝えることができます。

sys.path.insert(0, 'django1.zip')
import django
django.__path__.append('django2.zip/django')

これによりdjango1.zipからdjangoをインポートするために、django/__init__.pyをこのアーカイブに含めてください。

2つめのアーカイブの__path__を設定すると、双方のアーカイブからdjangoのモジュールをインポートします。

追記

App Engineでzipimportを使用するために追加で注意すること:

  • モジュールアーカイブを使用すると、最初にアーカイブインポートする時に付加的なCPUを消費します。同様のインスタンスへのリクエストに関してはメモリーにキャッシュされており、またアーカイブは解凍された状態でキャッシュされコンパイルされているので、同様のインスタンスのインポートについては、デコンパイル、コンパイルに必要なCPU時間は短縮されます。
  • App Engineのzipimportは、プリコンパイルされた.pycではなく、.pyソースファイルのみです。
  • ハンドラースクリプトがパスにモジュールアーカイブを追加要求するので、モジュールアーカイブにハンドラースクリプト自体を保存することはできません。モジュールアーカイブにはあらゆるパイソンコードを保存できます。

コメント

  1. 「正式書類のない機能」よりも「ドキュメントに記載されていない機能」等の方が良いと思います。

    返信削除
  2. >> 松尾さん
    ありがとうございます。訂正します。

    返信削除

コメントを投稿

このブログの人気の投稿

Python から Win32 API 経由で印刷する

財務諸表 (Financial Statements)

Netflix のスケール - オートメーション編