Skip to content

Commit

Permalink
Merge pull request #22 from stephenfin/add-openapi-version-3-support
Browse files Browse the repository at this point in the history
OpenAPI 3.0.0 support
  • Loading branch information
ikalnytskyi committed Dec 6, 2018
2 parents 778c766 + da1a174 commit f680e03
Show file tree
Hide file tree
Showing 12 changed files with 996 additions and 235 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ matrix:
env: TOXENV=py35
- python: 3.6
env: TOXENV=py36
- python: 3.7
env: TOXENV=py37
dist: xenial # https://github.com/travis-ci/travis-ci/issues/9069#issuecomment-425720905
sudo: true
- env: TOXENV=docs

install:
Expand Down
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ Links
* Bugs: https://github.com/ikalnytskyi/sphinxcontrib-openapi/issues


.. _Sphinx: https://sphinx.pocoo.org/
.. _OpenAPI: https://openapis.org/specification
.. _sphinxcontrib-httpdomain: https://pythonhosted.org/sphinxcontrib-httpdomain/
.. _Sphinx: https://www.sphinx-doc.org/en/master/
.. _OpenAPI: https://github.com/OAI/OpenAPI-Specification
.. _sphinxcontrib-httpdomain: https://sphinxcontrib-httpdomain.readthedocs.io/
8 changes: 4 additions & 4 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ encoding
source encoding will be used.

paths
A comma separated list of paths to filter the included openapi spec by.
A comma separated list of paths to filter the included OpenAPI spec by.
For example:

.. code:: restructuredtext
Expand All @@ -61,7 +61,7 @@ paths
ignoring all others.


.. _Sphinx: https://sphinx.pocoo.org
.. _OpenAPI: https://openapis.org/specification
.. _sphinxcontrib-httpdomain: https://pythonhosted.org/sphinxcontrib-httpdomain/
.. _Sphinx: https://www.sphinx-doc.org/en/master/
.. _OpenAPI: https://github.com/OAI/OpenAPI-Specification
.. _sphinxcontrib-httpdomain: https://sphinxcontrib-httpdomain.readthedocs.io/
.. _sphinxcontrib-redoc: https://sphinxcontrib-redoc.readthedocs.io/
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
],
namespace_packages=['sphinxcontrib'],
)
217 changes: 0 additions & 217 deletions sphinxcontrib/openapi.py

This file was deleted.

29 changes: 29 additions & 0 deletions sphinxcontrib/openapi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
sphinxcontrib.openapi
---------------------
The OpenAPI spec renderer for Sphinx. It's a new way to document your
RESTful API. Based on ``sphinxcontrib-httpdomain``.
:copyright: (c) 2016, Ihor Kalnytskyi.
:license: BSD, see LICENSE for details.
"""

from __future__ import unicode_literals

from pkg_resources import get_distribution, DistributionNotFound

from sphinxcontrib.openapi import directive

try:
__version__ = get_distribution(__name__).version
except DistributionNotFound:
# package is not installed
__version__ = None


def setup(app):
app.setup_extension('sphinxcontrib.httpdomain')
app.add_directive('openapi', directive.OpenApi)

return {'version': __version__, 'parallel_read_safe': True}
94 changes: 94 additions & 0 deletions sphinxcontrib/openapi/directive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""
sphinxcontrib.openapi.directive
-------------------------------
The main directive for the extension.
:copyright: (c) 2016, Ihor Kalnytskyi.
:license: BSD, see LICENSE for details.
"""

from __future__ import unicode_literals

import collections
import io

from docutils import nodes
from docutils.parsers.rst import Directive, directives
from docutils.statemachine import ViewList
import yaml

from sphinx.util.nodes import nested_parse_with_titles

from sphinxcontrib.openapi import openapi20
from sphinxcontrib.openapi import openapi30


# Dictionaries do not guarantee to preserve the keys order so when we load
# JSON or YAML - we may loose the order. In most cases it's not important
# because we're interested in data. However, in case of OpenAPI spec it'd
# be really nice to preserve them since, for example, endpoints may be
# grouped logically and that improved readability.
class _YamlOrderedLoader(yaml.SafeLoader):
pass


_YamlOrderedLoader.add_constructor(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
lambda loader, node: collections.OrderedDict(loader.construct_pairs(node))
)


class OpenApi(Directive):

required_arguments = 1 # path to openapi spec
final_argument_whitespace = True # path may contain whitespaces
option_spec = {
'encoding': directives.encoding, # useful for non-ascii cases :)
'paths': lambda s: s.split(), # endpoints to be rendered
}

def run(self):
env = self.state.document.settings.env
relpath, abspath = env.relfn2path(directives.path(self.arguments[0]))

# Add OpenAPI spec as a dependency to the current document. That means
# the document will be rebuilt if the spec is changed.
env.note_dependency(relpath)

# Read the spec using encoding passed to the directive or fallback to
# the one specified in Sphinx's config.
encoding = self.options.get('encoding', env.config.source_encoding)
with io.open(abspath, 'rt', encoding=encoding) as stream:
spec = yaml.load(stream, _YamlOrderedLoader)

# URI parameter is crucial for resolving relative references. So
# we need to set this option properly as it's used later down the
# stack.
self.options.setdefault('uri', 'file://%s' % abspath)

# We support both OpenAPI 2.0 (f.k.a. Swagger) and OpenAPI 3.0.0, so
# determine which version we are parsing here.
spec_version = spec.get('openapi', spec.get('swagger', '2.0'))
if spec_version.startswith('2.'):
openapihttpdomain = openapi20.openapihttpdomain
elif spec_version.startswith('3.'):
openapihttpdomain = openapi30.openapihttpdomain
else:
raise ValueError('Unsupported OpenAPI version (%s)' % spec_version)

# reStructuredText DOM manipulation is pretty tricky task. It requires
# passing dozen arguments which is not easy without well-documented
# internals. So the idea here is to represent OpenAPI spec as
# reStructuredText in-memory text and parse it in order to produce a
# real DOM.
viewlist = ViewList()
for line in openapihttpdomain(spec, **self.options):
viewlist.append(line, '<openapi>')

# Parse reStructuredText contained in `viewlist` and return produced
# DOM nodes.
node = nodes.section()
node.document = self.state.document
nested_parse_with_titles(self.state, viewlist, node)
return node.children

0 comments on commit f680e03

Please sign in to comment.