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()