From: Ben Darnell Date: Sun, 26 Jun 2011 22:24:23 +0000 (-0700) Subject: Add command-line interface to tornado.autoreload X-Git-Tag: v2.1.0~145 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=815a1f06c2aedb053241d5e29ce917d3ec6c77ad;p=thirdparty%2Ftornado.git Add command-line interface to tornado.autoreload --- diff --git a/tornado/autoreload.py b/tornado/autoreload.py index 2ed0faec5..e22365747 100644 --- a/tornado/autoreload.py +++ b/tornado/autoreload.py @@ -26,6 +26,8 @@ and Google AppEngine. It also will not work correctly when HTTPServer's multi-process mode is used. """ +from __future__ import with_statement + import functools import logging import os @@ -51,6 +53,25 @@ def start(io_loop=None, check_time=500): scheduler = ioloop.PeriodicCallback(callback, check_time, io_loop=io_loop) scheduler.start() +def wait(): + """Wait for a watched file to change, then restart the process. + + Intended to be used at the end of scripts like unit test runners, + to run the tests again after any source file changes (but see also + the command-line interface in `main`) + """ + io_loop = ioloop.IOLoop() + start(io_loop) + io_loop.start() + +_watched_files = set() + +def watch(filename): + """Add a file to the watch list. + + All imported modules are watched by default. + """ + _watched_files.add(filename) _reload_attempted = False @@ -69,13 +90,18 @@ def _reload_on_update(io_loop, modify_times): if not path: continue if path.endswith(".pyc") or path.endswith(".pyo"): path = path[:-1] + _check_file(io_loop, modify_times, path) + for path in _watched_files: + _check_file(io_loop, modify_times, path) + +def _check_file(io_loop, modify_times, path): try: modified = os.stat(path).st_mtime except: - continue + return if path not in modify_times: modify_times[path] = modified - continue + return if modify_times[path] != modified: logging.info("%s modified; restarting server", path) _reload_attempted = True @@ -106,3 +132,61 @@ def _reload_on_update(io_loop, modify_times): os.spawnv(os.P_NOWAIT, sys.executable, [sys.executable] + sys.argv) sys.exit(0) + +_USAGE = """\ +Usage: + python -m tornado.autoreload -m module.to.run [args...] + python -m tornado.autoreload path/to/script.py [args...] +""" +def main(): + """Command-line wrapper to re-run a script whenever its source changes. + + Scripts may be specified by filename or module name:: + + python -m tornado.autoreload -m tornado.test.runtests + python -m tornado.autoreload tornado/test/runtests.py + + Running a script with this wrapper is similar to calling + `tornado.autoreload.wait` at the end of the script, but this wrapper + can catch import-time problems like syntax errors that would otherwise + prevent the script from reaching its call to `wait`. + """ + original_argv = sys.argv + sys.argv = sys.argv[:] + if len(sys.argv) >= 3 and sys.argv[1] == "-m": + mode = "module" + module = sys.argv[2] + del sys.argv[1:3] + elif len(sys.argv) >= 2: + mode = "script" + script = sys.argv[1] + sys.argv = sys.argv[1:] + else: + print >>sys.stderr, _USAGE + sys.exit(1) + + try: + if mode == "module": + import runpy + runpy.run_module(module, run_name="__main__", alter_sys=True) + elif mode == "script": + with open(script) as f: + global __file__ + __file__ = script + # Use globals as our "locals" dictionary so that + # something that tries to import __main__ (e.g. the unittest + # module) will see the right things. + exec f.read() in globals(), globals() + print "exec done" + except SystemExit, e: + logging.info("Script exited with status %s", e.code) + except Exception, e: + logging.warning("Script exited with uncaught exception", exc_info=True) + if isinstance(e, SyntaxError): + watch(e.filename) + sys.argv = original_argv + wait() + + +if __name__ == "__main__": + main() diff --git a/tornado/testing.py b/tornado/testing.py index cb724d4f4..f068b167d 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -294,11 +294,15 @@ class LogTrapTestCase(unittest.TestCase): handler.stream = old_stream def main(): - """A simple test runner with autoreload support. + """A simple test runner. + + This test runner is essentially equivalent to `unittest.main` from + the standard library, but adds support for tornado-style option + parsing and log formatting. The easiest way to run a test is via the command line:: - python -m tornado.testing --autoreload tornado.test.stack_context_test + python -m tornado.testing tornado.test.stack_context_test See the standard library unittest module for ways in which tests can be specified. @@ -310,19 +314,15 @@ def main(): be overridden by naming a single test on the command line:: # Runs all tests - tornado/test/runtests.py --autoreload + tornado/test/runtests.py # Runs one test - tornado/test/runtests.py --autoreload tornado.test.stack_context_test + tornado/test/runtests.py tornado.test.stack_context_test - If --autoreload is specified, the process will continue running - after the tests finish, and when any source file changes the tests - will be rerun. Without --autoreload, the process will exit - once the tests finish (with an exit status of 0 for success and - non-zero for failures). """ from tornado.options import define, options, parse_command_line - define('autoreload', type=bool, default=False) + define('autoreload', type=bool, default=False, + help="DEPRECATED: use tornado.autoreload.main instead") define('httpclient', type=str, default=None) argv = [sys.argv[0]] + parse_command_line(sys.argv) @@ -353,10 +353,7 @@ def main(): raise if options.autoreload: import tornado.autoreload - import tornado.ioloop - ioloop = tornado.ioloop.IOLoop() - tornado.autoreload.start(ioloop) - ioloop.start() + tornado.autoreload.wait() if __name__ == '__main__': main() diff --git a/website/sphinx/releases/next.rst b/website/sphinx/releases/next.rst index a7961da48..15f20a934 100644 --- a/website/sphinx/releases/next.rst +++ b/website/sphinx/releases/next.rst @@ -8,6 +8,11 @@ New features ~~~~~~~~~~~~ * New method `tornado.iostream.IOStream.read_until_close` +* `tornado.autoreload` has a new command-line interface which can be used + to wrap any script. This replaces the ``--autoreload`` argument to + `tornado.testing.main` and is more robust against syntax errors. +* `tornado.autoreload.watch` can be used to watch files other than + the sources of imported modules. * `tornado.ioloop.IOLoop` and `tornado.httpclient.HTTPClient` now have ``close()`` methods that should be used in applications that create and destroy many of these objects.