Djangoのエントリポイントと
アプリケーションの仕組み
PyCon mini Sapporo 2015
2015/09/12
お前、誰よ?
◦tokibito
◦ Djangoフレームワークを9年ぐらい使ってます(2006年ごろ、0.95~)
◦ 東京在住(北海道には年1~2回ぐらい来てます)
◦ 株式会社ビープラウド勤務
◦ Pythonプロフェッショナルプログラミング 第2版 買ってね!
◦ イベント支援サイトConnpassをよろしく!
アジェンダ
1. はじめに
2. 前提、注意事項
3. Djangoフレームワークを使ったプロジェクトの構造
4. Djangoのエントリポイント
1. manage.py
2. wsgi.py
5. まとめ
はじめに
◦ Djangoフレームワークを使う場合
◦ マニュアルに従ってmanage.pyコマンドやwsgi.pyを利用する
◦ Django内部を知らなくても多くの機能を使える
◦ この発表内容を知らなくてもDjangoは使えます
◦ この発表内容を聞いて得られるもの
◦ Djangoの内部構造の知識を少し
知っておくと、Django用のモジュールを作るときに楽
(かもしれません)
今日はDjangoのエントリポイントや内部構造について話します
前提、注意事項
◦ 2015年9月時点での情報です
◦ Djangoのバージョンは1.8を前提としています
◦ 将来的にプロジェクトの構造が変わる可能性はあります
Djangoフレームワークを使っ
たプロジェクトの構造
Djangoフレームワークを使う場合、 django-admin コマンドを使って、プ
ロジェクトを作成します。
以下のようなディレクトリ構造でファイルが作られます:
プロジェクト管理スクリプト
プロジェクト設定ファイル
URL設定ファイル
WSGIアプリケーション定義を含むファイル
$ django-admin startproject mysite
Djangoのエントリポイント
管理コマンドを使う場合:
$ ./manage.py runserver
$ gunicorn mysite.wsgi
WSGIサーバーでアプリケーションを動かす場合(gunicornの例):
→ エントリポイントは manage.py
→ エントリポイントは wsgi.py
「エントリポイント」 … プログラムを実行するときの開始位置
※ WSGI: Web Server Gateway Interface
エントリポイントのソースコー
ドを見てみる
◦ manage.py と wsgi.py
◦ どちらもPythonのスクリプトなので、エディタで開けば読めます
manage.py
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
環境変数の設定(既にあれば何もしない)
sys.argvはコマンドライン引数。execute_from_command_line関数の実行
django.core.management
◦ django/core/managent/__init__.pyに実装がある
◦ https://github.com/django/django/blob/stable/1.8.x/django/core/m
anagement/__init__.py#L346
def execute_from_command_line(argv=None):
"""
A simple method that runs a ManagementUtility.
"""
utility = ManagementUtility(argv)
utility.execute() ManagementUtility.execute()メソッドを実行
詳細は省略します(時間が足りない)
execute_from_command_line
内部の処理
1. argparseモジュールを使ってコマンドライン引数をパース
2. settings.INSTALLED_APPSが有効か判定(ImproperlyConfigured発生を見る)
◦ django.conf.settings
◦ ここでDjangoの設定ファイルが読み込まれる
◦ 設定ファイルが不要なコマンドの場合は読み込まずにsettings.configure()で初期化
3. Djangoの初期化
◦ django.setup()
4. コマンドのオートコンプリート実行(環境変数が設定されていれば)
5. サブコマンドの実行
1. コマンド一覧の作成(<dict {app_name: [commands]}>)
1. django.core.management.commands以下のモジュールを取得
2. INSTALLED_APPSのアプリケーションの management.commands以下のモジュールを取得
2. 実行するコマンドの決定
3. コマンドクラスをロードしてクラスオブジェクト取得
4. コマンドクラスを実行
雑に説明すると、
1. プロジェクトの設定ファイルを読み込み
2. アプリケーションをロード
3. 実行したいコマンドを見つける
4. 実行
以上
django.conf.settings
◦ django.conf.LazySettingsクラスのインスタンス
◦ __getattr__が実装されている
◦ LazySettings._wrappedの属性をgetattrで返す
◦ 初回アクセス時に LazySettings._setup() で設定ファイルを読み込む
◦ LazySettings._setup()
◦ 環境変数DJANGO_SETTINGS_MODULEに指定されたモジュールをロードして、Settingsクラス
のインスタンスを作って_wrappedに代入
◦ DJANGO_SETTINGS_MODULEが空の場合はImproperlyConfigured例外が発生
◦ 設定ファイルにはデフォルト値(django.conf.global_settings)に対する差分だけ指定されて
いればよい
◦ LazySettings.configure()
◦ DJANGO_SETTINGS_MODULEを使わずに設定(UserSettingsHolder)オブジェクトを_wrappedに
代入
◦ キーワード引数でデフォルト値に上書きする設定項目を指定できる
◦ Djangoの設定ファイル(settings.py)の項目は、このオブジェクトを通して参照する
ImproperlyConfigured例外
◦ Djangoの設定に不備がある際に発生する例外
◦ 例: djangoのプロジェクト以外でdjangoの機能を使おうとするとき
>>> from django.forms import Form
>>> Form()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:¥work¥venv¥lib¥site-packages¥django¥forms¥forms.py", line 129, in __init__
self.label_suffix = label_suffix if label_suffix is not None else _(':')
File "C:¥work¥venv¥lib¥site-packages¥django¥utils¥translation¥__init__.py", line 84, in ugettext
return _trans.ugettext(message)
File "C:¥work¥venv¥lib¥site-packages¥django¥utils¥translation¥__init__.py", line 56, in __getattr__
if settings.USE_I18N:
File "C:¥work¥venv¥lib¥site-packages¥django¥conf¥__init__.py", line 48, in __getattr__
self._setup(name)
File "C:¥work¥venv¥lib¥site-packages¥django¥conf¥__init__.py", line 42, in _setup
% (desc, ENVIRONMENT_VARIABLE))
django.core.exceptions.ImproperlyConfigured: Requested setting USE_I18N, but settings are not configured. You
must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before
accessing settings.
Djangoは設定をしないと動かない!
Tips: プロジェクトの設定項目
を参照する
◦ プロジェクトのsettings.pyに書いた設定を参照する場合
◦ × settings.pyをimportして読む(設定ファイルの切り替えに対応できない)
◦ ◯ django.conf.settingsを使う
# myproject/myproject/settings.py
SPAM_VALUE = 'egg'
>>> from django.conf import settings
>>> settings.SPAM_VALUE
'egg'
Tips: 設定ファイルの切り替え
◦ manage.pyコマンドの場合
◦ --settings オプションで設定モジュールを指定
◦ または、 DJANGO_SETTINGS_MODULE 環境変数
$ ./manage.py runserver --settings=myproject.debug_settings
◦ Djangoの各種機能を使うための準備
◦ ロギングの設定(configure_logging)
◦ settings.LOGGINGをロギングモジュールに反映
◦ INSTALLED_APPSに指定されたアプリケーションのロード
(django.apps.apps.populate)
◦ =importして内部でキャッシュ
django.setup()
Djangoはdjango.setup()を呼ばないと動かない!
Tips: Djangoのプロジェクトを
作らずにDjangoを使う
◦ settings._setup()を呼ぶかsettings.configure()を呼べば、Djangoプロジェクト
外での設定の不備は解消できる
◦ 設定後にdjango.setup()を呼ぶと、アプリケーションのロードなどが実行され
てDjangoを使う準備が完了する
>>> from django.conf import settings
>>> settings.configure()
>>> import django
>>> django.setup() # これでDjangoを使う準備が完了する
設定とsetup()をしたのでDjangoを使える!!!
django.apps.apps
◦ django.apps.appsは、django.apps.registry.Appsクラスのインスタンス
◦ django.apps.registry.Apps
◦ Djangoアプリケーションのロードとモデルクラスを管理するクラス
◦ Apps.populate()メソッド
◦ アプリケーションをロードし、アプリケーション内のモデルクラスをキャッシュする
◦ 各Djangoアプリケーションはdjango.apps.config.AppConfigクラスのインスタンスによって管理さ
れる
◦ django.apps.config.AppConfig
◦ DjangoアプリケーションのモジュールをAppConfig.moduleで保持している
◦ Djangoでは、AppConfig.labelでアプリケーションの一意性を確保している
◦ 同一のアプリケーションのモジュールでも、AppConfig.labelが別のAppConfigインスタンスを
用意すれば、別アプリケーションとして認識させられる
◦ django.appsモジュールはDjango1.7で追加された
◦ 1.6までは、django.db.models.loadingにありました
django.apps.registry.Apps.all_models
django.apps.registry.Apps.app_configs
図で説明すると、
django.apps.registry.Apps
AppConfig
label='admin'
AppConfig
label='myapp'
AppConfig
label='auth'
AppConfig
label='sessions'
User MyModelPermission
Tips: アプリケーションモジュール
やモデルクラスへアクセスする
◦ アプリケーション内で別のアプリケーション以下にあるモデルクラスを使い
たい場合など
◦ django.apps.appsを参照することで、モジュール名を固定せず、すべてのモ
デルクラスやアプリケーションモジュールへアクセスできる
# manage.py shell
>>> from django.apps import apps
>>> apps.get_app_configs() # プロジェクト内のAppConfigクラスを取得する
ValuesView(OrderedDict([('admin', <AdminConfig: admin>), ('auth', <AuthConfig:
auth>), ('contenttypes', <ContentTypesConfig: contenttypes>), ('sessions',
<SessionsConfig: sessions>), ('messages', <MessagesConfig: messages>),
('staticfiles', <StaticFilesConfig: staticfiles>), ('myapp', <AppConfig: myapp>)]))
>>> apps.get_models() # プロジェクト内のモデルクラスを取得する
[<class 'django.contrib.admin.models.LogEntry'>, <class
'django.contrib.auth.models.Permission'>, <class
'django.contrib.auth.models.Group'>, <class 'django.contrib.auth.models.User'>,
<class 'django.contrib.contenttypes.models.ContentType'>, <class
'django.contrib.sessions.models.Session'>, <class 'myapp.models.Spam'>]
内部の実装で使われている
テクニック
◦ モジュールの遅延ロード
◦ django.conf. LazySettingsなど
◦ メリット
◦ 起動が速い
◦ デメリット
◦ 仕組みが複雑
◦ 問題の発生も遅延する(シンタックスエラーなど)
◦ 文字列を元にしたモジュールのインポート
◦ django.apps.registry.AppsConfigなど
◦ メリット
◦ 実行時に読み込むモジュールを決定できる(遅延ロードできる)
◦ デメリット
◦ 静的解析に弱い(モジュール名が文字列なので追いかけられない)
◦ インスタンスのキャッシュ
◦ django.conf.settings._wrappedなど
◦ メリット
◦ ミドルウェアやファイルシステムなどを使わず手軽にキャッシュして高速化
◦ デメリット
◦ 使用メモリ増加
manage.pyについてまとめ
1. プロジェクトの設定ファイルを読み込み
1. django.conf.settings
2. アプリケーションをロード
1. django.setup()
1. django.apps.apps
3. 実行したいコマンドを見つける
4. 実行
次はwsgi.py
$ gunicorn myproject.wsgi
application = myproject.wsgi.application
gunicornを使ってwsgi.pyを動かす場合
内部ではインポートして"application"という名前の変数を参照して使っている
WSGIサーバーの役割
1. tcp/80でソケットを開いて待受け(listen)
2. Webブラウザなどのクライアントから接続
3. HTTPリクエストをクライアントから受信する(recv)
4. 受け取ったHTTPリクエストをパースしてヘッダ、bodyなどの情報に分解
1. 辞書オブジェクトに詰め込む(environ)
5. WSGIアプリケーションの実行
1. response = application(environ, start_response)
1. ここで実行されるapplicationがWSGIHandler.__call__(environ, start_response) .. Djangoの
場合
6. HTTPレスポンスをクライアントに送信(send)
1. コールバック(start_response)を通して受け取ったヘッダをクライアントに送信
2. returnで返却されたbodyをクライアントに送信
wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
application = get_wsgi_application()
環境変数の設定(既にあれば何もしない)
WSGIアプリケーションの取得
django.core.wsgi
import django
from django.core.handlers.wsgi import WSGIHandler
def get_wsgi_application():
django.setup()
return WSGIHandler()
WSGIHandler(WSGIアプリケーション)インスタンスの生成
Djangoの初期化
django.core.handlers.wsgi.
WSGIHandler内部の処理
◦ WSGIHandlerクラスはdjango.core.handlers.base.BaseHandlerを継承
◦ WSGIHandler.__call__(environ, start_response)
◦ WSGIアプリケーションとして実行可能なメソッド
◦ Djangoミドルウェアのロード
◦ リクエストオブジェクトの生成(django.core.handlers.wsgi.WSGIRequest)
◦ get_response(request)メソッドの呼び出し
◦ BaseHandler.get_response(request)
◦ リクエストミドルウェアの適用
◦ URLディスパッチ
◦ settings.ROOT_URLCONFを元にdjango.core.urlresolvers.RegexURLResolverインスタンス生成
◦ RegexURLResolver.resolve()で実行するViewを取得
◦ Viewミドルウェアの適用
◦ Viewを実行してレスポンスオブジェクトを取得
◦ response.render()メソッドがあれば呼び出し
◦ レスポンスミドルウェアの適用
◦ レスポンスオブジェクトを返す
雑に説明すると、
1. プロジェクトの設定ファイルを読み込み
2. アプリケーションをロード
3. リクエストオブジェクトを作成
4. 実行するViewを決定
5. View実行
6. レスポンスを返す
以上
リクエストオブジェクト
◦ django.http.request.HttpRequestクラスのインスタンス
◦ WSGIの場合は継承してWSGIRequestクラス(wsgi.inputなどのWSGI固有の対応など)
◦ HTTPリクエスト内容の抽象化
◦ WSGIのenviron辞書はPythonの辞書だが、抽象度は低い
◦ GET、POSTフィールドのデコード、分解やCOOKIEなどのヘッダのデコードなど
◦ リクエストの処理終了後に破棄される
◦ → requestオブジェクトにミドルウェア等で属性を追加しても問題ない
◦ 例
◦ request.sessions (SessionMiddlewareによって追加)
◦ request.user (AuthenticationMiddlewareによって追加)
◦ WSGIRequestの場合
◦ WSGIサーバーから渡されるenviron辞書を使って生成
URLディスパッチ
◦ リクエストされたURLから実行するViewを決定する仕組み
◦ django.core.urlresolvers.RegexURLResolver
◦ urls.pyの内容を元にして、URLとViewを正規表現でマッチさせるクラス
>>> from django.conf import settings
>>> from django.core.urlresolvers import RegexURLResolver
>>> resolver = RegexURLResolver(r'^/', settings.ROOT_URLCONF)
>>> resolver_match = resolver.resolve('/')
>>> resolver_match
ResolverMatch(func=mysite.views.index, args=(), kwargs={}, url_name=None,
app_name=None, namespaces=[])
>>> callback, callback_args, callback_kwargs = resolver_match callbackがView
resolveメソッドでURLにマッチした
ResolverMatchオブジェクトが返される
Viewの実行
◦ DjangoのViewとして使えるもの
◦ リクエストオブジェクトを引数に指定でき、レスポンスオブジェクトを返す関数またはメソッド
◦ Viewの実行は関数呼び出し
>>> from django.http import HttpRequest
>>> request = HttpRequest()
>>> callback(request, *callback_args, **callback_kwargs)
<django.http.response.HttpResponse object at 0x7fbc65349f98>
resolverで取得したcallback(View)の実行
Viewを呼ぶとResponseが返される
レスポンスオブジェクト
◦ django.http.response.HttpResponseクラスのインスタンス
◦ HttpResponseはsix.Iteratorを継承している
◦ HTTPレスポンス内容の抽象化
◦ WSGIアプリケーションのレスポンスとしてそのまま使える
◦ レスポンス文字列を返すための__iter__メソッドが実装されている
>>> response = callback(request)
>>> response
<django.http.response.HttpResponse object at 0x7f9bb5904ef0>
>>> b''.join(s for s in response)
b'Hello, world!'
Viewを実行して取得したレスポンスオブジェクトの評価
WSGIサーバー側では大体こんな感じで使われる
まとめ
◦ Djangoのエントリポイントはmanage.pyとwsgi.py
◦ Djangoを動かすには設定と準備が必要
◦ django.conf.settings.configure()
◦ django.setup()
◦ Djangoのアプリケーションとモデルは一箇所で管理されてるよ
◦ django.apps.apps (django.apps.registry.Apps)
◦ WSGIHandlerでリクエストを処理しているよ(WSGIの場合)
◦ リクエストオブジェクト生成
◦ URLディスパッチ
◦ View実行
◦ レスポンスオブジェクトを返す
参考
◦ http://d.hatena.ne.jp/nullpobug/20140301/1393660554
◦ https://docs.djangoproject.com/en/1.8/topics/settings/
◦ https://docs.djangoproject.com/en/1.8/ref/applications/
おわり。

Djangoのエントリポイントとアプリケーションの仕組み