-
-
Notifications
You must be signed in to change notification settings - Fork 762
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Run an API spec, no boilerplate needed #284
Changes from 24 commits
c74d8cd
e9d5a61
9c5f6bc
82dcb61
e7f96e4
97adb25
8ef5794
d9b59c6
7ecb585
186f33b
dbe20e9
07c6eb7
2208a95
b024a90
fec010b
6076f96
9b7cb99
a2c074d
b3fc3ff
b57ebfa
46fa019
c1d55c1
d71674b
08b20e5
2d23ac9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from connexion.cli import main # pragma: no cover | ||
|
||
main() # pragma: no cover |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import logging | ||
import sys | ||
from os import path | ||
|
||
import click | ||
from clickclick import AliasedGroup, fatal_error | ||
from connexion import App | ||
|
||
logger = logging.getLogger('connexion.cli') | ||
|
||
main = AliasedGroup(context_settings=dict(help_option_names=[ | ||
'-h', '--help'])) | ||
|
||
|
||
def validate_wsgi_server_requirements(ctx, param, value): | ||
if value == 'gevent': | ||
try: | ||
import gevent # NOQA | ||
except: | ||
fatal_error('gevent library is not installed') | ||
elif value == 'tornado': | ||
try: | ||
import tornado # NOQA | ||
except: | ||
fatal_error('tornado library is not installed') | ||
|
||
|
||
@main.command() | ||
@click.argument('spec_file') | ||
@click.argument('base_module_path', required=False) | ||
@click.option('--port', '-p', default=5000, type=int, help='Port to listen.') | ||
@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('--stub', | ||
help='Returns status code 501, and `Not Implemented Yet` payload, for ' | ||
'the endpoints which handlers are not found.', | ||
is_flag=True, default=False) | ||
@click.option('--hide-spec', | ||
help='Hides the API spec in JSON format which is by default available at `/swagger.json`.', | ||
is_flag=True, default=False) | ||
@click.option('--hide-console-ui', | ||
help='Hides the the API console UI which is by default available at `/ui`.', | ||
is_flag=True, default=False) | ||
@click.option('--console-ui-url', metavar='URL', | ||
help='Personalize what URL path the API console UI will be mounted.') | ||
@click.option('--console-ui-from', metavar='PATH', | ||
help='Path to a customized API console UI dashboard.') | ||
@click.option('--auth-all-paths', | ||
help='Enable authentication to paths not defined in the spec.', | ||
is_flag=True, default=False) | ||
@click.option('--validate-responses', | ||
help='Enable validation of response values from operation handlers.', | ||
is_flag=True, default=False) | ||
@click.option('--strict-validation', | ||
help='Enable strict validation of request payloads.', | ||
is_flag=True, default=False) | ||
@click.option('--debug', '-d', help='Show debugging information.', | ||
is_flag=True, default=False) | ||
@click.option('--verbose', '-v', help='Show logging information.', | ||
is_flag=True, default=False) | ||
@click.option('--very-verbose', '-vv', help='Show debugging information, same as `--debug`.', | ||
is_flag=True, default=False) | ||
def run(spec_file, | ||
base_module_path, | ||
port, | ||
wsgi_server, | ||
stub, | ||
hide_spec, | ||
hide_console_ui, | ||
console_ui_url, | ||
console_ui_from, | ||
auth_all_paths, | ||
validate_responses, | ||
strict_validation, | ||
debug, | ||
verbose, | ||
very_verbose): | ||
""" | ||
Runs a server compliant with a OpenAPI/Swagger 2.0 Specification file. | ||
|
||
Arguments: | ||
|
||
- SPEC_FILE: specification file that describes the server endpoints. | ||
|
||
- BASE_MODULE_PATH (optional): filesystem path where the API endpoints handlers are going to be imported from. | ||
""" | ||
logging_level = logging.WARN | ||
if verbose: | ||
logging_level = logging.INFO | ||
|
||
if debug or very_verbose: | ||
logging_level = logging.DEBUG | ||
debug = True | ||
|
||
logging.basicConfig(level=logging_level) | ||
|
||
spec_file_full_path = path.abspath(spec_file) | ||
py_module_path = base_module_path or path.dirname(spec_file_full_path) | ||
sys.path.insert(1, path.abspath(py_module_path)) | ||
logger.debug('Added {} to system path.'.format(py_module_path)) | ||
|
||
resolver_error = None | ||
if stub: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a future improvement we could implement a resolver that mocks a response based on the specification. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is definitely interesting. There are some open source tools that do that, but bringing together the possibility to have partially mocked API's, that can morph in a fully implemented one, is a new approach. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I'm looking to do is use my resolving swagger parser prance to generate test cases for an API. That's coming from realizing that swagger-tester isn't quite what I'd like it to be. Some of that code might well be useful for generating the mocked response. One way or another it's going through the spec and generating stuff. Just a thought. But I can't guarantee I'll be able to spend time on that. |
||
resolver_error = 501 | ||
|
||
app = App(__name__, | ||
swagger_json=not hide_spec, | ||
swagger_ui=not hide_console_ui, | ||
swagger_path=console_ui_from or None, | ||
swagger_url=console_ui_url or None, | ||
auth_all_paths=auth_all_paths, | ||
debug=debug) | ||
|
||
app.add_api(spec_file_full_path, | ||
resolver_error=resolver_error, | ||
validate_responses=validate_responses, | ||
strict_validation=strict_validation) | ||
|
||
app.run(port=port, | ||
server=wsgi_server, | ||
debug=debug) | ||
|
||
|
||
if __name__ == '__main__': # pragma: no cover | ||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
Command-Line Interface | ||
====================== | ||
For convenience Connexion provides a command-line interface | ||
(CLI). This interface aims to be a starting point in developing or | ||
testing OpenAPI specifications with Connexion. | ||
|
||
The available commands are: | ||
|
||
- ``connexion run`` | ||
|
||
All commands can run with -h or --help to list more information. | ||
|
||
Running an OpenAPI specification | ||
-------------------------------- | ||
|
||
The subcommand ``run`` of Connexion's CLI makes it easy to run OpenAPI | ||
specifications directly even before any operation handler function gets | ||
implemented. This allows you to verify and inspect how your API will | ||
work with Connexion. | ||
|
||
To run your specification, execute in your shell: | ||
|
||
.. code-block:: bash | ||
|
||
$ connexion run your_api.yaml --stub --debug | ||
|
||
This command will tell Connexion to run the ``your_api.yaml`` | ||
specification file attaching a stub operation (``--stub``) to the | ||
unavailable operations/functions of your API and in debug mode | ||
(``--debug``). | ||
|
||
The basic usage of this command is: | ||
|
||
.. code-block:: bash | ||
|
||
$ connexion run [OPTIONS] SPEC_FILE [BASE_MODULE_PATH] | ||
|
||
Where: | ||
|
||
- SPEC_FILE: Your OpenAPI specification file in YAML format. | ||
- BASE_MODULE_PATH (optional): filesystem path where the API endpoints | ||
handlers are going to be imported from. In short, where your Python | ||
code is saved. | ||
|
||
There are more options available for the ``run`` command, for a full | ||
list run: | ||
|
||
.. code-block:: bash | ||
|
||
$ connexion run --help |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ Contents: | |
:maxdepth: 2 | ||
|
||
quickstart | ||
cli | ||
routing | ||
request | ||
response | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
|
||
import connexion | ||
from connexion import NoContent | ||
|
||
import orm | ||
|
||
db_session = None | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,4 @@ requests>=2.9.1 | |
six>=1.7 | ||
strict-rfc3339>=0.6 | ||
swagger_spec_validator>=2.0.2 | ||
clickclick>=1.2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
swagger: "2.0" | ||
|
||
info: | ||
title: "Testing API" | ||
version: "1.0" | ||
|
||
basePath: "/testing" | ||
|
||
paths: | ||
/operation-not-implemented: | ||
get: | ||
summary: Operation function does not exist. | ||
operationId: api.this_function_does_not_exist | ||
responses: | ||
200: | ||
description: OK |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
swagger: "2.0" | ||
|
||
info: | ||
title: "Not Exist API" | ||
version: "1.0" | ||
|
||
basePath: '/na' | ||
|
||
paths: | ||
/module-not-implemented: | ||
get: | ||
summary: Operation function does not exist. | ||
operationId: m.module_does_not_exist | ||
responses: | ||
200: | ||
description: OK |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nicer way of doing that: http://click.pocoo.org/5/options/#counting