Skip to content

Support remote OpenAPI specifications #118

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -35,6 +35,11 @@ and it will be rendered into something like:

.. openapi:: specs/openapi.yml

You can also use URLs where to fetch the OpenAPI spec from:

.. code:: restructuredtext

.. openapi:: https://petstore.swagger.io/v2/swagger.json

Options
=======
5 changes: 3 additions & 2 deletions sphinxcontrib/openapi/__main__.py
Original file line number Diff line number Diff line change
@@ -59,8 +59,9 @@ def main():
if options.group:
openapi_options['group'] = True

openapi_options.setdefault('uri', 'file://%s' % options.input)
spec = directive._get_spec(options.input, options.encoding)
uri = directive._get_spec_uri(options.input)
openapi_options.setdefault('uri', uri.geturl())
spec = directive._get_spec(uri, options.encoding)
renderer = renderers.HttpdomainOldRenderer(None, openapi_options)

for line in renderer.render_restructuredtext_markup(spec):
43 changes: 31 additions & 12 deletions sphinxcontrib/openapi/directive.py
Original file line number Diff line number Diff line change
@@ -12,15 +12,35 @@

from docutils.parsers.rst import directives
from sphinx.util.docutils import SphinxDirective
from urllib.parse import urlparse
import yaml
try:
import requests
_requests = requests
except ImportError:
_requests = None


# Locally cache spec to speedup processing of same spec file in multiple
# openapi directives
@functools.lru_cache()
def _get_spec(abspath, encoding):
with open(abspath, 'rt', encoding=encoding) as stream:
return yaml.safe_load(stream)
def _get_spec(uri, encoding):
if uri.scheme == 'http' or uri.scheme == 'https':
r = _requests.get(uri.geturl())
return yaml.safe_load(r.text.encode(encoding))
else:
with open(uri.path, 'rt', encoding=encoding) as stream:
return yaml.safe_load(stream)


def _get_spec_uri(arg):
try:
ret = urlparse(arg)
return ret
except Exception:
path = directives.path(arg)
ret = urlparse("file://{}".format(path))
return ret


def create_directive_from_renderer(renderer_cls):
@@ -37,22 +57,21 @@ class _RenderingDirective(SphinxDirective):
)

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

uri = _get_spec_uri(self.arguments[0])
# 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)

# Add a given OpenAPI spec as a dependency of the referring
# reStructuredText document, so the document is rebuilt each time
# the spec is changed.
self.env.note_dependency(relpath)
self.options.setdefault('uri', uri.geturl())
if uri.scheme == 'file':
# Add a given OpenAPI spec as a dependency of the referring
# reStructuredText document, so the document is rebuilt each time
# the spec is changed.
self.env.note_dependency(uri.path)

# Read the spec using encoding passed to the directive or fallback to
# the one specified in Sphinx's config.
encoding = self.options.get('encoding', self.config.source_encoding)
spec = _get_spec(abspath, encoding)
spec = _get_spec(uri, encoding)
return renderer_cls(self.state, self.options).render(spec)

return _RenderingDirective
36 changes: 29 additions & 7 deletions tests/test_openapi.py
Original file line number Diff line number Diff line change
@@ -1818,15 +1818,21 @@ def test_noproperties(self):
def test_openapi2_examples(tmpdir, run_sphinx):
spec = os.path.join(
os.path.abspath(os.path.dirname(__file__)),
'OpenAPI-Specification',
'examples',
'testspecs',
'v2.0',
'json',
'uber.json')
py.path.local(spec).copy(tmpdir.join('src', 'test-spec.yml'))

with pytest.raises(ValueError) as excinfo:
run_sphinx('test-spec.yml', options={'examples': True})
run_sphinx(tmpdir.join('src', 'test-spec.yml'), options={'examples': True})

assert str(excinfo.value) == (
'Rendering examples is not supported for OpenAPI v2.x specs.')


def test_openapi2_url(run_sphinx):
with pytest.raises(ValueError) as excinfo:
run_sphinx('https://petstore.swagger.io/v2/swagger.json', options={'examples': True})

assert str(excinfo.value) == (
'Rendering examples is not supported for OpenAPI v2.x specs.')
@@ -1836,12 +1842,28 @@ def test_openapi2_examples(tmpdir, run_sphinx):
def test_openapi3_examples(tmpdir, run_sphinx, render_examples):
spec = os.path.join(
os.path.abspath(os.path.dirname(__file__)),
'OpenAPI-Specification',
'examples',
'testspecs',
'v3.0',
'petstore.yaml')
py.path.local(spec).copy(tmpdir.join('src', 'test-spec.yml'))
run_sphinx('test-spec.yml', options={'examples': render_examples})
run_sphinx(tmpdir.join('src', 'test-spec.yml'), options={'examples': render_examples})

rendered_html = tmpdir.join('out', 'index.html').read_text('utf-8')

assert ('<strong>Example response:</strong>' in rendered_html) \
== render_examples


@pytest.mark.parametrize('render_examples', [False, True])
def test_openapi3_url(tmpdir, run_sphinx, render_examples):
os.path.join(
os.path.abspath(os.path.dirname(__file__)),
'testspecs',
'v3.0',
'petstore.yaml')
run_sphinx(
'https://petstore3.swagger.io/api/v3/openapi.json',
options={'examples': render_examples})

rendered_html = tmpdir.join('out', 'index.html').read_text('utf-8')