[3] | 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 |
---|