From: Ben Darnell Date: Sun, 23 Jul 2023 01:09:36 +0000 (-0400) Subject: autoreload: Switch to a real option parser X-Git-Tag: v6.4.0b1~20^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bf3b76b3faa852f3b90cf483db6f374f19444f9a;p=thirdparty%2Ftornado.git autoreload: Switch to a real option parser This will make it easier to add other options (for #2398) --- diff --git a/tornado/autoreload.py b/tornado/autoreload.py index 07b9112a6..262a30590 100644 --- a/tornado/autoreload.py +++ b/tornado/autoreload.py @@ -268,8 +268,7 @@ def _reload() -> None: os._exit(0) -_USAGE = """\ -Usage: +_USAGE = """ python -m tornado.autoreload -m module.to.run [args...] python -m tornado.autoreload path/to/script.py [args...] """ @@ -291,6 +290,12 @@ def main() -> None: # Remember that we were launched with autoreload as main. # The main module can be tricky; set the variables both in our globals # (which may be __main__) and the real importable version. + # + # We use optparse instead of the newer argparse because we want to + # mimic the python command-line interface which requires stopping + # parsing at the first positional argument. optparse supports + # this but as far as I can tell argparse does not. + import optparse import tornado.autoreload global _autoreload_is_main @@ -300,32 +305,39 @@ def main() -> None: tornado.autoreload._original_argv = _original_argv = original_argv original_spec = getattr(sys.modules["__main__"], "__spec__", None) tornado.autoreload._original_spec = _original_spec = original_spec - 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:] + + parser = optparse.OptionParser( + prog="python -m tornado.autoreload", + usage=_USAGE, + epilog="Either -m or a path must be specified, but not both", + ) + parser.disable_interspersed_args() + parser.add_option("-m", dest="module", metavar="module", help="module to run") + opts, rest = parser.parse_args() + if opts.module is None: + if not rest: + print("Either -m or a path must be specified", file=sys.stderr) + sys.exit(1) + path = rest[0] + sys.argv = rest[:] else: - print(_USAGE, file=sys.stderr) - sys.exit(1) + path = None + sys.argv = [sys.argv[0]] + rest try: - if mode == "module": + if opts.module is not None: import runpy - runpy.run_module(module, run_name="__main__", alter_sys=True) - elif mode == "script": - with open(script) as f: + runpy.run_module(opts.module, run_name="__main__", alter_sys=True) + else: + assert path is not None + with open(path) as f: # Execute the script in our namespace instead of creating # a new one so that something that tries to import __main__ # (e.g. the unittest module) will see names defined in the # script instead of just those defined in this module. global __file__ - __file__ = script + __file__ = path # If __package__ is defined, imports may be incorrectly # interpreted as relative to this module. global __package__ @@ -352,10 +364,11 @@ def main() -> None: # restore sys.argv so subsequent executions will include autoreload sys.argv = original_argv - if mode == "module": + if opts.module is not None: + assert opts.module is not None # runpy did a fake import of the module as __main__, but now it's # no longer in sys.modules. Figure out where it is and watch it. - loader = pkgutil.get_loader(module) + loader = pkgutil.get_loader(opts.module) if loader is not None: watch(loader.get_filename()) # type: ignore diff --git a/tornado/test/autoreload_test.py b/tornado/test/autoreload_test.py index feee94f3b..c08c5be36 100644 --- a/tornado/test/autoreload_test.py +++ b/tornado/test/autoreload_test.py @@ -183,3 +183,47 @@ else: out = autoreload_proc.communicate()[0] self.assertEqual(out, "Starting\n" * 2) + + def test_reload_wrapper_args(self): + main = """\ +import os +import sys + +print(os.path.basename(sys.argv[0])) +print(f'argv={sys.argv[1:]}') +sys.stdout.flush() +os._exit(0) +""" + # Create temporary test application + main_file = os.path.join(self.path, "main.py") + with open(main_file, "w", encoding="utf-8") as f: + f.write(main) + + # Make sure the tornado module under test is available to the test + # application + pythonpath = os.getcwd() + if "PYTHONPATH" in os.environ: + pythonpath += os.pathsep + os.environ["PYTHONPATH"] + + autoreload_proc = Popen( + [ + sys.executable, + "-m", + "tornado.autoreload", + "main.py", + "arg1", + "--arg2", + "-m", + "arg3", + ], + stdout=subprocess.PIPE, + cwd=self.path, + env=dict(os.environ, PYTHONPATH=pythonpath), + universal_newlines=True, + encoding="utf-8", + ) + + out, err = autoreload_proc.communicate() + if err: + print("subprocess stderr:", err) + self.assertEqual(out, "main.py\nargv=['arg1', '--arg2', '-m', 'arg3']\n")