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