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アーカイブを再作成:
- DjangoウェブサイトからDjango 1.0ディストリビューションをダウンロードしてください。OSの適切なツールを使ってこのアーカイブを解凍してください(
.tar.gz
を解凍できるツール)。LinuxもしくはMac OS Xのコマンドラインでの例:tar -xzvf Django-1.0.tar.gz
.../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
conf
パッケージには多くのローカライズファイルが含まれています。全てのファイルをアーカイブに追加すると、アーカイブのサイズが1MBの限界を超えてしまいます。しかし、いくつかのファイルは追加できる余裕があり、多くのDjangoパッケージがconf
のうちいくつかが必要です。locale
フォルダを除くすべてのconf
ファイルをアーカイブに追加してください。もし、必要なロケールファイルを追加する場合は、必ずファイルサイズが1MB未満であることを確認してください。以下のコマンドで、
conf/locale
以外のすべてのconf
をアーカイブに追加できます。zip -r django.zip django/conf -x 'django/conf/locale/*'
- 同様に、
.../contrib/
内の必要なものだけを追加してください。contrib
内の最大のコンポーネントはDjango Adminアプリケーションですが、これはApp Engineでは動作しないため、admin
とadmindocs
ディレクトリは削除してください。例えば、formtools
を追加します。zip -r django.zip django/contrib/__init__.py \ django/contrib/formtools
- アプリケーションディレクトリにアーカイブファイルを入れてください。
mv django.zip your-app-dir/
- DjangoウェブサイトからDjango 1.0ディストリビューションをダウンロードしてください。OSの適切なツールを使ってこのアーカイブを解凍してください(
- モジュールアーカイブを使う
チップス: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に伝えます。パッケージ(モジュール)オブジェクトの
は、パッケージコンテンツのロケーションのリストです。例えば、djangoパッケージがdjango1.zip、django2.zipという2つのアーカイブに分割した場合、以下のコードにより両方のアーカイブを見るようPythonに伝えることができます。__path__
メンバー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
ソースファイルのみです。 - ハンドラースクリプトがパスにモジュールアーカイブを追加要求するので、モジュールアーカイブにハンドラースクリプト自体を保存することはできません。モジュールアーカイブにはあらゆるパイソンコードを保存できます。
「正式書類のない機能」よりも「ドキュメントに記載されていない機能」等の方が良いと思います。
返信削除>> 松尾さん
返信削除ありがとうございます。訂正します。