Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an --app= flag for specifying the WSGI application #457

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
12 changes: 11 additions & 1 deletion docs/runner.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <invocation>`.

Boolean arguments are represented by flags. If you wish to explicitly set a
Expand Down Expand Up @@ -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.

Expand Down
8 changes: 8 additions & 0 deletions src/waitress/adjustments.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,9 +459,12 @@ def parse_args(cls, argv):
else:
long_opts.append(opt + "=")

long_opts.append("app=")
kgaughan marked this conversation as resolved.
Show resolved Hide resolved

kw = {
"help": False,
"call": False,
"app": None,
}

opts, args = getopt.getopt(argv, "", long_opts)
Expand All @@ -477,11 +480,16 @@ 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:
kw[param] = value

if kw["app"] is None and len(args) > 0:
kw["app"] = args.pop(0)

return kw, args

@classmethod
Expand Down
16 changes: 11 additions & 5 deletions src/waitress/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -308,8 +314,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
Expand All @@ -323,7 +329,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)
Expand All @@ -332,7 +338,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
18 changes: 14 additions & 4 deletions tests/test_adjustments.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,22 +396,32 @@ 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_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):
Expand Down
4 changes: 2 additions & 2 deletions tests/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'")
Expand Down