Go to the documentation of this file.00001 
00002 
00003 
00004 
00005 
00006 
00007 
00008 
00009 
00010 
00011 
00012 
00013 
00014 
00015 
00016 
00017 """A module to automatically restart the server when a module is modified.
00018 
00019 Most applications should not call this module directly.  Instead, pass the
00020 keyword argument ``debug=True`` to the `tornado.web.Application` constructor.
00021 This will enable autoreload mode as well as checking for changes to templates
00022 and static resources.
00023 
00024 This module depends on IOLoop, so it will not work in WSGI applications
00025 and Google AppEngine.  It also will not work correctly when HTTPServer's
00026 multi-process mode is used.
00027 """
00028 
00029 from __future__ import absolute_import, division, with_statement
00030 
00031 import os
00032 import sys
00033 
00034 
00035 
00036 
00037 
00038 
00039 
00040 
00041 
00042 
00043 
00044 
00045 
00046 
00047 
00048 
00049 
00050 
00051 
00052 
00053 
00054 if __name__ == "__main__":
00055     
00056     
00057     
00058     
00059     
00060     
00061     if sys.path[0] == os.path.dirname(__file__):
00062         del sys.path[0]
00063 
00064 import functools
00065 import logging
00066 import os
00067 import pkgutil
00068 import sys
00069 import types
00070 import subprocess
00071 
00072 from tornado import ioloop
00073 from tornado import process
00074 
00075 try:
00076     import signal
00077 except ImportError:
00078     signal = None
00079 
00080 
00081 def start(io_loop=None, check_time=500):
00082     """Restarts the process automatically when a module is modified.
00083 
00084     We run on the I/O loop, and restarting is a destructive operation,
00085     so will terminate any pending requests.
00086     """
00087     io_loop = io_loop or ioloop.IOLoop.instance()
00088     add_reload_hook(functools.partial(_close_all_fds, io_loop))
00089     modify_times = {}
00090     callback = functools.partial(_reload_on_update, modify_times)
00091     scheduler = ioloop.PeriodicCallback(callback, check_time, io_loop=io_loop)
00092     scheduler.start()
00093 
00094 
00095 def wait():
00096     """Wait for a watched file to change, then restart the process.
00097 
00098     Intended to be used at the end of scripts like unit test runners,
00099     to run the tests again after any source file changes (but see also
00100     the command-line interface in `main`)
00101     """
00102     io_loop = ioloop.IOLoop()
00103     start(io_loop)
00104     io_loop.start()
00105 
00106 _watched_files = set()
00107 
00108 
00109 def watch(filename):
00110     """Add a file to the watch list.
00111 
00112     All imported modules are watched by default.
00113     """
00114     _watched_files.add(filename)
00115 
00116 _reload_hooks = []
00117 
00118 
00119 def add_reload_hook(fn):
00120     """Add a function to be called before reloading the process.
00121 
00122     Note that for open file and socket handles it is generally
00123     preferable to set the ``FD_CLOEXEC`` flag (using `fcntl` or
00124     `tornado.platform.auto.set_close_exec`) instead of using a reload
00125     hook to close them.
00126     """
00127     _reload_hooks.append(fn)
00128 
00129 
00130 def _close_all_fds(io_loop):
00131     for fd in io_loop._handlers.keys():
00132         try:
00133             os.close(fd)
00134         except Exception:
00135             pass
00136 
00137 _reload_attempted = False
00138 
00139 
00140 def _reload_on_update(modify_times):
00141     if _reload_attempted:
00142         
00143         return
00144     if process.task_id() is not None:
00145         
00146         
00147         
00148         return
00149     for module in sys.modules.values():
00150         
00151         
00152         
00153         
00154         if not isinstance(module, types.ModuleType):
00155             continue
00156         path = getattr(module, "__file__", None)
00157         if not path:
00158             continue
00159         if path.endswith(".pyc") or path.endswith(".pyo"):
00160             path = path[:-1]
00161         _check_file(modify_times, path)
00162     for path in _watched_files:
00163         _check_file(modify_times, path)
00164 
00165 
00166 def _check_file(modify_times, path):
00167     try:
00168         modified = os.stat(path).st_mtime
00169     except Exception:
00170         return
00171     if path not in modify_times:
00172         modify_times[path] = modified
00173         return
00174     if modify_times[path] != modified:
00175         logging.info("%s modified; restarting server", path)
00176         _reload()
00177 
00178 
00179 def _reload():
00180     global _reload_attempted
00181     _reload_attempted = True
00182     for fn in _reload_hooks:
00183         fn()
00184     if hasattr(signal, "setitimer"):
00185         
00186         
00187         
00188         signal.setitimer(signal.ITIMER_REAL, 0, 0)
00189     
00190     
00191     
00192     
00193     path_prefix = '.' + os.pathsep
00194     if (sys.path[0] == '' and
00195         not os.environ.get("PYTHONPATH", "").startswith(path_prefix)):
00196         os.environ["PYTHONPATH"] = (path_prefix +
00197                                     os.environ.get("PYTHONPATH", ""))
00198     if sys.platform == 'win32':
00199         
00200         
00201         
00202         subprocess.Popen([sys.executable] + sys.argv)
00203         sys.exit(0)
00204     else:
00205         try:
00206             os.execv(sys.executable, [sys.executable] + sys.argv)
00207         except OSError:
00208             
00209             
00210             
00211             
00212             
00213             
00214             
00215             
00216             
00217             
00218             
00219             os.spawnv(os.P_NOWAIT, sys.executable,
00220                       [sys.executable] + sys.argv)
00221             sys.exit(0)
00222 
00223 _USAGE = """\
00224 Usage:
00225   python -m tornado.autoreload -m module.to.run [args...]
00226   python -m tornado.autoreload path/to/script.py [args...]
00227 """
00228 
00229 
00230 def main():
00231     """Command-line wrapper to re-run a script whenever its source changes.
00232 
00233     Scripts may be specified by filename or module name::
00234 
00235         python -m tornado.autoreload -m tornado.test.runtests
00236         python -m tornado.autoreload tornado/test/runtests.py
00237 
00238     Running a script with this wrapper is similar to calling
00239     `tornado.autoreload.wait` at the end of the script, but this wrapper
00240     can catch import-time problems like syntax errors that would otherwise
00241     prevent the script from reaching its call to `wait`.
00242     """
00243     original_argv = sys.argv
00244     sys.argv = sys.argv[:]
00245     if len(sys.argv) >= 3 and sys.argv[1] == "-m":
00246         mode = "module"
00247         module = sys.argv[2]
00248         del sys.argv[1:3]
00249     elif len(sys.argv) >= 2:
00250         mode = "script"
00251         script = sys.argv[1]
00252         sys.argv = sys.argv[1:]
00253     else:
00254         print >>sys.stderr, _USAGE
00255         sys.exit(1)
00256 
00257     try:
00258         if mode == "module":
00259             import runpy
00260             runpy.run_module(module, run_name="__main__", alter_sys=True)
00261         elif mode == "script":
00262             with open(script) as f:
00263                 global __file__
00264                 __file__ = script
00265                 
00266                 
00267                 
00268                 exec f.read() in globals(), globals()
00269     except SystemExit, e:
00270         logging.info("Script exited with status %s", e.code)
00271     except Exception, e:
00272         logging.warning("Script exited with uncaught exception", exc_info=True)
00273         if isinstance(e, SyntaxError):
00274             watch(e.filename)
00275     else:
00276         logging.info("Script exited normally")
00277     
00278     sys.argv = original_argv
00279 
00280     if mode == 'module':
00281         
00282         
00283         loader = pkgutil.get_loader(module)
00284         if loader is not None:
00285             watch(loader.get_filename())
00286 
00287     wait()
00288 
00289 
00290 if __name__ == "__main__":
00291     
00292     
00293     main()