summaryrefslogtreecommitdiff
path: root/paste/reloader.py
blob: 6fa0a2787b17aa3d3e7801b3f1dfc64f31eb4d3b (plain)
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
"""
A file monitor and server restarter.

Use this like::

    import reloader
    reloader.install()

Then make sure your server is installed with a shell script like::

    err=3
    while test "$err" -eq 3 ; do
        python server.py
        err="$?"
    done

or restart in Python (server.py does this).  Use the watch_file(filename)
function to cause a reload/restart for other other non-Python files (e.g.,
configuration files).
"""

import os
import sys
import time
import threading
import atexit
from paste.util.classinstance import classinstancemethod

def install(poll_interval=1, raise_keyboard_interrupt=True):
    mon = Monitor(poll_interval=poll_interval,
                  raise_keyboard_interrupt=raise_keyboard_interrupt)
    t = threading.Thread(target=mon.periodic_reload)
    t.start()
    
class Monitor:

    instances = []
    global_extra_files = []

    def __init__(self, poll_interval, raise_keyboard_interrupt):
        self.module_mtimes = {}
        atexit.register(self.atexit)
        self.keep_running = True
        self.poll_interval = poll_interval
        self.raise_keyboard_interrupt = raise_keyboard_interrupt
        self.extra_files = self.global_extra_files[:]
        self.instances.append(self)

    def atexit(self):
        self.keep_running = False
        if self.raise_keyboard_interrupt:
            # This exception is somehow magic, because it applies
            # to more threads and situations (like socket.accept)
            # that a mere SystemExit will not.
            raise KeyboardInterrupt("Exiting process")

    def periodic_reload(self):
        while 1:
            if not self.keep_running:
                break
            if not self.check_reload():
                os._exit(3)
                break
            time.sleep(self.poll_interval)

    def check_reload(self):
        filenames = self.extra_files[:]
        for module in sys.modules.values():
            try:
                filenames.append(module.__file__)
            except AttributeError:
                continue
        for filename in filenames:
            try:
                mtime = os.stat(filename).st_mtime
            except (OSError, IOError):
                continue
            if filename.endswith('.pyc') and os.path.exists(filename[:-1]):
                mtime = max(os.stat(filename[:-1]).st_mtime, mtime)
            if not self.module_mtimes.has_key(filename):
                self.module_mtimes[filename] = mtime
            elif self.module_mtimes[filename] < mtime:
                print >> sys.stderr, (
                    "%s changed; reloading..." % filename)
                return False
        return True

    def watch_file(self, cls, filename):
        filename = os.path.abspath(filename)
        if self is None:
            for instance in cls.instances:
                instance.watch_file(filename)
            cls.global_extra_files.append(filename)
        else:
            self.extra_files.append(filename)

    watch_file = classinstancemethod(watch_file)

watch_file = Monitor.watch_file