Djangoを読む

はじめに

Djangoプロジェクト(とアプリ)を作成したのでいよいよフレームワークの中身に入っていきます。 と言ってもいきなりフルセットのアプリを追っかけるのはつらいので、チュートリアルにそってまずは単純なビューについて動作を見ていきましょう。

チュートリアルの通りに書いていくと以下の3つのファイルを作成することになります。

polls/view.py

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
 
 
 
 
from django.http import HttpResponse
 
def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

polls/urls.py

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
 
 
 
 
 
 
 
from django.conf.urls import url
 
from . import views
 
urlpatterns = [
    url(r'^$', views.index, name='index'),
]

mysite/urls.py

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
 
 
 
 
 
 
 
from django.conf.urls import include, url
from django.contrib import admin
 
urlpatterns = [
    url(r'^polls/', include('polls.urls')),
    url(r'^admin/', admin.site.urls),
]

で、manage.pyを使って開発サーバを起動と。

$ python manage.py runserver

django/core/management/commands/runserver.py

それでは挙げたのとは逆順にサーバ起動から見ていきましょう。実行されるのは前回でわかったようにrunserver.pyにあるCommandクラスです。handleメソッドは、オプション処理を全部無視すると非常にシンプルです。

Everything is expanded.Everything is shortened.
  1
  2
 
 
    def handle(self, *args, **options):
        self.run(**options)

runメソッド

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 
-
|
!
 
 
 
 
 
 
    def run(self, **options):
        """
        Runs the server, using the autoreloader if needed
        """
        use_reloader = options['use_reloader']
 
        if use_reloader:
            autoreload.main(self.inner_run, None, options)
        else:
            self.inner_run(None, **options)

オートリローダーを使う、使わないの違いはありますがともかく、inner_runが呼び出されています。オートリローダーはオートリローダーでおもしろいので後で見ます。

inner_runもシステムチェックとかをしている部分は飛ばして、コアとなる部分、

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 
 
 
 
 
 
-
!
 
 
 
    def inner_run(self, *args, **options):
        try:
            handler = self.get_handler(*args, **options)
            run(self.addr, int(self.port), handler,
                ipv6=self.use_ipv6, threading=threading)
        except socket.error as e:
            # 省略
        except KeyboardInterrupt:
            if shutdown_message:
                self.stdout.write(shutdown_message)
            sys.exit(0)

get_handlerに進む前に、 またrunが出てきていますが、このrunは先ほどのrunメソッドではありません。Pythonの場合、メソッド呼び出しなら「self.run(...)」になります。というわけでこのrunが何者かとファイルの上の方を見てみると、

Everything is expanded.Everything is shortened.
  1
 
from django.core.servers.basehttp import get_internal_wsgi_application, run

と、basehttpに定義されている関数なことがわかります。紛らわしい。

django/core/servers/basehttp.py

get_internal_wsgi_application

では改めてget_handlerメソッドを、と言いたいところですが、get_handlerメソッドは一行書いてあるだけです。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
 
-
|
!
 
    def get_handler(self, *args, **options):
        """
        Returns the default WSGI handler for the runner.
        """
        return get_internal_wsgi_application()

結局、basehttp.pyに集約されているわけですね。というわけでget_internal_wsgi_applicationを見てみましょう。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
-
|
|
|
|
|
|
|
|
|
|
!
 
 
 
 
 
 
 
 
-
def get_internal_wsgi_application():
    """
    Loads and returns the WSGI application as configured by the user in
    ``settings.WSGI_APPLICATION``. With the default ``startproject`` layout,
    this will be the ``application`` object in ``projectname/wsgi.py``.
 
    This function, and the ``WSGI_APPLICATION`` setting itself, are only useful
    for Django's internal server (runserver); external WSGI servers should just
    be configured to point to the correct application object directly.
 
    If settings.WSGI_APPLICATION is not set (is ``None``), we just return
    whatever ``django.core.wsgi.get_wsgi_application`` returns.
    """
    from django.conf import settings
    app_path = getattr(settings, 'WSGI_APPLICATION')
    if app_path is None:
        return get_wsgi_application()
 
    try:
        return import_string(app_path)
    except ImportError as e:
        # 省略

飛ばしましたが、前回も出てきたsettings、今度はファイルがちゃんとあるので読み込まれます。その際、django.conf.settingsオブジェクトの属性(global_settings)に「プロジェクトフォルダにあるsettings」の内容がマージされます。そんなからくりでプロジェクトのsettingsで設定した内容がDjangoの実行時に使われることになります。うん、まあちゃんと見たほうがいいか。

django/conf/__init__.py

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 
-
!
 
 
 
-
!
 
 
 
 
 
 
 
 
 
 
class Settings(BaseSettings):
    def __init__(self, settings_module):
        # update this dict from global settings (but only for ALL_CAPS settings)
        for setting in dir(global_settings):
            if setting.isupper():
                setattr(self, setting, getattr(global_settings, setting))
 
        # store the settings module in case someone later cares
        self.SETTINGS_MODULE = settings_module
 
        mod = importlib.import_module(self.SETTINGS_MODULE)
 
        self._explicit_settings = set()
        for setting in dir(mod):
            if setting.isupper():
                setting_value = getattr(mod, setting)
 
                setattr(self, setting, setting_value)
                self._explicit_settings.add(setting)

わかってる人にはわかってるけど、dirで属性一覧が取得できるとか参考になりますね。

話を戻して、WSGI_APPLICATIONはプロジェクトフォルダのsettingsに書かれているので、そちらが利用されます。

Everything is expanded.Everything is shortened.
  1
 
WSGI_APPLICATION = 'mysite.wsgi.application'

import_stringはdjango/utils/module_loading.pyに定義されていて、最後のピリオドの前までをモジュールとしてインポート、最後のピリオドの後をモジュール内の属性として返す関数です。つまり、mysite.wsgiモジュールのapplication属性が返されます。というわけで、mysite/wsgi.pyに進む、

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
-
|
|
|
|
|
|
!
 
 
 
 
 
 
 
 
"""
WSGI config for mysite project.
 
It exposes the WSGI callable as a module-level variable named ``application``.
 
For more information on this file, see
https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
"""
 
import os
 
from django.core.wsgi import get_wsgi_application
 
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
 
application = get_wsgi_application()

たらいまわしされてる気分ですが、結局、get_wsgi_applicationが呼ばれます。

django/coreの方のwsgi.py

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 
 
 
 
 
-
|
|
|
|
|
!
 
 
import django
from django.core.handlers.wsgi import WSGIHandler
 
 
def get_wsgi_application():
    """
    The public interface to Django's WSGI support. Should return a WSGI
    callable.
 
    Allows us to avoid making django.core.handlers.WSGIHandler public API, in
    case the internal WSGI implementation changes or moves in the future.
    """
    django.setup(set_prefix=False)
    return WSGIHandler()

うんざりしてきました。今度は、django/core/handlersのwsgi.pyです。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
 
 
 
 
class WSGIHandler(base.BaseHandler):
    def __init__(self, *args, **kwargs):
        super(WSGIHandler, self).__init__(*args, **kwargs)
        self.load_middleware()

BaseHandler.load_middleware

baseは同じフォルダにあるdjango/core/handlers/base.pyです。__init__は見ないでもいいとしてload_middlewareは見ておく必要があるでしょう。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 
-
|
|
|
|
!
 
 
 
 
 
 
 
-
!
 
 
 
 
 
 
-
!
 
 
 
 
 
 
 
 
 
 
-
|
!
    def load_middleware(self):
        """
        Populate middleware lists from settings.MIDDLEWARE (or the deprecated
        MIDDLEWARE_CLASSES).
 
        Must be called after the environment is fixed (see __call__ in subclasses).
        """
        self._request_middleware = []
        self._view_middleware = []
        self._template_response_middleware = []
        self._response_middleware = []
        self._exception_middleware = []
 
        if settings.MIDDLEWARE is None:
            # 省略
        else:
            handler = convert_exception_to_response(self._get_response)
            for middleware_path in reversed(settings.MIDDLEWARE):
                middleware = import_string(middleware_path)
                try:
                    mw_instance = middleware(handler)
                except MiddlewareNotUsed as exc:
                    # 省略
                    continue
 
                if hasattr(mw_instance, 'process_view'):
                    self._view_middleware.insert(0, mw_instance.process_view)
                if hasattr(mw_instance, 'process_template_response'):
                    self._template_response_middleware.append(mw_instance.process_template_response)
                if hasattr(mw_instance, 'process_exception'):
                    self._exception_middleware.append(mw_instance.process_exception)
 
                handler = convert_exception_to_response(mw_instance)
 
        # We only assign to this when initialization is complete as it is used
        # as a flag for initialization being complete.
        self._middleware_chain = handler

convert_exception_to_responseは同じフォルダにあるexception.pyに定義されています。詳しくはまた後で。 settingsに定義されているミドルウェアを一つずつ構築し、handlerに再代入しています。大体予測できますがミドルウェア定義に行ってみましょう。ちなみに、settingsに定義されているミドルウェアは以下の通りです。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 
 
 
 
 
 
 
 
 
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

reversedしているので、下から先に構築されます。 どれでもいいのでdjango/contrib/sessions/middleware.pyを見てみましょう。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 
 
 
 
 
 
 
 
 
 
 
-
class SessionMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):
        self.get_response = get_response
        engine = import_module(settings.SESSION_ENGINE)
        self.SessionStore = engine.SessionStore
 
    def process_request(self, request):
        session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
        request.session = self.SessionStore(session_key)
 
    def process_response(self, request, response):
        # 省略

MiddlewareMixinはdjango/utils/deprecation.pyに定義されています。・・・deprecation?そのうち廃止されるってこと?(上に挙げたミドルウェアは全部これを継承している)

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
class MiddlewareMixin(object):
    def __init__(self, get_response=None):
        self.get_response = get_response
        super(MiddlewareMixin, self).__init__()
 
    def __call__(self, request):
        response = None
        if hasattr(self, 'process_request'):
            response = self.process_request(request)
        if not response:
            response = self.get_response(request)
        if hasattr(self, 'process_response'):
            response = self.process_response(request, response)
        return response

書いてあるままですが、

  1. process_requestが定義されてたらそれを呼ぶ
  2. process_requestが何も返さなかったらコンストラクタで受け取ったget_responseを呼ぶ
  3. process_responseが定義されてたらそれを呼ぶ

ここで、get_responseという名前ですが、これは__call__メソッドが定義されたミドルウェアオブジェクトです。つまり、

ラップして作られた先頭のミドルウェアの__call__が呼ばれる
次のミドルウェアの__call__が呼ばれる
・・・
BaseHandlerの_get_responseが呼ばれる(これは関数)

のようにミドルウェアが連続実行されていくという仕組みになっているのですね。これでようやくhandlerが作られるところが終わりました。_get_responseは後ほど見ます。

run

さて、話をdjango/core/servers/basehttp.pyに戻して、run関数です。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 
 
 
 
 
 
 
 
-
|
|
|
|
|
!
 
 
def run(addr, port, wsgi_handler, ipv6=False, threading=False):
    server_address = (addr, port)
    if threading:
        httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {})
    else:
        httpd_cls = WSGIServer
    httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
    if threading:
        # ThreadingMixIn.daemon_threads indicates how threads will behave on an
        # abrupt shutdown; like quitting the server by the user or restarting
        # by the auto-reloader. True means the server will not wait for thread
        # termination before it quits. This will make auto-reloader faster
        # and will prevent the need to kill the server manually if a thread
        # isn't terminating correctly.
        httpd.daemon_threads = True
    httpd.set_app(wsgi_handler)
    httpd.serve_forever()

スレッドを使うかどうかの違いはありますが使われるのはWSGIServerクラスです。で、WSGIServerはファイルの上の方で定義されていて標準ライブラリのwsgiref.simple_server.WSGIServerを継承したクラスです。リクエストハンドリングについては次回で見ていきます。

django/utils/autoreload.py

最後に、後で見るといったautoloadです。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 
 
 
 
 
 
 
 
 
 
 
 
def main(main_func, args=None, kwargs=None):
    if args is None:
        args = ()
    if kwargs is None:
        kwargs = {}
    if sys.platform.startswith('java'):
        reloader = jython_reloader
    else:
        reloader = python_reloader
 
    wrapped_main_func = check_errors(main_func)
    reloader(wrapped_main_func, args, kwargs)

まあCPythonということでpython_reloaderへ。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
def python_reloader(main_func, args, kwargs):
    if os.environ.get("RUN_MAIN") == "true":
        thread.start_new_thread(main_func, args, kwargs)
        try:
            reloader_thread()
        except KeyboardInterrupt:
            pass
    else:
        try:
            exit_code = restart_with_reloader()
            if exit_code < 0:
                os.kill(os.getpid(), -exit_code)
            else:
                sys.exit(exit_code)
        except KeyboardInterrupt:
            pass

RUN_MAINという謎の環境変数が出てきました。定義されてないはずなのでrestart_with_reloaderへ。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 
 
 
 
 
 
 
 
 
 
def restart_with_reloader():
    while True:
        args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions] + sys.argv
        if sys.platform == "win32":
            args = ['"%s"' % arg for arg in args]
        new_environ = os.environ.copy()
        new_environ["RUN_MAIN"] = 'true'
        exit_code = os.spawnve(os.P_WAIT, sys.executable, args, new_environ)
        if exit_code != 3:
            return exit_code

RUN_MAIN出てきました。それにしても力技感あふれるコードです。 次にreload_threadを見てみます。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 
 
 
 
 
 
 
 
 
 
 
 
 
def reloader_thread():
    ensure_echo_on()
    if USE_INOTIFY:
        fn = inotify_code_changed
    else:
        fn = code_changed
    while RUN_RELOADER:
        change = fn()
        if change == FILE_MODIFIED:
            sys.exit(3)  # force reload
        elif change == I18N_MODIFIED:
            reset_translations()
        time.sleep(1)

これ以上追いかけるのはやめますが、何をやっているかをまとめると、

  1. 親プロセスはRUN_MAIN環境変数を定義して自分と同じ引数で子プロセスを起動
  2. 子プロセスは元々の関数をスレッドで実行。一方でファイルの更新がないか確認
  3. ファイルが更新されてたらステータス3で終了
  4. それを検出した親プロセスは子プロセスを実行し直す→更新したファイルがリロードされたことになる

なんというか力技ですね:-)

おわりに

今回はDjangoテストサーバの起動について見てきました。個々のやっていることは単純ですがあちこち飛び回って少しややこしく感じました。まあそれでもそのファイル内を見れば外部のファイルはどこにあるのかわかる分Railsよりは楽ですね(笑)

Railsバッシングばかりしている気がしますが、次回はリクエストを受け付けてのハンドリング、初めに挙げたチュートリアルコードのurls.pyだとかviews.pyが呼び出される部分について見ていきます。


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-01-29 (日) 16:57:29 (2870d)