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 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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user