From c970e55747aee93e1464afd09fa95a8b8554446e Mon Sep 17 00:00:00 2001 From: Keith Gaughan Date: Sun, 24 Nov 2024 15:38:34 +0000 Subject: [PATCH 1/2] A less invasive --app flag --- src/waitress/adjustments.py | 6 ++++++ src/waitress/runner.py | 8 ++++---- tests/test_adjustments.py | 8 ++++---- tests/test_runner.py | 4 ++-- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/waitress/adjustments.py b/src/waitress/adjustments.py index 6266245c..4354ec45 100644 --- a/src/waitress/adjustments.py +++ b/src/waitress/adjustments.py @@ -459,9 +459,12 @@ def parse_args(cls, argv): else: long_opts.append(opt + "=") + long_opts.append("app=") + kw = { "help": False, "call": False, + "app": None, } opts, args = getopt.getopt(argv, "", long_opts) @@ -482,6 +485,9 @@ def parse_args(cls, argv): else: kw[param] = value + if kw["app"] is None and len(args) > 0: + kw["app"] = args.pop(0) + return kw, args @classmethod diff --git a/src/waitress/runner.py b/src/waitress/runner.py index 90f42970..cc13f275 100644 --- a/src/waitress/runner.py +++ b/src/waitress/runner.py @@ -308,8 +308,8 @@ def run(argv=sys.argv, _serve=serve): show_help(sys.stdout, name) return 0 - if len(args) != 1: - show_help(sys.stderr, name, "Specify one application only") + if kw["app"] is None: + show_help(sys.stderr, name, "Specify an application") return 1 # set a default level for the logger only if it hasn't been set explicitly @@ -323,7 +323,7 @@ def run(argv=sys.argv, _serve=serve): # Get the WSGI function. try: - app = pkgutil.resolve_name(args[0]) + app = pkgutil.resolve_name(kw["app"]) except (ValueError, ImportError, AttributeError) as exc: show_help(sys.stderr, name, str(exc)) show_exception(sys.stderr) @@ -332,7 +332,7 @@ def run(argv=sys.argv, _serve=serve): app = app() # These arguments are specific to the runner, not waitress itself. - del kw["call"], kw["help"] + del kw["call"], kw["help"], kw["app"] _serve(app, **kw) return 0 diff --git a/tests/test_adjustments.py b/tests/test_adjustments.py index b7243a92..e7f4b713 100644 --- a/tests/test_adjustments.py +++ b/tests/test_adjustments.py @@ -396,22 +396,22 @@ def assertDictContainsSubset(self, subset, dictionary): def test_noargs(self): opts, args = self.parse([]) - self.assertDictEqual(opts, {"call": False, "help": False}) + self.assertDictEqual(opts, {"call": False, "help": False, "app": None}) self.assertSequenceEqual(args, []) def test_help(self): opts, args = self.parse(["--help"]) - self.assertDictEqual(opts, {"call": False, "help": True}) + self.assertDictEqual(opts, {"call": False, "help": True, "app": None}) self.assertSequenceEqual(args, []) def test_call(self): opts, args = self.parse(["--call"]) - self.assertDictEqual(opts, {"call": True, "help": False}) + self.assertDictEqual(opts, {"call": True, "help": False, "app": None}) self.assertSequenceEqual(args, []) def test_both(self): opts, args = self.parse(["--call", "--help"]) - self.assertDictEqual(opts, {"call": True, "help": True}) + self.assertDictEqual(opts, {"call": True, "help": True, "app": None}) self.assertSequenceEqual(args, []) def test_positive_boolean(self): diff --git a/tests/test_runner.py b/tests/test_runner.py index ad43a35d..e56cdd9b 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -21,10 +21,10 @@ def test_help(self): self.match_output(["--help"], 0, "^Usage:\n\n waitress-serve") def test_no_app(self): - self.match_output([], 1, "^Error: Specify one application only") + self.match_output([], 1, "^Error: Specify an application") def test_multiple_apps_app(self): - self.match_output(["a:a", "b:b"], 1, "^Error: Specify one application only") + self.match_output(["a:a", "b:b"], 1, "^Error: No module named 'a'") def test_bad_apps_app(self): self.match_output(["a"], 1, "^Error: No module named 'a'") From 109bf418b08ab020668e971f498a6fb8a7d68d79 Mon Sep 17 00:00:00 2001 From: Keith Gaughan Date: Sun, 24 Nov 2024 19:40:00 +0000 Subject: [PATCH 2/2] Add a proper test for the --app flag and docs. --- docs/runner.rst | 12 +++++++++++- src/waitress/adjustments.py | 2 ++ src/waitress/runner.py | 8 +++++++- tests/test_adjustments.py | 10 ++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/runner.rst b/docs/runner.rst index 6d687f88..d6dbee1a 100644 --- a/docs/runner.rst +++ b/docs/runner.rst @@ -20,6 +20,10 @@ Is equivalent to:: waitress-serve --port=8041 --url-scheme=https myapp:wsgifunc +Or: + + waitress-serve --port=8041 --url-scheme=https --app=myapp:wsgifunc + The full argument list is :ref:`given below `. Boolean arguments are represented by flags. If you wish to explicitly set a @@ -64,13 +68,19 @@ Invocation Usage:: - waitress-serve [OPTS] MODULE:OBJECT + waitress-serve [OPTS] [MODULE:OBJECT] Common options: ``--help`` Show this information. +``--app=MODULE:OBJECT`` + Run the given callable object the WSGI application. + + You can specify the WSGI application using this flag or as a positional + argument. + ``--call`` Call the given object to get the WSGI application. diff --git a/src/waitress/adjustments.py b/src/waitress/adjustments.py index 4354ec45..6049c914 100644 --- a/src/waitress/adjustments.py +++ b/src/waitress/adjustments.py @@ -480,6 +480,8 @@ def parse_args(cls, argv): kw[param] = "false" elif param in ("help", "call"): kw[param] = True + elif param == "app": + kw[param] = value elif cls._param_map[param] is asbool: kw[param] = "true" else: diff --git a/src/waitress/runner.py b/src/waitress/runner.py index cc13f275..25b8a8c0 100644 --- a/src/waitress/runner.py +++ b/src/waitress/runner.py @@ -29,13 +29,19 @@ HELP = """\ Usage: - {0} [OPTS] MODULE:OBJECT + {0} [OPTS] [MODULE:OBJECT] Standard options: --help Show this information. + --app=MODULE:OBJECT + Run the given callable object the WSGI application. + + You can specify the WSGI application using this flag or as a positional + argument. + --call Call the given object to get the WSGI application. diff --git a/tests/test_adjustments.py b/tests/test_adjustments.py index e7f4b713..fa6fe110 100644 --- a/tests/test_adjustments.py +++ b/tests/test_adjustments.py @@ -414,6 +414,16 @@ def test_both(self): self.assertDictEqual(opts, {"call": True, "help": True, "app": None}) self.assertSequenceEqual(args, []) + def test_app_flag(self): + opts, args = self.parse(["--app=fred:wilma", "barney:betty"]) + self.assertEqual(opts["app"], "fred:wilma") + self.assertSequenceEqual(args, ["barney:betty"]) + + def test_app_arg(self): + opts, args = self.parse(["barney:betty"]) + self.assertEqual(opts["app"], "barney:betty") + self.assertSequenceEqual(args, []) + def test_positive_boolean(self): opts, args = self.parse(["--expose-tracebacks"]) self.assertDictContainsSubset({"expose_tracebacks": "true"}, opts)