diff --git a/connexion/cli.py b/connexion/cli.py new file mode 100644 index 0000000..2ea723a --- /dev/null +++ b/connexion/cli.py @@ -0,0 +1,56 @@ +import logging +import sys +from os import path + +import click +from connexion import App + +from clickclick import AliasedGroup + +main = AliasedGroup(context_settings=dict(help_option_names=[ + '-h', '--help'])) + + +@main.command() +@click.argument('spec_file') +@click.argument('base_path', required=False) +@click.option('--port', '-p', default=5000, type=int, help='Port to listen.') +@click.option('--server', '-s', default='gevent', + type=click.Choice(['gevent', 'tornado']), + help='Which WSGI server to use.') +@click.option('--hide-spec', + help='Hides the API spec in JSON format which is by default available at `/swagger.json`.', + is_flag=True, default=True) +@click.option('--hide-swagger-ui', + help='Hides the the Swagger UI which is by default available at `/ui`.', + is_flag=True, default=True) +@click.option('--swagger-ui-url', metavar='URL', + help='Personalize what URL path the Swagger UI will be mounted.') +@click.option('--swagger-ui-from', metavar='PATH', + help='Path to a customized Swagger UI dashboard.') +@click.option('--auth-all-paths', + help='Enable authentication to paths not defined in the spec.', + is_flag=True, default=False) +@click.option('--debug', '-d', help='Show debugging information.', + is_flag=True, default=False) +def run(spec_file, base_path, port, server, debug): + """ + Runs a server using the passed OpenAPI/Swagger 2.0 Specification file. + + Possible arguments: + + - SPEC_FILE: specification file of the API to run. + + - BASE_PATH (optional): filesystem path from where to import the API handlers. + """ + logging_level = logging.ERROR + if debug: + logging_level = logging.DEBUG + logging.basicConfig(level=logging_level) + + sys.path.insert(1, path.abspath(base_path or '.')) + + app = App(__name__) + app.add_api(path.abspath(spec_file)) + click.echo('Running at http://localhost:{}/...'.format(port)) + app.run(port=port, server=server) diff --git a/connexion/utils.py b/connexion/utils.py index 3dc5f94..5573c5e 100644 --- a/connexion/utils.py +++ b/connexion/utils.py @@ -20,7 +20,6 @@ import string import flask import werkzeug.wrappers - PATH_PARAMETER = re.compile(r'\{([^}]*)\}') # map Swagger type to flask path converter diff --git a/requirements.txt b/requirements.txt index 0b6240e..b623297 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ requests>=2.9.1 six>=1.7 strict-rfc3339>=0.6 swagger_spec_validator>=2.0.2 +clickclick>=1.1 diff --git a/setup.py b/setup.py index d2d413a..d26ebc1 100755 --- a/setup.py +++ b/setup.py @@ -88,5 +88,6 @@ setup( 'Topic :: Software Development :: Libraries :: Application Frameworks' ], include_package_data=True, # needed to include swagger-ui (see MANIFEST.in) - + entry_points={'console_scripts': ['connexion = connexion.cli:main', + 'cnx = connexion.cli:main']} ) diff --git a/tests/api/test_parameters.py b/tests/api/test_parameters.py index b25c039..8e42117 100644 --- a/tests/api/test_parameters.py +++ b/tests/api/test_parameters.py @@ -287,5 +287,3 @@ def test_args_kwargs(simple_app): resp = app_client.get('/v1.0/query-params-as-kwargs?foo=a&bar=b') assert resp.status_code == 200 assert json.loads(resp.data.decode()) == {'foo': 'a'} - - diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..f6eccde --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,52 @@ +import logging + +from click.testing import CliRunner +from connexion import App, __version__ +from connexion.cli import main + +import pytest +from conftest import FIXTURES_FOLDER +from mock import MagicMock + + +@pytest.fixture() +def mock_app_run(monkeypatch): + test_server = MagicMock(wraps=App(__name__)) + test_server.run = MagicMock(return_value=True) + test_app = MagicMock(return_value=test_server) + monkeypatch.setattr('connexion.cli.App', test_app) + return test_server + + +def test_run_missing_spec(): + runner = CliRunner() + result = runner.invoke(main, ['run'], catch_exceptions=False) + assert "Missing argument" in result.output + + +def test_run_simple_spec(mock_app_run): + spec_file = str(FIXTURES_FOLDER / 'simple/swagger.yaml') + default_port = 5000 + default_server = 'gevent' + runner = CliRunner() + result = runner.invoke(main, + ['run', spec_file], + catch_exceptions=False) + + mock_app_run.run.assert_called_with(port=default_port, server=default_server) + assert 'Running at' in result.output + + +def test_run_in_debug_mode(mock_app_run, monkeypatch): + spec_file = str(FIXTURES_FOLDER / 'simple/swagger.yaml') + + logging_config = MagicMock(name='connexion.cli.logging.basicConfig') + monkeypatch.setattr('connexion.cli.logging.basicConfig', + logging_config) + + runner = CliRunner() + result = runner.invoke(main, + ['run', spec_file, '-d'], + catch_exceptions=False) + + logging_config.assert_called_with(level=logging.DEBUG)