mirror of
https://github.com/LukeHagar/connexion.git
synced 2025-12-10 04:19:37 +00:00
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:
@@ -1,6 +1,7 @@
|
||||
import importlib
|
||||
import logging
|
||||
import sys
|
||||
from itertools import chain
|
||||
from os import path
|
||||
|
||||
import click
|
||||
@@ -11,9 +12,25 @@ from connexion.mock import MockResolver
|
||||
|
||||
logger = logging.getLogger('connexion.cli')
|
||||
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':
|
||||
try:
|
||||
import gevent # NOQA
|
||||
@@ -24,6 +41,8 @@ def validate_wsgi_server_requirements(ctx, param, value):
|
||||
import tornado # NOQA
|
||||
except ImportError:
|
||||
fatal_error('tornado library is not installed')
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def print_version(ctx, param, value):
|
||||
@@ -45,10 +64,14 @@ def main():
|
||||
@click.argument('base_module_path', required=False)
|
||||
@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('--wsgi-server', '-w', default='flask',
|
||||
type=click.Choice(['flask', 'gevent', 'tornado']),
|
||||
callback=validate_wsgi_server_requirements,
|
||||
help='Which WSGI server container to use.')
|
||||
@click.option('--wsgi-server', '-w',
|
||||
type=click.Choice(AVAILABLE_SERVERS.keys()),
|
||||
callback=validate_server_requirements,
|
||||
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',
|
||||
help='Returns status code 501, and `Not Implemented Yet` payload, for '
|
||||
'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('--base-path', metavar='PATH',
|
||||
help='Override the basePath in the API spec.')
|
||||
@click.option('--app-cls', metavar='APP_CLS',
|
||||
help='Override the app class (the default is connexion.apis.flask_app.FlaskApp)')
|
||||
@click.option('--app-framework', '-f', default=FLASK_APP,
|
||||
type=click.Choice(AVAILABLE_APPS.keys()),
|
||||
help='The app framework used to run the server')
|
||||
def run(spec_file,
|
||||
base_module_path,
|
||||
port,
|
||||
host,
|
||||
wsgi_server,
|
||||
server,
|
||||
stub,
|
||||
mock,
|
||||
hide_spec,
|
||||
@@ -98,7 +123,7 @@ def run(spec_file,
|
||||
debug,
|
||||
verbose,
|
||||
base_path,
|
||||
app_cls=None):
|
||||
app_framework):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
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
|
||||
if verbose > 0:
|
||||
logging_level = logging.INFO
|
||||
@@ -132,14 +174,9 @@ def run(spec_file,
|
||||
resolver = MockResolver(mock_all=mock == 'all')
|
||||
api_extra_args['resolver'] = resolver
|
||||
|
||||
if app_cls is None:
|
||||
app_cls = connexion.FlaskApp
|
||||
else:
|
||||
module = app_cls.split('.')
|
||||
app_cls = module.pop()
|
||||
module = importlib.import_module('.'.join(module))
|
||||
app_cls = getattr(module, app_cls)
|
||||
|
||||
app_cls = connexion.utils.get_function_from_name(
|
||||
AVAILABLE_APPS[app_framework]
|
||||
)
|
||||
app = app_cls(__name__,
|
||||
swagger_json=not hide_spec,
|
||||
swagger_ui=not hide_console_ui,
|
||||
@@ -157,7 +194,7 @@ def run(spec_file,
|
||||
|
||||
app.run(port=port,
|
||||
host=host,
|
||||
server=wsgi_server,
|
||||
server=server,
|
||||
debug=debug)
|
||||
|
||||
|
||||
|
||||
@@ -12,19 +12,22 @@ from mock import MagicMock
|
||||
|
||||
|
||||
@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.run = MagicMock(return_value=True)
|
||||
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
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_importlib(monkeypatch):
|
||||
importlib = MagicMock()
|
||||
monkeypatch.setattr('connexion.cli.importlib', importlib)
|
||||
return importlib
|
||||
def mock_get_function_from_name(monkeypatch):
|
||||
get_function_from_name = MagicMock()
|
||||
monkeypatch.setattr(
|
||||
'connexion.cli.connexion.utils.get_function_from_name',
|
||||
get_function_from_name
|
||||
)
|
||||
return get_function_from_name
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@@ -68,7 +71,7 @@ def test_run_simple_spec(mock_app_run, spec_file):
|
||||
app_instance.run.assert_called_with(
|
||||
port=default_port,
|
||||
host=None,
|
||||
server=None,
|
||||
server='flask',
|
||||
debug=False)
|
||||
|
||||
|
||||
@@ -81,7 +84,7 @@ def test_run_spec_with_host(mock_app_run, spec_file):
|
||||
app_instance.run.assert_called_with(
|
||||
port=default_port,
|
||||
host='custom.host',
|
||||
server=None,
|
||||
server='flask',
|
||||
debug=False)
|
||||
|
||||
|
||||
@@ -254,9 +257,25 @@ def test_run_with_wsgi_containers(mock_app_run, spec_file):
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_run_using_other_app_class(mock_importlib, spec_file):
|
||||
default_port = 5000
|
||||
def test_run_with_wsgi_server_and_server_opts(mock_app_run, spec_file):
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user