diff --git a/docs/index.rst b/docs/index.rst index 5f60b6f..14b833e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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 ======= diff --git a/sphinxcontrib/openapi/__main__.py b/sphinxcontrib/openapi/__main__.py index e7530a5..cdc55df 100644 --- a/sphinxcontrib/openapi/__main__.py +++ b/sphinxcontrib/openapi/__main__.py @@ -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): diff --git a/sphinxcontrib/openapi/directive.py b/sphinxcontrib/openapi/directive.py index d42785a..235a50c 100644 --- a/sphinxcontrib/openapi/directive.py +++ b/sphinxcontrib/openapi/directive.py @@ -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 diff --git a/tests/test_openapi.py b/tests/test_openapi.py index 103e00d..f544978 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -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 ('Example response:' 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')