Skip to content

Commit d526545

Browse files
drodilstephenfin
authored andcommitted
Support remote OpenAPI specifications
This adds support to fetch OpenAPI specifications over the internet to be rendered by the sphinxcontrib-openapi.
1 parent 28de02a commit d526545

File tree

3 files changed

+57
-22
lines changed

3 files changed

+57
-22
lines changed

sphinxcontrib/openapi/__main__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ def main():
5959
if options.group:
6060
openapi_options['group'] = True
6161

62-
openapi_options.setdefault('uri', 'file://%s' % options.input)
63-
spec = directive._get_spec(options.input, options.encoding)
62+
uri = directive._get_spec_uri(options.input)
63+
openapi_options.setdefault('uri', uri.geturl())
64+
spec = directive._get_spec(uri, options.encoding)
6465
renderer = renderers.HttpdomainOldRenderer(None, openapi_options)
6566

6667
for line in renderer.render_restructuredtext_markup(spec):

sphinxcontrib/openapi/directive.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,33 @@
1212

1313
from docutils.parsers.rst import directives
1414
from sphinx.util.docutils import SphinxDirective
15+
from urllib.parse import urlparse
1516
import yaml
16-
17+
try:
18+
import requests
19+
_requests = requests
20+
except ImportError:
21+
_requests = None
1722

1823
# Locally cache spec to speedup processing of same spec file in multiple
1924
# openapi directives
2025
@functools.lru_cache()
21-
def _get_spec(abspath, encoding):
22-
with open(abspath, 'rt', encoding=encoding) as stream:
23-
return yaml.safe_load(stream)
26+
def _get_spec(uri, encoding):
27+
if uri.scheme == 'http' or uri.scheme == 'https':
28+
r = _requests.get(uri.geturl())
29+
return yaml.safe_load(r.text.encode(encoding))
30+
else:
31+
with open(uri.path, 'rt', encoding=encoding) as stream:
32+
return yaml.safe_load(stream)
2433

34+
def _get_spec_uri(arg):
35+
try:
36+
ret = urlparse(arg)
37+
return ret
38+
except:
39+
path = directives.path(arg)
40+
ret = urlparse("file://{}".format(path))
41+
return ret
2542

2643
def create_directive_from_renderer(renderer_cls):
2744
"""Create rendering directive from a renderer class."""
@@ -37,22 +54,21 @@ class _RenderingDirective(SphinxDirective):
3754
)
3855

3956
def run(self):
40-
relpath, abspath = self.env.relfn2path(directives.path(self.arguments[0]))
41-
57+
uri = _get_spec_uri(self.arguments[0])
4258
# URI parameter is crucial for resolving relative references. So we
4359
# need to set this option properly as it's used later down the
4460
# stack.
45-
self.options.setdefault('uri', 'file://%s' % abspath)
46-
47-
# Add a given OpenAPI spec as a dependency of the referring
48-
# reStructuredText document, so the document is rebuilt each time
49-
# the spec is changed.
50-
self.env.note_dependency(relpath)
61+
self.options.setdefault('uri', uri.geturl())
62+
if uri.scheme == 'file':
63+
# Add a given OpenAPI spec as a dependency of the referring
64+
# reStructuredText document, so the document is rebuilt each time
65+
# the spec is changed.
66+
self.env.note_dependency(uri.path)
5167

5268
# Read the spec using encoding passed to the directive or fallback to
5369
# the one specified in Sphinx's config.
5470
encoding = self.options.get('encoding', self.config.source_encoding)
55-
spec = _get_spec(abspath, encoding)
71+
spec = _get_spec(uri, encoding)
5672
return renderer_cls(self.state, self.options).render(spec)
5773

5874
return _RenderingDirective

tests/test_openapi.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,30 +1818,48 @@ def test_noproperties(self):
18181818
def test_openapi2_examples(tmpdir, run_sphinx):
18191819
spec = os.path.join(
18201820
os.path.abspath(os.path.dirname(__file__)),
1821-
'OpenAPI-Specification',
1822-
'examples',
1821+
'testspecs',
18231822
'v2.0',
1824-
'json',
18251823
'uber.json')
18261824
py.path.local(spec).copy(tmpdir.join('src', 'test-spec.yml'))
18271825

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

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

18341832

1833+
def test_openapi2_url(run_sphinx):
1834+
with pytest.raises(ValueError) as excinfo:
1835+
run_sphinx('https://petstore.swagger.io/v2/swagger.json', options={'examples': True})
1836+
1837+
assert str(excinfo.value) == (
1838+
'Rendering examples is not supported for OpenAPI v2.x specs.')
1839+
18351840
@pytest.mark.parametrize('render_examples', [False, True])
18361841
def test_openapi3_examples(tmpdir, run_sphinx, render_examples):
18371842
spec = os.path.join(
18381843
os.path.abspath(os.path.dirname(__file__)),
1839-
'OpenAPI-Specification',
1840-
'examples',
1844+
'testspecs',
18411845
'v3.0',
18421846
'petstore.yaml')
18431847
py.path.local(spec).copy(tmpdir.join('src', 'test-spec.yml'))
1844-
run_sphinx('test-spec.yml', options={'examples': render_examples})
1848+
run_sphinx(tmpdir.join('src', 'test-spec.yml'), options={'examples': render_examples})
1849+
1850+
rendered_html = tmpdir.join('out', 'index.html').read_text('utf-8')
1851+
1852+
assert ('<strong>Example response:</strong>' in rendered_html) \
1853+
== render_examples
1854+
1855+
@pytest.mark.parametrize('render_examples', [False, True])
1856+
def test_openapi3_url(tmpdir, run_sphinx, render_examples):
1857+
spec = os.path.join(
1858+
os.path.abspath(os.path.dirname(__file__)),
1859+
'testspecs',
1860+
'v3.0',
1861+
'petstore.yaml')
1862+
run_sphinx('https://petstore3.swagger.io/api/v3/openapi.json', options={'examples': render_examples})
18451863

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

0 commit comments

Comments
 (0)