| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- # (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 not self.module_mtimes.has_key(filename):
- 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
|