1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) |
---|
2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php |
---|
3 | """ |
---|
4 | A file monitor and server restarter. |
---|
5 | |
---|
6 | Use this like: |
---|
7 | |
---|
8 | ..code-block:: Python |
---|
9 | |
---|
10 | import reloader |
---|
11 | reloader.install() |
---|
12 | |
---|
13 | Then make sure your server is installed with a shell script like:: |
---|
14 | |
---|
15 | err=3 |
---|
16 | while test "$err" -eq 3 ; do |
---|
17 | python server.py |
---|
18 | err="$?" |
---|
19 | done |
---|
20 | |
---|
21 | or is run from this .bat file (if you use Windows):: |
---|
22 | |
---|
23 | @echo off |
---|
24 | :repeat |
---|
25 | python server.py |
---|
26 | if %errorlevel% == 3 goto repeat |
---|
27 | |
---|
28 | or run a monitoring process in Python (``paster serve --reload`` does |
---|
29 | this). Use the watch_file(filename) function to cause a |
---|
30 | reload/restart for other other non-Python files (e.g., configuration |
---|
31 | files). |
---|
32 | """ |
---|
33 | |
---|
34 | import os |
---|
35 | import sys |
---|
36 | import time |
---|
37 | import threading |
---|
38 | from paste.util.classinstance import classinstancemethod |
---|
39 | |
---|
40 | def install(poll_interval=1): |
---|
41 | """ |
---|
42 | Install the reloading monitor. |
---|
43 | |
---|
44 | On some platforms server threads may not terminate when the main |
---|
45 | thread does, causing ports to remain open/locked. The |
---|
46 | ``raise_keyboard_interrupt`` option creates a unignorable signal |
---|
47 | which causes the whole application to shut-down (rudely). |
---|
48 | """ |
---|
49 | mon = Monitor(poll_interval=poll_interval) |
---|
50 | t = threading.Thread(target=mon.periodic_reload) |
---|
51 | t.setDaemon(True) |
---|
52 | t.start() |
---|
53 | |
---|
54 | class Monitor(object): |
---|
55 | |
---|
56 | instances = [] |
---|
57 | global_extra_files = [] |
---|
58 | |
---|
59 | def __init__(self, poll_interval): |
---|
60 | self.module_mtimes = {} |
---|
61 | self.keep_running = True |
---|
62 | self.poll_interval = poll_interval |
---|
63 | self.extra_files = self.global_extra_files[:] |
---|
64 | self.instances.append(self) |
---|
65 | |
---|
66 | def periodic_reload(self): |
---|
67 | while 1: |
---|
68 | if not self.check_reload(): |
---|
69 | # use os._exit() here and not sys.exit() since within a |
---|
70 | # thread sys.exit() just closes the given thread and |
---|
71 | # won't kill the process; note os._exit does not call |
---|
72 | # any atexit callbacks, nor does it do finally blocks, |
---|
73 | # flush open files, etc. In otherwords, it is rude. |
---|
74 | os._exit(3) |
---|
75 | break |
---|
76 | time.sleep(self.poll_interval) |
---|
77 | |
---|
78 | def check_reload(self): |
---|
79 | filenames = self.extra_files[:] |
---|
80 | for module in sys.modules.values(): |
---|
81 | try: |
---|
82 | filenames.append(module.__file__) |
---|
83 | except AttributeError: |
---|
84 | continue |
---|
85 | for filename in filenames: |
---|
86 | try: |
---|
87 | stat = os.stat(filename) |
---|
88 | if stat: |
---|
89 | mtime = stat.st_mtime |
---|
90 | else: |
---|
91 | mtime = 0 |
---|
92 | except (OSError, IOError): |
---|
93 | continue |
---|
94 | if filename.endswith('.pyc') and os.path.exists(filename[:-1]): |
---|
95 | mtime = max(os.stat(filename[:-1]).st_mtime, mtime) |
---|
96 | if not self.module_mtimes.has_key(filename): |
---|
97 | self.module_mtimes[filename] = mtime |
---|
98 | elif self.module_mtimes[filename] < mtime: |
---|
99 | print >> sys.stderr, ( |
---|
100 | "%s changed; reloading..." % filename) |
---|
101 | return False |
---|
102 | return True |
---|
103 | |
---|
104 | def watch_file(self, cls, filename): |
---|
105 | filename = os.path.abspath(filename) |
---|
106 | if self is None: |
---|
107 | for instance in cls.instances: |
---|
108 | instance.watch_file(filename) |
---|
109 | cls.global_extra_files.append(filename) |
---|
110 | else: |
---|
111 | self.extra_files.append(filename) |
---|
112 | |
---|
113 | watch_file = classinstancemethod(watch_file) |
---|
114 | |
---|
115 | watch_file = Monitor.watch_file |
---|