Автоматичне перевантаження Django (WSGI) при змінах у файлах

  у розділі Технічні теми 

Django

Django — чудовий python-веб-фреймворк, який незважаючи на усю свою потужність потребує веб-сервера.

Для розробки та тестування цілком підійде вбудований в Django сервер. Проте Django зосереджений на своїй роботі, а обслуговувати HTTP-підключень, роздавати статичні файли (малюнки тощо) — не царська робота. Тому, для розгортання Django-проектів в "бойових" умовах потрібен повноцінний веб-сервер.

Рекомендованим методом розгортання Django є використання Apache та модулю mod_wsgi. Таку ж систему зручно використовувати і в розробці, для умов наближених до робочих.

Недоліком використання mod_wsgi для розробки є те, що після змін у коді потрібно перевантажувати веб-сервер. На щастя це легко виправити (після підказок).

Спочатку потрібно переконатися що наш WSGI-додаток працює у режимі "демона" (Daemon Mode). Детальніше про перевірку читайте тут.

У корені Django-проекту створіть файл monitor.py з таким вмістом:

# -*- coding: utf-8 -*-

""" Моніторинг файлів що змінилися.
    Якщо зафіксовано зміну — передається команда перевантаження
    процесу веб-сервера що обслуговує цей екземпляр Django.
"""

import os
import sys
#import time
import signal
import threading
import atexit
import Queue

_interval = 1.0
_times = {}
_files = []

_running = False
_queue = Queue.Queue()
_lock = threading.Lock()

def _restart(path):
    _queue.put(True)
    prefix = 'monitor (pid=%d):' % os.getpid()
    print >> sys.stderr, '%s Change detected to \'%s\'.' % (prefix, path)
    print >> sys.stderr, '%s Triggering process restart.' % prefix
    os.kill(os.getpid(), signal.SIGINT)

def _modified(path):
    try:
        # If path doesn't denote a file and were previously
        # tracking it, then it has been removed or the file type
        # has changed so force a restart. If not previously
        # tracking the file then we can ignore it as probably
        # pseudo reference such as when file extracted from a
        # collection of modules contained in a zip file.

        if not os.path.isfile(path):
            return path in _times

        # Check for when file last modified.

        mtime = os.stat(path).st_mtime
        if path not in _times:
            _times[path] = mtime

        # Force restart when modification time has changed, even
        # if time now older, as that could indicate older file
        # has been restored.

        if mtime != _times[path]:
            return True
    except:
        # If any exception occured, likely that file has been
        # been removed just before stat(), so force a restart.

        return True

    return False

def _monitor():
    while 1:
        # Check modification times on all files in sys.modules.

        for module in sys.modules.values():
            if not hasattr(module, '__file__'):
                continue
            path = getattr(module, '__file__')
            if not path:
                continue
            if os.path.splitext(path)[1] in ['.pyc', '.pyo', '.pyd']:
                path = path[:-1]
            if _modified(path):
                return _restart(path)

        # Check modification times on files which have
        # specifically been registered for monitoring.

        for path in _files:
            if _modified(path):
                return _restart(path)

        # Go to sleep for specified interval.

        try:
            return _queue.get(timeout=_interval)
        except:
            pass

_thread = threading.Thread(target=_monitor)
_thread.setDaemon(True)

def _exiting():
    try:
        _queue.put(True)
    except:
        pass
    _thread.join()

atexit.register(_exiting)

def track(path):
    if not path in _files:
        _files.append(path)

def start(interval=1.0):
    global _interval
    if interval < _interval:
        _interval = interval

    global _running
    _lock.acquire()
    if not _running:
        prefix = 'monitor (pid=%d):' % os.getpid()
        print >> sys.stderr, '%s Starting change monitor.' % prefix
        _running = True
        _thread.start()
    _lock.release()

У WSGI-скрипті1 що обслуговує проект, у кінці, вставте:

import monitor
monitor.start(interval=1.0)

Щоб не забути вимкнути цю функцію на робочому сервері (автоматичне перевантаження тільки в тестових умовах) можна вставити такий код:

import settings
if settings.DEBUG:
    import monitor
    monitor.start(interval=1.0)

Це все. Тепер, як тільки у якийсь файл буде внесено зміни сервер автоматично перечитає новий код і у браузері будемо бачити останню версію проекту.


  1. WSGI-скрипт це той файл, який вказується в конфігурації сайту Apache, наприклад WSGIScriptAlias / /usr/local/www/mysite/apache/django.wsgi


Коментарі