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 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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=")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The option can’t be added to cls._params ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cls._params is for the configuration passed to waitress.serve() as keyword arguments. The app is an argument for waitress.serve(), but it's not one of the keyword arguments and requires some extra processing, and I'm trying to keep this change to a minimum. Incorporating it into cls._params could be worth revisiting separately though.


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