Changed the cli script:

- created a new option called 'server'
 - did wsgi-server option deprecated
 - changed the app-cls option to app-framework
 - fixed the possible frameworks that will run the app
 - added a validation on framework/server selection
This commit is contained in:
Diogo Dutra
2017-11-09 16:21:51 -02:00
committed by Diogo
parent 8328507f88
commit 09daab63ca
2 changed files with 85 additions and 29 deletions

View File

@@ -1,6 +1,7 @@
import importlib import importlib
import logging import logging
import sys import sys
from itertools import chain
from os import path from os import path
import click import click
@@ -11,9 +12,25 @@ from connexion.mock import MockResolver
logger = logging.getLogger('connexion.cli') logger = logging.getLogger('connexion.cli')
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
FLASK_APP = 'flask'
AIOHTTP_APP = 'aiohttp'
AVAILABLE_SERVERS = {
'flask': [FLASK_APP],
'gevent': [FLASK_APP],
'tornado': [FLASK_APP],
'aiohttp': [AIOHTTP_APP]
}
AVAILABLE_APPS = {
FLASK_APP: 'connexion.apps.flask_app.FlaskApp',
AIOHTTP_APP: 'connexion.apps.aiohttp_app.AioHttpApp'
}
DEFAULT_SERVERS = {
FLASK_APP: FLASK_APP,
AIOHTTP_APP: AIOHTTP_APP
}
def validate_wsgi_server_requirements(ctx, param, value): def validate_server_requirements(ctx, param, value):
if value == 'gevent': if value == 'gevent':
try: try:
import gevent # NOQA import gevent # NOQA
@@ -24,6 +41,8 @@ def validate_wsgi_server_requirements(ctx, param, value):
import tornado # NOQA import tornado # NOQA
except ImportError: except ImportError:
fatal_error('tornado library is not installed') fatal_error('tornado library is not installed')
else:
return value
def print_version(ctx, param, value): def print_version(ctx, param, value):
@@ -45,10 +64,14 @@ def main():
@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('--host', '-H', type=str, help='Host interface to bind on.')
@click.option('--wsgi-server', '-w', default='flask', @click.option('--wsgi-server', '-w',
type=click.Choice(['flask', 'gevent', 'tornado']), type=click.Choice(AVAILABLE_SERVERS.keys()),
callback=validate_wsgi_server_requirements, callback=validate_server_requirements,
help='Which WSGI server container to use.') help='Which WSGI server container to use. (deprecated, use --server instead)')
@click.option('--server', '-s',
type=click.Choice(AVAILABLE_SERVERS.keys()),
callback=validate_server_requirements,
help='Which server container to use.')
@click.option('--stub', @click.option('--stub',
help='Returns status code 501, and `Not Implemented Yet` payload, for ' help='Returns status code 501, and `Not Implemented Yet` payload, for '
'the endpoints which handlers are not found.', 'the endpoints which handlers are not found.',
@@ -79,13 +102,15 @@ def main():
@click.option('--verbose', '-v', help='Show verbose information.', count=True) @click.option('--verbose', '-v', help='Show verbose information.', count=True)
@click.option('--base-path', metavar='PATH', @click.option('--base-path', metavar='PATH',
help='Override the basePath in the API spec.') help='Override the basePath in the API spec.')
@click.option('--app-cls', metavar='APP_CLS', @click.option('--app-framework', '-f', default=FLASK_APP,
help='Override the app class (the default is connexion.apis.flask_app.FlaskApp)') type=click.Choice(AVAILABLE_APPS.keys()),
help='The app framework used to run the server')
def run(spec_file, def run(spec_file,
base_module_path, base_module_path,
port, port,
host, host,
wsgi_server, wsgi_server,
server,
stub, stub,
mock, mock,
hide_spec, hide_spec,
@@ -98,7 +123,7 @@ def run(spec_file,
debug, debug,
verbose, verbose,
base_path, base_path,
app_cls=None): app_framework):
""" """
Runs a server compliant with a OpenAPI/Swagger 2.0 Specification file. Runs a server compliant with a OpenAPI/Swagger 2.0 Specification file.
@@ -108,6 +133,23 @@ def run(spec_file,
- 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 excludent",
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
@@ -132,14 +174,9 @@ def run(spec_file,
resolver = MockResolver(mock_all=mock == 'all') resolver = MockResolver(mock_all=mock == 'all')
api_extra_args['resolver'] = resolver api_extra_args['resolver'] = resolver
if app_cls is None: app_cls = connexion.utils.get_function_from_name(
app_cls = connexion.FlaskApp AVAILABLE_APPS[app_framework]
else: )
module = app_cls.split('.')
app_cls = module.pop()
module = importlib.import_module('.'.join(module))
app_cls = getattr(module, app_cls)
app = app_cls(__name__, app = app_cls(__name__,
swagger_json=not hide_spec, swagger_json=not hide_spec,
swagger_ui=not hide_console_ui, swagger_ui=not hide_console_ui,
@@ -157,7 +194,7 @@ def run(spec_file,
app.run(port=port, app.run(port=port,
host=host, host=host,
server=wsgi_server, server=server,
debug=debug) debug=debug)

View File

@@ -12,19 +12,22 @@ from mock import MagicMock
@pytest.fixture() @pytest.fixture()
def mock_app_run(monkeypatch): def mock_app_run(mock_get_function_from_name):
test_server = MagicMock(wraps=connexion.FlaskApp(__name__)) test_server = MagicMock(wraps=connexion.FlaskApp(__name__))
test_server.run = MagicMock(return_value=True) test_server.run = MagicMock(return_value=True)
test_app = MagicMock(return_value=test_server) test_app = MagicMock(return_value=test_server)
monkeypatch.setattr('connexion.cli.connexion.FlaskApp', test_app) mock_get_function_from_name.return_value = test_app
return test_app return test_app
@pytest.fixture() @pytest.fixture()
def mock_importlib(monkeypatch): def mock_get_function_from_name(monkeypatch):
importlib = MagicMock() get_function_from_name = MagicMock()
monkeypatch.setattr('connexion.cli.importlib', importlib) monkeypatch.setattr(
return importlib 'connexion.cli.connexion.utils.get_function_from_name',
get_function_from_name
)
return get_function_from_name
@pytest.fixture() @pytest.fixture()
@@ -68,7 +71,7 @@ def test_run_simple_spec(mock_app_run, spec_file):
app_instance.run.assert_called_with( app_instance.run.assert_called_with(
port=default_port, port=default_port,
host=None, host=None,
server=None, server='flask',
debug=False) debug=False)
@@ -81,7 +84,7 @@ def test_run_spec_with_host(mock_app_run, spec_file):
app_instance.run.assert_called_with( app_instance.run.assert_called_with(
port=default_port, port=default_port,
host='custom.host', host='custom.host',
server=None, server='flask',
debug=False) debug=False)
@@ -254,9 +257,25 @@ def test_run_with_wsgi_containers(mock_app_run, spec_file):
assert result.exit_code == 0 assert result.exit_code == 0
def test_run_using_other_app_class(mock_importlib, spec_file): def test_run_with_wsgi_server_and_server_opts(mock_app_run, spec_file):
default_port = 5000
runner = CliRunner() runner = CliRunner()
runner.invoke(main, ['run', spec_file, '--app-cls=test.test'], catch_exceptions=False)
assert mock_importlib.import_module.call_args_list == [mock_call('test')] result = runner.invoke(main,
['run', spec_file,
'-w', 'flask',
'-s', 'flask'],
catch_exceptions=False)
assert "these options are mutually excludent" in result.output
assert result.exit_code == 2
def test_run_with_incompatible_server_and_framework(mock_app_run, spec_file):
runner = CliRunner()
result = runner.invoke(main,
['run', spec_file,
'-s', 'flask',
'-f', 'aiohttp'],
catch_exceptions=False)
assert "Invalid server 'flask' for app-framework 'aiohttp'" in result.output
assert result.exit_code == 2