Update CLI for 3.0 (#1687)

Fixes #1684
This commit is contained in:
Robbe Sneyders
2023-04-21 12:11:38 +02:00
committed by GitHub
parent d9b699bf65
commit bcdd26ba0d
2 changed files with 22 additions and 101 deletions

View File

@@ -9,40 +9,23 @@ from os import path
import click import click
import importlib_metadata import importlib_metadata
from clickclick import AliasedGroup, fatal_error from clickclick import AliasedGroup
import connexion import connexion
from connexion.mock import MockResolver from connexion.mock import MockResolver
logger = logging.getLogger("connexion.cli") logger = logging.getLogger("connexion.cli")
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
FLASK_APP = "flask" FLASK_APP = "flask"
AVAILABLE_SERVERS = { ASYNC_APP = "async"
"flask": [FLASK_APP],
"gevent": [FLASK_APP],
"tornado": [FLASK_APP],
}
AVAILABLE_APPS = { AVAILABLE_APPS = {
FLASK_APP: "connexion.apps.flask_app.FlaskApp", FLASK_APP: "connexion.apps.flask.FlaskApp",
} ASYNC_APP: "connexion.apps.asynchronous.AsyncApp",
DEFAULT_SERVERS = {
FLASK_APP: FLASK_APP,
} }
# app is defined globally so it can be passed as an import_string to `app.run`, which is needed
def validate_server_requirements(ctx, param, value): # to enable reloading
if value == "gevent": app = None
try:
import gevent # NOQA
except ImportError:
fatal_error("gevent library is not installed")
elif value == "tornado":
try:
import tornado # NOQA
except ImportError:
fatal_error("tornado library is not installed")
else:
return value
def print_version(ctx, param, value): def print_version(ctx, param, value):
@@ -52,7 +35,7 @@ def print_version(ctx, param, value):
ctx.exit() ctx.exit()
@click.group(cls=AliasedGroup, context_settings=CONTEXT_SETTINGS) @click.group(cls=AliasedGroup, context_settings={"help_option_names": ["-h", "--help"]})
@click.option( @click.option(
"-V", "-V",
"--version", "--version",
@@ -70,20 +53,8 @@ def main():
@click.argument("spec_file") @click.argument("spec_file")
@click.argument("base_module_path", required=False) @click.argument("base_module_path", required=False)
@click.option("--port", "-p", default=5000, type=int, help="Port to listen.") @click.option("--port", "-p", default=5000, type=int, help="Port to listen.")
@click.option("--host", "-H", type=str, help="Host interface to bind on.")
@click.option( @click.option(
"--wsgi-server", "--host", "-H", default="127.0.0.1", type=str, help="Host interface to bind on."
"-w",
type=click.Choice(list(AVAILABLE_SERVERS)),
callback=validate_server_requirements,
help="Which WSGI server container to use. (deprecated, use --server instead)",
)
@click.option(
"--server",
"-s",
type=click.Choice(list(AVAILABLE_SERVERS)),
callback=validate_server_requirements,
help="Which server container to use.",
) )
@click.option( @click.option(
"--stub", "--stub",
@@ -147,7 +118,7 @@ def main():
@click.option( @click.option(
"--app-framework", "--app-framework",
"-f", "-f",
default=FLASK_APP, default=ASYNC_APP,
type=click.Choice(list(AVAILABLE_APPS)), type=click.Choice(list(AVAILABLE_APPS)),
help="The app framework used to run the server", help="The app framework used to run the server",
) )
@@ -156,8 +127,6 @@ def run(
base_module_path, base_module_path,
port, port,
host, host,
wsgi_server,
server,
stub, stub,
mock, mock,
hide_spec, hide_spec,
@@ -181,23 +150,6 @@ def run(
- BASE_MODULE_PATH (optional): filesystem path where the API endpoints handlers are going to be imported from. - BASE_MODULE_PATH (optional): filesystem path where the API endpoints handlers are going to be imported from.
""" """
if wsgi_server and server:
raise click.BadParameter(
"these options are mutually exclusive",
param_hint="'wsgi-server' and 'server'",
)
elif wsgi_server:
server = wsgi_server
if server is None:
server = DEFAULT_SERVERS[app_framework]
if app_framework not in AVAILABLE_SERVERS[server]:
message = "Invalid server '{}' for app-framework '{}'".format(
server, app_framework
)
raise click.UsageError(message)
logging_level = logging.WARN logging_level = logging.WARN
if verbose > 0: if verbose > 0:
logging_level = logging.INFO logging_level = logging.INFO
@@ -224,14 +176,17 @@ def run(
app_cls = connexion.utils.get_function_from_name(AVAILABLE_APPS[app_framework]) app_cls = connexion.utils.get_function_from_name(AVAILABLE_APPS[app_framework])
options = { swagger_ui_options = {
"serve_spec": not hide_spec, "serve_spec": not hide_spec,
"swagger_path": console_ui_from or None, "swagger_path": console_ui_from or None,
"swagger_ui": not hide_console_ui, "swagger_ui": not hide_console_ui,
"swagger_url": console_ui_url or None, "swagger_url": console_ui_url or None,
} }
app = app_cls(__name__, auth_all_paths=auth_all_paths, options=options) global app
app = app_cls(
__name__, auth_all_paths=auth_all_paths, swagger_ui_options=swagger_ui_options
)
app.add_api( app.add_api(
spec_file_full_path, spec_file_full_path,
@@ -242,7 +197,7 @@ def run(
**api_extra_args, **api_extra_args,
) )
app.run(port=port, host=host, server=server, debug=debug) app.run("connexion.cli:app", port=port, host=host, debug=debug)
if __name__ == "__main__": # pragma: no cover if __name__ == "__main__": # pragma: no cover

View File

@@ -35,7 +35,7 @@ def expected_arguments():
Default values arguments used to call `connexion.App` by cli. Default values arguments used to call `connexion.App` by cli.
""" """
return { return {
"options": { "swagger_ui_options": {
"serve_spec": True, "serve_spec": True,
"swagger_ui": True, "swagger_ui": True,
"swagger_path": None, "swagger_path": None,
@@ -90,7 +90,7 @@ def test_run_using_option_hide_spec(mock_app_run, expected_arguments, spec_file)
runner = CliRunner() runner = CliRunner()
runner.invoke(main, ["run", spec_file, "--hide-spec"], catch_exceptions=False) runner.invoke(main, ["run", spec_file, "--hide-spec"], catch_exceptions=False)
expected_arguments["options"]["serve_spec"] = False expected_arguments["swagger_ui_options"]["serve_spec"] = False
mock_app_run.assert_called_with("connexion.cli", **expected_arguments) mock_app_run.assert_called_with("connexion.cli", **expected_arguments)
@@ -98,7 +98,7 @@ def test_run_using_option_hide_console_ui(mock_app_run, expected_arguments, spec
runner = CliRunner() runner = CliRunner()
runner.invoke(main, ["run", spec_file, "--hide-console-ui"], catch_exceptions=False) runner.invoke(main, ["run", spec_file, "--hide-console-ui"], catch_exceptions=False)
expected_arguments["options"]["swagger_ui"] = False expected_arguments["swagger_ui_options"]["swagger_ui"] = False
mock_app_run.assert_called_with("connexion.cli", **expected_arguments) mock_app_run.assert_called_with("connexion.cli", **expected_arguments)
@@ -109,7 +109,7 @@ def test_run_using_option_console_ui_from(mock_app_run, expected_arguments, spec
main, ["run", spec_file, "--console-ui-from", user_path], catch_exceptions=False main, ["run", spec_file, "--console-ui-from", user_path], catch_exceptions=False
) )
expected_arguments["options"]["swagger_path"] = user_path expected_arguments["swagger_ui_options"]["swagger_path"] = user_path
mock_app_run.assert_called_with("connexion.cli", **expected_arguments) mock_app_run.assert_called_with("connexion.cli", **expected_arguments)
@@ -120,7 +120,7 @@ def test_run_using_option_console_ui_url(mock_app_run, expected_arguments, spec_
main, ["run", spec_file, "--console-ui-url", user_url], catch_exceptions=False main, ["run", spec_file, "--console-ui-url", user_url], catch_exceptions=False
) )
expected_arguments["options"]["swagger_url"] = user_url expected_arguments["swagger_ui_options"]["swagger_url"] = user_url
mock_app_run.assert_called_with("connexion.cli", **expected_arguments) mock_app_run.assert_called_with("connexion.cli", **expected_arguments)
@@ -202,37 +202,3 @@ def test_run_unimplemented_operations_and_mock(mock_app_run):
main, ["run", spec_file, "--mock=all"], catch_exceptions=False main, ["run", spec_file, "--mock=all"], catch_exceptions=False
) )
assert result.exit_code == 0 assert result.exit_code == 0
def test_run_with_wsgi_containers(mock_app_run, spec_file):
runner = CliRunner()
# missing gevent
result = runner.invoke(
main, ["run", spec_file, "-w", "gevent"], catch_exceptions=False
)
assert "gevent library is not installed" in result.output
assert result.exit_code == 1
# missing tornado
result = runner.invoke(
main, ["run", spec_file, "-w", "tornado"], catch_exceptions=False
)
assert "tornado library is not installed" in result.output
assert result.exit_code == 1
# using flask
result = runner.invoke(
main, ["run", spec_file, "-w", "flask"], catch_exceptions=False
)
assert result.exit_code == 0
def test_run_with_wsgi_server_and_server_opts(mock_app_run, spec_file):
runner = CliRunner()
result = runner.invoke(
main, ["run", spec_file, "-w", "flask", "-s", "flask"], catch_exceptions=False
)
assert "these options are mutually exclusive" in result.output
assert result.exit_code == 2