summaryrefslogtreecommitdiff
path: root/pyramid/scripts/pserve.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyramid/scripts/pserve.py')
-rw-r--r--pyramid/scripts/pserve.py867
1 files changed, 867 insertions, 0 deletions
diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py
new file mode 100644
index 000000000..de5276a9f
--- /dev/null
+++ b/pyramid/scripts/pserve.py
@@ -0,0 +1,867 @@
+# (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
+#
+# For discussion of daemonizing:
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
+#
+# Code taken also from QP: http://www.mems-exchange.org/software/qp/ From
+# lib/site.py
+
+import atexit
+import errno
+import logging
+import optparse
+import os
+import re
+import subprocess
+import sys
+import threading
+import time
+import traceback
+
+from paste.deploy import loadapp, loadserver
+
+from pyramid.scripts.common import logging_file_config
+
+MAXFD = 1024
+
+def main(argv=sys.argv):
+ command = PServeCommand(argv)
+ return command.run()
+
+class DaemonizeException(Exception):
+ pass
+
+class PServeCommand(object):
+
+ min_args = 0
+ usage = 'CONFIG_FILE [start|stop|restart|status] [var=value]'
+ takes_config_file = 1
+ summary = "Serve the described application"
+ description = """\
+ This command serves a web application that uses a paste.deploy
+ configuration file for the server and application.
+
+ If start/stop/restart is given, then --daemon is implied, and it will
+ start (normal operation), stop (--stop-daemon), or do both.
+
+ You can also include variable assignments like 'http_port=8080'
+ and then use %(http_port)s in your config files.
+ """
+ verbose = 1
+
+ # used by subclasses that configure apps and servers differently
+ requires_config_file = True
+
+ parser = optparse.OptionParser()
+ parser.add_option(
+ '-n', '--app-name',
+ dest='app_name',
+ metavar='NAME',
+ help="Load the named application (default main)")
+ parser.add_option(
+ '-s', '--server',
+ dest='server',
+ metavar='SERVER_TYPE',
+ help="Use the named server.")
+ parser.add_option(
+ '--server-name',
+ dest='server_name',
+ metavar='SECTION_NAME',
+ help=("Use the named server as defined in the configuration file "
+ "(default: main)"))
+ if hasattr(os, 'fork'):
+ parser.add_option(
+ '--daemon',
+ dest="daemon",
+ action="store_true",
+ help="Run in daemon (background) mode")
+ parser.add_option(
+ '--pid-file',
+ dest='pid_file',
+ metavar='FILENAME',
+ help=("Save PID to file (default to pyramid.pid if running in "
+ "daemon mode)"))
+ parser.add_option(
+ '--log-file',
+ dest='log_file',
+ metavar='LOG_FILE',
+ help="Save output to the given log file (redirects stdout)")
+ parser.add_option(
+ '--reload',
+ dest='reload',
+ action='store_true',
+ help="Use auto-restart file monitor")
+ parser.add_option(
+ '--reload-interval',
+ dest='reload_interval',
+ default=1,
+ help=("Seconds between checking files (low number can cause "
+ "significant CPU usage)"))
+ parser.add_option(
+ '--monitor-restart',
+ dest='monitor_restart',
+ action='store_true',
+ help="Auto-restart server if it dies")
+ parser.add_option(
+ '--status',
+ action='store_true',
+ dest='show_status',
+ help="Show the status of the (presumably daemonized) server")
+ parser.add_option(
+ '-q', '--quiet',
+ action='store_true',
+ dest='quiet',
+ help='Produce little or no output')
+
+ if hasattr(os, 'setuid'):
+ # I don't think these are available on Windows
+ parser.add_option(
+ '--user',
+ dest='set_user',
+ metavar="USERNAME",
+ help="Set the user (usually only possible when run as root)")
+ parser.add_option(
+ '--group',
+ dest='set_group',
+ metavar="GROUP",
+ help="Set the group (usually only possible when run as root)")
+
+ parser.add_option(
+ '--stop-daemon',
+ dest='stop_daemon',
+ action='store_true',
+ help=('Stop a daemonized server (given a PID file, or default '
+ 'pyramid.pid file)'))
+
+ _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
+
+ default_verbosity = 1
+
+ _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
+ _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN'
+
+ possible_subcommands = ('start', 'stop', 'restart', 'status')
+
+ def __init__(self, argv):
+ self.options, self.args = self.parser.parse_args(argv[1:])
+
+ def out(self, msg):
+ if not self.options.quiet:
+ print(msg)
+
+ def run(self):
+ if self.options.stop_daemon:
+ return self.stop_daemon()
+
+ if not hasattr(self.options, 'set_user'):
+ # Windows case:
+ self.options.set_user = self.options.set_group = None
+
+ # @@: Is this the right stage to set the user at?
+ self.change_user_group(
+ self.options.set_user, self.options.set_group)
+
+ if self.requires_config_file:
+ if not self.args:
+ self.out('You must give a config file')
+ return
+ app_spec = self.args[0]
+ if (len(self.args) > 1
+ and self.args[1] in self.possible_subcommands):
+ cmd = self.args[1]
+ restvars = self.args[2:]
+ else:
+ cmd = None
+ restvars = self.args[1:]
+ else:
+ app_spec = ""
+ if (self.args
+ and self.args[0] in self.possible_subcommands):
+ cmd = self.args[0]
+ restvars = self.args[1:]
+ else:
+ cmd = None
+ restvars = self.args[:]
+
+ if self.options.reload:
+ if os.environ.get(self._reloader_environ_key):
+ if self.verbose > 1:
+ self.out('Running reloading file monitor')
+ install_reloader(int(self.options.reload_interval))
+ if self.requires_config_file:
+ watch_file(self.args[0])
+ else:
+ return self.restart_with_reloader()
+
+ if cmd not in (None, 'start', 'stop', 'restart', 'status'):
+ self.out(
+ 'Error: must give start|stop|restart (not %s)' % cmd)
+ return
+
+ if cmd == 'status' or self.options.show_status:
+ return self.show_status()
+
+ if cmd == 'restart' or cmd == 'stop':
+ result = self.stop_daemon()
+ if result:
+ if cmd == 'restart':
+ self.out("Could not stop daemon; aborting")
+ else:
+ self.out("Could not stop daemon")
+ return result
+ if cmd == 'stop':
+ return result
+ self.options.daemon = True
+
+ if cmd == 'start':
+ self.options.daemon = True
+
+ app_name = self.options.app_name
+ vars = self.parse_vars(restvars)
+ if not self._scheme_re.search(app_spec):
+ app_spec = 'config:' + app_spec
+ server_name = self.options.server_name
+ if self.options.server:
+ server_spec = 'egg:pyramid'
+ assert server_name is None
+ server_name = self.options.server
+ else:
+ server_spec = app_spec
+ base = os.getcwd()
+
+ if getattr(self.options, 'daemon', False):
+ if not self.options.pid_file:
+ self.options.pid_file = 'pyramid.pid'
+ if not self.options.log_file:
+ self.options.log_file = 'pyramid.log'
+
+ # Ensure the log file is writeable
+ if self.options.log_file:
+ try:
+ writeable_log_file = open(self.options.log_file, 'a')
+ except IOError as ioe:
+ msg = 'Error: Unable to write to log file: %s' % ioe
+ raise ValueError(msg)
+ writeable_log_file.close()
+
+ # Ensure the pid file is writeable
+ if self.options.pid_file:
+ try:
+ writeable_pid_file = open(self.options.pid_file, 'a')
+ except IOError as ioe:
+ msg = 'Error: Unable to write to pid file: %s' % ioe
+ raise ValueError(msg)
+ writeable_pid_file.close()
+
+ if getattr(self.options, 'daemon', False):
+ try:
+ self.daemonize()
+ except DaemonizeException as ex:
+ if self.verbose > 0:
+ self.out(str(ex))
+ return
+
+ if (self.options.monitor_restart
+ and not os.environ.get(self._monitor_environ_key)):
+ return self.restart_with_monitor()
+
+ if self.options.pid_file:
+ self.record_pid(self.options.pid_file)
+
+ if self.options.log_file:
+ stdout_log = LazyWriter(self.options.log_file, 'a')
+ sys.stdout = stdout_log
+ sys.stderr = stdout_log
+ logging.basicConfig(stream=stdout_log)
+
+ log_fn = app_spec
+ if log_fn.startswith('config:'):
+ log_fn = app_spec[len('config:'):]
+ elif log_fn.startswith('egg:'):
+ log_fn = None
+ if log_fn:
+ log_fn = os.path.join(base, log_fn)
+ logging_file_config(log_fn)
+
+ server = self.loadserver(server_spec, name=server_name,
+ relative_to=base, global_conf=vars)
+ app = self.loadapp(app_spec, name=app_name,
+ relative_to=base, global_conf=vars)
+
+ if self.verbose > 0:
+ if hasattr(os, 'getpid'):
+ msg = 'Starting server in PID %i.' % os.getpid()
+ else:
+ msg = 'Starting server.'
+ self.out(msg)
+
+ def serve():
+ try:
+ server(app)
+ except (SystemExit, KeyboardInterrupt) as e:
+ if self.verbose > 1:
+ raise
+ if str(e):
+ msg = ' '+str(e)
+ else:
+ msg = ''
+ self.out('Exiting%s (-v to see traceback)' % msg)
+
+ serve()
+
+ def loadserver(self, server_spec, name, relative_to, **kw):
+ return loadserver(
+ server_spec, name=name, relative_to=relative_to, **kw)
+
+ def loadapp(self, app_spec, name, relative_to, **kw):
+ return loadapp(app_spec, name=name, relative_to=relative_to, **kw)
+
+ def parse_vars(self, args):
+ """
+ Given variables like ``['a=b', 'c=d']`` turns it into ``{'a':
+ 'b', 'c': 'd'}``
+ """
+ result = {}
+ for arg in args:
+ if '=' not in arg:
+ raise ValueError(
+ 'Variable assignment %r invalid (no "=")'
+ % arg)
+ name, value = arg.split('=', 1)
+ result[name] = value
+ return result
+
+ def quote_first_command_arg(self, arg):
+ """
+ There's a bug in Windows when running an executable that's
+ located inside a path with a space in it. This method handles
+ that case, or on non-Windows systems or an executable with no
+ spaces, it just leaves well enough alone.
+ """
+ if (sys.platform != 'win32' or ' ' not in arg):
+ # Problem does not apply:
+ return arg
+ try:
+ import win32api
+ except ImportError:
+ raise ValueError(
+ "The executable %r contains a space, and in order to "
+ "handle this issue you must have the win32api module "
+ "installed" % arg)
+ arg = win32api.GetShortPathName(arg)
+ return arg
+
+ def daemonize(self): # pragma: no cover (nfw)
+ pid = live_pidfile(self.options.pid_file)
+ if pid:
+ raise DaemonizeException(
+ "Daemon is already running (PID: %s from PID file %s)"
+ % (pid, self.options.pid_file))
+
+ if self.verbose > 0:
+ self.out('Entering daemon mode')
+ pid = os.fork()
+ if pid:
+ # The forked process also has a handle on resources, so we
+ # *don't* want proper termination of the process, we just
+ # want to exit quick (which os._exit() does)
+ os._exit(0)
+ # Make this the session leader
+ os.setsid()
+ # Fork again for good measure!
+ pid = os.fork()
+ if pid:
+ os._exit(0)
+
+ # @@: Should we set the umask and cwd now?
+
+ import resource # Resource usage information.
+ maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
+ if (maxfd == resource.RLIM_INFINITY):
+ maxfd = MAXFD
+ # Iterate through and close all file descriptors.
+ for fd in range(0, maxfd):
+ try:
+ os.close(fd)
+ except OSError: # ERROR, fd wasn't open to begin with (ignored)
+ pass
+
+ if (hasattr(os, "devnull")):
+ REDIRECT_TO = os.devnull
+ else:
+ REDIRECT_TO = "/dev/null"
+ os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
+ # Duplicate standard input to standard output and standard error.
+ os.dup2(0, 1) # standard output (1)
+ os.dup2(0, 2) # standard error (2)
+
+ def _remove_pid_file(self, written_pid, filename, verbosity):
+ current_pid = os.getpid()
+ if written_pid != current_pid:
+ # A forked process must be exiting, not the process that
+ # wrote the PID file
+ return
+ if not os.path.exists(filename):
+ return
+ f = open(filename)
+ content = f.read().strip()
+ f.close()
+ try:
+ pid_in_file = int(content)
+ except ValueError:
+ pass
+ else:
+ if pid_in_file != current_pid:
+ self.out("PID file %s contains %s, not expected PID %s" % (
+ filename, pid_in_file, current_pid))
+ return
+ if verbosity > 0:
+ self.out("Removing PID file %s" % filename)
+ try:
+ os.unlink(filename)
+ return
+ except OSError as e:
+ # Record, but don't give traceback
+ self.out("Cannot remove PID file: %s" % e)
+ # well, at least lets not leave the invalid PID around...
+ try:
+ f = open(filename, 'w')
+ f.write('')
+ f.close()
+ except OSError as e:
+ self.out('Stale PID left in file: %s (%e)' % (filename, e))
+ else:
+ self.out('Stale PID removed')
+
+ def record_pid(self, pid_file): # pragma: no cover (nfw)
+ pid = os.getpid()
+ if self.verbose > 1:
+ self.out('Writing PID %s to %s' % (pid, pid_file))
+ f = open(pid_file, 'w')
+ f.write(str(pid))
+ f.close()
+ atexit.register(self._remove_pid_file, pid, pid_file, self.verbose)
+
+ def stop_daemon(self): # pragma: no cover (nfw)
+ pid_file = self.options.pid_file or 'pyramid.pid'
+ if not os.path.exists(pid_file):
+ self.out('No PID file exists in %s' % pid_file)
+ return 1
+ pid = read_pidfile(pid_file)
+ if not pid:
+ self.out("Not a valid PID file in %s" % pid_file)
+ return 1
+ pid = live_pidfile(pid_file)
+ if not pid:
+ self.out("PID in %s is not valid (deleting)" % pid_file)
+ try:
+ os.unlink(pid_file)
+ except (OSError, IOError) as e:
+ self.out("Could not delete: %s" % e)
+ return 2
+ return 1
+ for j in range(10):
+ if not live_pidfile(pid_file):
+ break
+ import signal
+ os.kill(pid, signal.SIGTERM)
+ time.sleep(1)
+ else:
+ self.out("failed to kill web process %s" % pid)
+ return 3
+ if os.path.exists(pid_file):
+ os.unlink(pid_file)
+ return 0
+
+ def show_status(self):
+ pid_file = self.options.pid_file or 'pyramid.pid'
+ if not os.path.exists(pid_file):
+ self.out('No PID file %s' % pid_file)
+ return 1
+ pid = read_pidfile(pid_file)
+ if not pid:
+ self.out('No PID in file %s' % pid_file)
+ return 1
+ pid = live_pidfile(pid_file)
+ if not pid:
+ self.out('PID %s in %s is not running' % (pid, pid_file))
+ return 1
+ self.out('Server running in PID %s' % pid)
+ return 0
+
+ def restart_with_reloader(self): # pragma: no cover (nfw)
+ self.restart_with_monitor(reloader=True)
+
+ def restart_with_monitor(self, reloader=False): # pragma: no cover (nfw)
+ if self.verbose > 0:
+ if reloader:
+ self.out('Starting subprocess with file monitor')
+ else:
+ self.out('Starting subprocess with monitor parent')
+ while 1:
+ args = [self.quote_first_command_arg(sys.executable)] + sys.argv
+ new_environ = os.environ.copy()
+ if reloader:
+ new_environ[self._reloader_environ_key] = 'true'
+ else:
+ new_environ[self._monitor_environ_key] = 'true'
+ proc = None
+ try:
+ try:
+ _turn_sigterm_into_systemexit()
+ proc = subprocess.Popen(args, env=new_environ)
+ exit_code = proc.wait()
+ proc = None
+ except KeyboardInterrupt:
+ self.out('^C caught in monitor process')
+ if self.verbose > 1:
+ raise
+ return 1
+ finally:
+ if (proc is not None
+ and hasattr(os, 'kill')):
+ import signal
+ try:
+ os.kill(proc.pid, signal.SIGTERM)
+ except (OSError, IOError):
+ pass
+
+ if reloader:
+ # Reloader always exits with code 3; but if we are
+ # a monitor, any exit code will restart
+ if exit_code != 3:
+ return exit_code
+ if self.verbose > 0:
+ self.out('%s %s %s' % ('-'*20, 'Restarting', '-'*20))
+
+ def change_user_group(self, user, group): # pragma: no cover (nfw)
+ if not user and not group:
+ return
+ import pwd, grp
+ uid = gid = None
+ if group:
+ try:
+ gid = int(group)
+ group = grp.getgrgid(gid).gr_name
+ except ValueError:
+ import grp
+ try:
+ entry = grp.getgrnam(group)
+ except KeyError:
+ raise ValueError(
+ "Bad group: %r; no such group exists" % group)
+ gid = entry.gr_gid
+ try:
+ uid = int(user)
+ user = pwd.getpwuid(uid).pw_name
+ except ValueError:
+ try:
+ entry = pwd.getpwnam(user)
+ except KeyError:
+ raise ValueError(
+ "Bad username: %r; no such user exists" % user)
+ if not gid:
+ gid = entry.pw_gid
+ uid = entry.pw_uid
+ if self.verbose > 0:
+ self.out('Changing user to %s:%s (%s:%s)' % (
+ user, group or '(unknown)', uid, gid))
+ if gid:
+ os.setgid(gid)
+ if uid:
+ os.setuid(uid)
+
+class LazyWriter(object):
+
+ """
+ File-like object that opens a file lazily when it is first written
+ to.
+ """
+
+ def __init__(self, filename, mode='w'):
+ self.filename = filename
+ self.fileobj = None
+ self.lock = threading.Lock()
+ self.mode = mode
+
+ def open(self):
+ if self.fileobj is None:
+ self.lock.acquire()
+ try:
+ if self.fileobj is None:
+ self.fileobj = open(self.filename, self.mode)
+ finally:
+ self.lock.release()
+ return self.fileobj
+
+ def write(self, text):
+ fileobj = self.open()
+ fileobj.write(text)
+ fileobj.flush()
+
+ def writelines(self, text):
+ fileobj = self.open()
+ fileobj.writelines(text)
+ fileobj.flush()
+
+ def flush(self):
+ self.open().flush()
+
+def live_pidfile(pidfile): # pragma: no cover (nfw)
+ """(pidfile:str) -> int | None
+ Returns an int found in the named file, if there is one,
+ and if there is a running process with that process id.
+ Return None if no such process exists.
+ """
+ pid = read_pidfile(pidfile)
+ if pid:
+ try:
+ os.kill(int(pid), 0)
+ return pid
+ except OSError as e:
+ if e.errno == errno.EPERM:
+ return pid
+ return None
+
+def read_pidfile(filename):
+ if os.path.exists(filename):
+ try:
+ f = open(filename)
+ content = f.read()
+ f.close()
+ return int(content.strip())
+ except (ValueError, IOError):
+ return None
+ else:
+ return None
+
+def ensure_port_cleanup(
+ bound_addresses, maxtries=30, sleeptime=2): # pragma: no cover (nfw)
+ """
+ This makes sure any open ports are closed.
+
+ Does this by connecting to them until they give connection
+ refused. Servers should call like::
+
+ ensure_port_cleanup([80, 443])
+ """
+ atexit.register(_cleanup_ports, bound_addresses, maxtries=maxtries,
+ sleeptime=sleeptime)
+
+def _cleanup_ports(
+ bound_addresses, maxtries=30, sleeptime=2): # pragma: no cover (nfw)
+ # Wait for the server to bind to the port.
+ import socket
+ import errno
+ for bound_address in bound_addresses:
+ for attempt in range(maxtries):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ sock.connect(bound_address)
+ except socket.error as e:
+ if e.args[0] != errno.ECONNREFUSED:
+ raise
+ break
+ else:
+ time.sleep(sleeptime)
+ else:
+ raise SystemExit('Timeout waiting for port.')
+ sock.close()
+
+def _turn_sigterm_into_systemexit(): # pragma: no cover (nfw)
+ """
+ Attempts to turn a SIGTERM exception into a SystemExit exception.
+ """
+ try:
+ import signal
+ except ImportError:
+ return
+ def handle_term(signo, frame):
+ raise SystemExit
+ signal.signal(signal.SIGTERM, handle_term)
+
+def install_reloader(poll_interval=1): # pragma: no cover (nfw)
+ """
+ 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 classinstancemethod(object):
+ """
+ Acts like a class method when called from a class, like an
+ instance method when called by an instance. The method should
+ take two arguments, 'self' and 'cls'; one of these will be None
+ depending on how the method was called.
+ """
+
+ def __init__(self, func):
+ self.func = func
+ self.__doc__ = func.__doc__
+
+ def __get__(self, obj, type=None):
+ return _methodwrapper(self.func, obj=obj, type=type)
+
+class _methodwrapper(object):
+
+ def __init__(self, func, obj, type):
+ self.func = func
+ self.obj = obj
+ self.type = type
+
+ def __call__(self, *args, **kw):
+ assert not 'self' in kw and not 'cls' in kw, (
+ "You cannot use 'self' or 'cls' arguments to a "
+ "classinstancemethod")
+ return self.func(*((self.obj, self.type) + args), **kw)
+
+ def __repr__(self):
+ if self.obj is None:
+ return ('<bound class method %s.%s>'
+ % (self.type.__name__, self.func.func_name))
+ else:
+ return ('<bound method %s.%s of %r>'
+ % (self.type.__name__, self.func.func_name, self.obj))
+
+
+class Monitor(object):
+ """
+ A file monitor and server restarter.
+
+ Use this like:
+
+ ..code-block:: Python
+
+ install_reloader()
+
+ 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 (``pserve --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()
+ 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.
+ """
+ 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): # pragma: no cover (nfw)
+ 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 reloader callback %r:" % file_callback)
+ 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)
+ if not filename in self.module_mtimes:
+ self.module_mtimes[filename] = mtime
+ elif self.module_mtimes[filename] < mtime:
+ print("%s changed; reloading..." % filename)
+ 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)
+
+watch_file = Monitor.watch_file
+add_file_callback = Monitor.add_file_callback
+
+# For paste.deploy server instantiation (egg:pyramid#wsgiref)
+def wsgiref_server_runner(wsgi_app, global_conf, **kw): # pragma: no cover
+ from wsgiref.simple_server import make_server
+ host = kw.get('host', '0.0.0.0')
+ port = int(kw.get('port', 8080))
+ server = make_server(host, port, wsgi_app)
+ print('Starting HTTP server on http://%s:%s' % (host, port))
+ server.serve_forever()