Skip to content

Commit

Permalink
Merge pull request #977 from gabisurita/631-swagger
Browse files Browse the repository at this point in the history
Add OpenAPI/Swagger spec (fixes #631)
  • Loading branch information
glasserc committed Dec 22, 2016
2 parents 5a6f4d3 + 250e78d commit 8f18dc1
Show file tree
Hide file tree
Showing 24 changed files with 3,585 additions and 6 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.rst
Expand Up @@ -3,11 +3,15 @@ Changelog

This document describes changes between each past release.

5.2.0 (unreleased)
5.2.0 (Unreleased)
------------------

- Nothing changed yet.
**Protocol**

- Add an `OpenAPI specification <https://kinto.readthedocs.io/en/latest/api/1.x/openapi.html>`
for the HTTP API on ``/swagger.json`` (#997)

Protocol is now at version **1.14**. See `API changelog`_.

5.1.0 (2016-12-19)
------------------
Expand Down
2 changes: 1 addition & 1 deletion MANIFEST.in
@@ -1,7 +1,7 @@
include *.rst LICENSE Makefile tox.ini .coveragerc app.wsgi
include kinto/config/kinto.tpl
include dev-requirements.txt
recursive-include kinto *.sql *.html *.ini
recursive-include kinto *.sql *.html *.ini *.yaml
recursive-include docs *.rst *.png *.svg *.css *.html conf.py piwik.js
recursive-include kinto/plugins/admin/build *
prune docs/_build/*
Expand Down
1 change: 1 addition & 0 deletions dev-requirements.txt
@@ -1,3 +1,4 @@
bravado_core
flake8
mock
pytest
Expand Down
4 changes: 4 additions & 0 deletions docs/api/1.x/index.rst
Expand Up @@ -25,6 +25,7 @@ Full detailed API documentation:
quotas
utilities
batch
openapi
timestamps
backoff
errors
Expand All @@ -45,6 +46,9 @@ Cheatsheet
| `GET` | :ref:`/__heartbeat__ <api-utilities>` | :ref:`Return the status of dependent services |
| | | <api-utilities>` |
+----------+----------------------------------------------------------------------------------------------+---------------------------------------------------------+
| `GET` | :ref:`/swagger.json <openapi_spec>` | :ref:`Return the OpenAPI description of the running |
| | | instance <openapi_spec>` |
+----------+----------------------------------------------------------------------------------------------+---------------------------------------------------------+
| **Buckets** |
+----------+----------------------------------------------------------------------------------------------+---------------------------------------------------------+
| `POST` | :ref:`/buckets <buckets-post>` | :ref:`Create a bucket <buckets-post>` |
Expand Down
129 changes: 129 additions & 0 deletions docs/api/1.x/openapi.rst
@@ -0,0 +1,129 @@
.. _openapi_spec:

OpenAPI Specification
#####################

The `OpenAPI Specification <https://github.com/OAI/OpenAPI-Specification>`_
(formally known as Swagger specification)
is a standard to describe REST APIs in a human and computer readable format
and allow several features like interactive documentation and automated
client generation.

GET /__api__
============

Returns the OpenAPI description for the running instance of Kinto on JSON format.

.. important::

Getting the description from this endpoint is the only way to get the full
description for the running instance. The description returned is based on a
``swagger.yaml`` valid OpenAPI description file on the root of the Kinto
python package, but which lacks some instance specific definitions that
are updated on runtime.


Known limitations
=================

OpenAPI 2.0 currently doesn't support some features that are present on Kinto API.
Some known limitations are:

#. Lack of validation on **OR** clauses (e.g. provide `data` or `permissions`
in PATCH operations), which are trated as non-required on the description.
This is more critical when setting
`Batch operations <http://kinto.readthedocs.io/en/stable/api/1.x/batch.html>`_,
which all the fields should be either defined on each request on the
``requests`` array or on the ``default`` object.

#. No support for :ref:`query filters <filtering>` on properties. Only named
parameters are accepted on OpenAPI 2.0, so these are not included on the
specification.

#. Optional response headers are not supported, as required for
`Backoff signs <http://kinto.readthedocs.io/en/stable/api/1.x/backoff.html>`_.
On the specification, the Backoff field is only listed as a response header
on `default` responses, that should be raised with HTTP 5xx errors.

#. :ref:`Collection defined schemas <collection-json-schema>`
fields are not validated because they accept any JSON Schema definition,
which may be too complex to be handled by an OpenAPI description.
Only the type of the field is checked as a valid JSON object.

.. important::

The specifications used for loose schemas
(objects that accept extra attributes of any type) include an
``additionalAttributes: {}`` definition that is not documented on the
OpenAPI 2.0 Specification, but that allow loose schemas to be compatible
with with several services created for OpenAPI/Swagger documented APIs,
including code generators and interactive documentation renderers.


Interactive documentation
=========================

One way to interact and explore an API is through interactive documentation.
SwaggerUI is a resource that allows testing and visualizing the behavior
of an OpenAPI described interface.

You can try the instance running on https://kinto.dev.mozaws.net/v1/ from you browser
accessing `our example on SwaggerHub <https://app.swaggerhub.com/api/Kinto/kinto>`_

Automated client generation
===========================

We support clients from a few languages like JavaScript, Python and Java,
but if you need a client in a language that is currently not supported or
want to have your own personalized interface, you can speedup your development by using
`Swagger Code Generator <https://github.com/swagger-api/swagger-codegen>`_,
which generates standardized clients for APIs in more than 25 languages.

Using a custom specification
============================

The Kinto OpenAPI description relies on a ``kinto/swagger.yaml`` file.
You may replace the OpenAPI documentation for your deployment by providing a
``kinto.swagger_file`` entry on your configuration file.

OpenAPI extensions
==================

By default all includes on the configuration file will be prompt for a
``swagger.yaml`` file on the package root containing an OpenAPI extension
for the main specification. You may use it to include additional resources
that the plugin provides or override the original definitions. The ``.yaml``
files provided are recursively merged in the order they are included
(the default description is always used first).

For example, if we want to include a plugin to our spec, we can use a description
as follows. The extensions are not required to be valid OpenAPI descriptions,
but they can be defined as such. You may also use references that are defined
on the base description.

.. code-block:: yaml
swagger: 2.0
paths:
'/path/to/my/plugin':
responses:
'200':
description: My plugin default response.
schema:
$ref: '#/definitions/MyPluginSchema'
default:
description: Default Kinto error.
schema:
$ref: '#/definitions/Error'
definitions:
MyPluginSchema:
type: object
.. important::

Extensions that change or include authentication methods may only overwrite
the ``securityDefinitions`` field. The default security field (used when
accessing API endpoints that require authentication) is updated on runtime
to match the security definitions.
5 changes: 5 additions & 0 deletions docs/api/index.rst
Expand Up @@ -11,6 +11,11 @@ API
Changelog
---------

1.14 (Unreleased)
'''''''''''''''''

- Add an OpenAPI 2.0 specification on ``GET /swagger.json`` endpoint.

1.13 (2016-12-19)
'''''''''''''''''

Expand Down
2 changes: 1 addition & 1 deletion kinto/__init__.py
Expand Up @@ -13,7 +13,7 @@
__version__ = pkg_resources.get_distribution(__package__).version

# Implemented HTTP API Version
HTTP_API_VERSION = '1.13'
HTTP_API_VERSION = '1.14'

# Main kinto logger
logger = logging.getLogger(__name__)
Expand Down
3 changes: 2 additions & 1 deletion kinto/core/__init__.py
Expand Up @@ -89,7 +89,8 @@
'multiauth.policy.basicauth.use': ('kinto.core.authentication.'
'BasicAuthAuthenticationPolicy'),
'multiauth.authorization_policy': ('kinto.core.authorization.'
'AuthorizationPolicy')
'AuthorizationPolicy'),
'swagger_file': 'swagger.yaml',
}


Expand Down
94 changes: 94 additions & 0 deletions kinto/core/views/swagger.py
@@ -0,0 +1,94 @@
import os
import pkg_resources

from ruamel import yaml
from pyramid import httpexceptions
from pyramid.settings import aslist
from pyramid.security import NO_PERMISSION_REQUIRED
from kinto.core import Service
from kinto.core.utils import recursive_update_dict

HERE = os.path.dirname(os.path.abspath(__file__))
ORIGIN = os.path.dirname(os.path.dirname(HERE))

swagger = Service(name="swagger", path='/__api__', description="OpenAPI description")


@swagger.get(permission=NO_PERMISSION_REQUIRED)
def swagger_view(request):

# Only build json once
try:
return swagger_view.__json__
except AttributeError:
pass

settings = request.registry.settings

# Base swagger spec
files = [
settings.get('swagger_file', ''), # From config
os.path.join(ORIGIN, 'swagger.yaml'), # Relative to the package root
os.path.join(HERE, 'swagger.yaml') # Relative to this file.
]

files = [f for f in files if os.path.exists(f)]

# Get first file that exists
if files:
files = files[:1]
else:
raise httpexceptions.HTTPNotFound()

# Plugin swagger extensions
includes = aslist(settings.get('includes', ''))
for app in includes:
f = pkg_resources.resource_filename(app, 'swagger.yaml')
if os.path.exists(f):
files.append(f)

swagger_view.__json__ = {}

# Read and merge files
for path in files:
abs_path = os.path.abspath(path)
with open(abs_path) as f:
spec = yaml.load(f)
recursive_update_dict(swagger_view.__json__, spec)

# Update instance fields
info = dict(
title=settings['project_name'],
version=settings['http_api_version'])

schemes = [settings.get('http_scheme') or 'http']

security_defs = swagger_view.__json__.get('securityDefinitions', {})

# BasicAuth is a non extension capability, so we should check it from config
if 'basicauth' in aslist(settings.get('multiauth.policies', '')):
basicauth = {'type': 'basic',
'description': 'HTTP Basic Authentication.'}
security_defs['basicAuth'] = basicauth

# Security options are JSON objects with a single key
security = swagger_view.__json__.get('security', [])
security_names = [next(iter(security_def)) for security_def in security]

# include securityDefinitions that are not on default security options
for name, prop in security_defs.items():
security_def = {name: prop.get('scopes', {}).keys()}
if name not in security_names:
security.append(security_def)

data = dict(
info=info,
host=request.host,
basePath=request.path.replace(swagger.path, ''),
schemes=schemes,
securityDefinitions=security_defs,
security=security)

recursive_update_dict(swagger_view.__json__, data)

return swagger_view.__json__

0 comments on commit 8f18dc1

Please sign in to comment.