diff options
author | Marc Abramowitz <marc@marc-abramowitz.com> | 2016-03-07 18:52:36 -0800 |
---|---|---|
committer | Marc Abramowitz <marc@marc-abramowitz.com> | 2016-03-07 18:52:36 -0800 |
commit | cc83e06efff71b81ca5a3ac6df65775971181295 (patch) | |
tree | d52fa3f1a93730f263c2c5ac8266de8e5fb12abf /paste/reloader.py | |
download | paste-git-tox_coverage.tar.gz |
tox.ini: Measure test coveragetox_coverage
Diffstat (limited to 'paste/reloader.py')
-rw-r--r-- | paste/reloader.py | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/paste/reloader.py b/paste/reloader.py new file mode 100644 index 0000000..c9d7c14 --- /dev/null +++ b/paste/reloader.py @@ -0,0 +1,179 @@ +# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) +# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php +""" +A file monitor and server restarter. + +Use this like: + +..code-block:: Python + + 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 is run from this .bat file (if you use Windows):: + + @echo off + :repeat + python server.py + if %errorlevel% == 3 goto repeat + +or run a monitoring process in Python (``paster serve --reload`` does +this). + +Use the ``watch_file(filename)`` function to cause a reload/restart for +other other non-Python files (e.g., configuration files). If you have +a dynamic set of files that grows over time you can use something like:: + + def watch_config_files(): + return CONFIG_FILE_CACHE.keys() + paste.reloader.add_file_callback(watch_config_files) + +Then every time the reloader polls files it will call +``watch_config_files`` and check all the filenames it returns. +""" + +from __future__ import print_function +import os +import sys +import time +import threading +import traceback +from paste.util.classinstance import classinstancemethod + +def install(poll_interval=1): + """ + Install the reloading monitor. + + On some platforms server threads may not terminate when the main + thread does, causing ports to remain open/locked. The + ``raise_keyboard_interrupt`` option creates a unignorable signal + which causes the whole application to shut-down (rudely). + """ + mon = Monitor(poll_interval=poll_interval) + t = threading.Thread(target=mon.periodic_reload) + t.setDaemon(True) + t.start() + +class Monitor(object): + + instances = [] + global_extra_files = [] + global_file_callbacks = [] + + def __init__(self, poll_interval): + self.module_mtimes = {} + self.keep_running = True + self.poll_interval = poll_interval + self.extra_files = list(self.global_extra_files) + self.instances.append(self) + self.file_callbacks = list(self.global_file_callbacks) + + def periodic_reload(self): + while True: + if not self.check_reload(): + # use os._exit() here and not sys.exit() since within a + # thread sys.exit() just closes the given thread and + # won't kill the process; note os._exit does not call + # any atexit callbacks, nor does it do finally blocks, + # flush open files, etc. In otherwords, it is rude. + os._exit(3) + break + time.sleep(self.poll_interval) + + def check_reload(self): + filenames = list(self.extra_files) + for file_callback in self.file_callbacks: + try: + filenames.extend(file_callback()) + except: + print("Error calling paste.reloader callback %r:" % file_callback, + file=sys.stderr) + traceback.print_exc() + for module in sys.modules.values(): + try: + filename = module.__file__ + except (AttributeError, ImportError): + continue + if filename is not None: + filenames.append(filename) + for filename in filenames: + try: + stat = os.stat(filename) + if stat: + mtime = stat.st_mtime + else: + mtime = 0 + except (OSError, IOError): + continue + if filename.endswith('.pyc') and os.path.exists(filename[:-1]): + mtime = max(os.stat(filename[:-1]).st_mtime, mtime) + elif filename.endswith('$py.class') and \ + os.path.exists(filename[:-9] + '.py'): + mtime = max(os.stat(filename[:-9] + '.py').st_mtime, mtime) + if filename not in self.module_mtimes: + self.module_mtimes[filename] = mtime + elif self.module_mtimes[filename] < mtime: + print("%s changed; reloading..." % filename, file=sys.stderr) + return False + return True + + def watch_file(self, cls, filename): + """Watch the named file for changes""" + 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) + + def add_file_callback(self, cls, callback): + """Add a callback -- a function that takes no parameters -- that will + return a list of filenames to watch for changes.""" + if self is None: + for instance in cls.instances: + instance.add_file_callback(callback) + cls.global_file_callbacks.append(callback) + else: + self.file_callbacks.append(callback) + + add_file_callback = classinstancemethod(add_file_callback) + +if sys.platform.startswith('java'): + try: + from _systemrestart import SystemRestart + except ImportError: + pass + else: + class JythonMonitor(Monitor): + + """ + Monitor that utilizes Jython's special + ``_systemrestart.SystemRestart`` exception. + + When raised from the main thread it causes Jython to reload + the interpreter in the existing Java process (avoiding + startup time). + + Note that this functionality of Jython is experimental and + may change in the future. + """ + + def periodic_reload(self): + while True: + if not self.check_reload(): + raise SystemRestart() + time.sleep(self.poll_interval) + +watch_file = Monitor.watch_file +add_file_callback = Monitor.add_file_callback |