autoreload.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # Copyright 2009 Facebook
00004 #
00005 # Licensed under the Apache License, Version 2.0 (the "License"); you may
00006 # not use this file except in compliance with the License. You may obtain
00007 # a copy of the License at
00008 #
00009 #     http://www.apache.org/licenses/LICENSE-2.0
00010 #
00011 # Unless required by applicable law or agreed to in writing, software
00012 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
00013 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
00014 # License for the specific language governing permissions and limitations
00015 # under the License.
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 # sys.path handling
00054 # -----------------
00055 #
00056 # If a module is run with "python -m", the current directory (i.e. "")
00057 # is automatically prepended to sys.path, but not if it is run as
00058 # "path/to/file.py".  The processing for "-m" rewrites the former to
00059 # the latter, so subsequent executions won't have the same path as the
00060 # original.
00061 #
00062 # Conversely, when run as path/to/file.py, the directory containing
00063 # file.py gets added to the path, which can cause confusion as imports
00064 # may become relative in spite of the future import.
00065 #
00066 # We address the former problem by setting the $PYTHONPATH environment
00067 # variable before re-execution so the new process will see the correct
00068 # path.  We attempt to address the latter problem when tornado.autoreload
00069 # is run as __main__, although we can't fix the general case because
00070 # we cannot reliably reconstruct the original command line
00071 # (http://bugs.python.org/issue14208).
00072 
00073 if __name__ == "__main__":
00074     # This sys.path manipulation must come before our imports (as much
00075     # as possible - if we introduced a tornado.sys or tornado.os
00076     # module we'd be in trouble), or else our imports would become
00077     # relative again despite the future import.
00078     #
00079     # There is a separate __main__ block at the end of the file to call main().
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         # We already tried to reload and it didn't work, so don't try again.
00159         return
00160     if process.task_id() is not None:
00161         # We're in a child process created by fork_processes.  If child
00162         # processes restarted themselves, they'd all restart and then
00163         # all call fork_processes again.
00164         return
00165     for module in sys.modules.values():
00166         # Some modules play games with sys.modules (e.g. email/__init__.py
00167         # in the standard library), and occasionally this can cause strange
00168         # failures in getattr.  Just ignore anything that's not an ordinary
00169         # module.
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         # Clear the alarm signal set by
00202         # ioloop.set_blocking_log_threshold so it doesn't fire
00203         # after the exec.
00204         signal.setitimer(signal.ITIMER_REAL, 0, 0)
00205     # sys.path fixes: see comments at top of file.  If sys.path[0] is an empty
00206     # string, we were (probably) invoked with -m and the effective path
00207     # is about to change on re-exec.  Add the current directory to $PYTHONPATH
00208     # to ensure that the new process sees the same path we did.
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         # os.execv is broken on Windows and can't properly parse command line
00216         # arguments and executable name if they contain whitespaces. subprocess
00217         # fixes that behavior.
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             # Mac OS X versions prior to 10.6 do not support execv in
00225             # a process that contains multiple threads.  Instead of
00226             # re-executing in the current process, start a new one
00227             # and cause the current process to exit.  This isn't
00228             # ideal since the new process is detached from the parent
00229             # terminal and thus cannot easily be killed with ctrl-C,
00230             # but it's better than not being able to autoreload at
00231             # all.
00232             # Unfortunately the errno returned in this case does not
00233             # appear to be consistent, so we can't easily check for
00234             # this error specifically.
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                 # Use globals as our "locals" dictionary so that
00282                 # something that tries to import __main__ (e.g. the unittest
00283                 # module) will see the right things.
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         # If an exception occurred at import time, the file with the error
00292         # never made it into sys.modules and so we won't know to watch it.
00293         # Just to make sure we've covered everything, walk the stack trace
00294         # from the exception and watch every file.
00295         for (filename, lineno, name, line) in traceback.extract_tb(sys.exc_info()[2]):
00296             watch(filename)
00297         if isinstance(e, SyntaxError):
00298             # SyntaxErrors are special:  their innermost stack frame is fake
00299             # so extract_tb won't see it and we have to get the filename
00300             # from the exception object.
00301             watch(e.filename)
00302     else:
00303         logging.basicConfig()
00304         gen_log.info("Script exited normally")
00305     # restore sys.argv so subsequent executions will include autoreload
00306     sys.argv = original_argv
00307 
00308     if mode == 'module':
00309         # runpy did a fake import of the module as __main__, but now it's
00310         # no longer in sys.modules.  Figure out where it is and watch it.
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     # See also the other __main__ block at the top of the file, which modifies
00320     # sys.path before our imports
00321     main()


rosbridge_tools
Author(s): Jonathan Mace
autogenerated on Sat Dec 27 2014 11:25:59