Skip to content

Commit 93e20d9

Browse files
Merge branch 'master' into json-example
2 parents 54c07af + 3df3e9c commit 93e20d9

27 files changed

+5018
-33
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
strategy:
2525
matrix:
2626
os: [ubuntu-latest, macos-latest]
27-
python-version: [3.5, 3.6, 3.7, 3.8]
27+
python-version: [3.6, 3.7, 3.8]
2828

2929
runs-on: ${{ matrix.os }}
3030
steps:

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ docs/_build/
4747
*.sublime-project
4848
*.sublime-workspace
4949

50+
# Ignore VSCode folder
51+
/.vscode/
52+
5053
# Ignore virtualenvs (who places it near)
5154
.venv/
5255

docs/index.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,36 @@ The ``openapi`` directive supports the following options:
100100
Would render ``/persons`` and ``/evidence`` endpoints, but not
101101
``/evidence/{pk}`` endpoints
102102

103+
``methods``
104+
A line separated list of http methods to filter included openapi
105+
spec. For example:
106+
107+
.. code:: restructuredtext
108+
109+
.. openapi:: specs/openapi.yml
110+
:methods:
111+
get
112+
post
113+
put
114+
:encoding: utf-8
115+
116+
Would render paths with get, post or put method
117+
103118
``exclude``, ``include`` and ``paths`` can also be used together (``exclude``
104119
taking precedence over ``include`` and ``paths``)
105120

121+
``http-methods-order``
122+
A whitespace delimited list of HTTP methods to render first. For example:
123+
124+
.. code:: restructuredtext
125+
126+
.. openapi:: specs/openapi.yml
127+
:http-methods-order:
128+
head
129+
get
130+
131+
Would render the ``head`` method, followed by the ``get`` method, followed by the rest of the methods in their declared ordered.
132+
106133

107134
.. _Sphinx: https://www.sphinx-doc.org/en/master/
108135
.. _OpenAPI: https://github.com/OAI/OpenAPI-Specification

setup.py

100644100755
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
"Programming Language :: Python",
4848
"Programming Language :: Python :: 3",
4949
"Programming Language :: Python :: 3 :: Only",
50-
"Programming Language :: Python :: 3.5",
5150
"Programming Language :: Python :: 3.6",
5251
"Programming Language :: Python :: 3.7",
5352
"Programming Language :: Python :: 3.8",
@@ -57,5 +56,5 @@
5756
"Framework :: Sphinx :: Extension",
5857
],
5958
namespace_packages=["sphinxcontrib"],
60-
python_requires=">=3.5",
59+
python_requires=">=3.6",
6160
)

sphinxcontrib/openapi/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121

2222
_BUILTIN_RENDERERS = {
23+
"httpdomain": renderers.HttpdomainRenderer,
2324
"httpdomain:old": renderers.HttpdomainOldRenderer,
2425
}
2526
_DEFAULT_RENDERER_NAME = "httpdomain:old"
@@ -54,6 +55,27 @@ def setup(app):
5455
app.add_config_value("openapi_default_renderer", _DEFAULT_RENDERER_NAME, "html")
5556
app.add_config_value("openapi_renderers", {}, "html")
5657

58+
from sphinxcontrib import httpdomain
59+
60+
for idx, fieldtype in enumerate(httpdomain.HTTPResource.doc_field_types):
61+
if fieldtype.name == 'requestheader':
62+
httpdomain.HTTPResource.doc_field_types[idx] = httpdomain.TypedField(
63+
fieldtype.name,
64+
label=fieldtype.label,
65+
names=fieldtype.names,
66+
typerolename='header',
67+
typenames=('reqheadertype', ),
68+
)
69+
70+
if fieldtype.name == 'responseheader':
71+
httpdomain.HTTPResource.doc_field_types[idx] = httpdomain.TypedField(
72+
fieldtype.name,
73+
label=fieldtype.label,
74+
names=fieldtype.names,
75+
typerolename='header',
76+
typenames=('resheadertype', ),
77+
)
78+
5779
app.setup_extension("sphinxcontrib.httpdomain")
5880
app.connect("config-inited", _register_rendering_directives)
5981

sphinxcontrib/openapi/directive.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,19 @@
88
:license: BSD, see LICENSE for details.
99
"""
1010

11-
import collections
1211
import functools
1312

1413
from docutils.parsers.rst import directives
1514
from sphinx.util.docutils import SphinxDirective
1615
import yaml
1716

1817

19-
# Dictionaries do not guarantee to preserve the keys order so when we load
20-
# JSON or YAML - we may loose the order. In most cases it's not important
21-
# because we're interested in data. However, in case of OpenAPI spec it'd
22-
# be really nice to preserve them since, for example, endpoints may be
23-
# grouped logically and that improved readability.
24-
class _YamlOrderedLoader(yaml.SafeLoader):
25-
pass
26-
27-
28-
_YamlOrderedLoader.add_constructor(
29-
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
30-
lambda loader, node: collections.OrderedDict(loader.construct_pairs(node))
31-
)
32-
33-
3418
# Locally cache spec to speedup processing of same spec file in multiple
3519
# openapi directives
3620
@functools.lru_cache()
3721
def _get_spec(abspath, encoding):
3822
with open(abspath, 'rt', encoding=encoding) as stream:
39-
return yaml.load(stream, _YamlOrderedLoader)
23+
return yaml.safe_load(stream)
4024

4125

4226
def create_directive_from_renderer(renderer_cls):

sphinxcontrib/openapi/openapi20.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def _convert(schema, name='', required=False):
104104

105105
type_ = schema.get('type', 'any')
106106
required_properties = schema.get('required', ())
107-
if type_ == 'object':
107+
if type_ == 'object' and schema.get('properties'):
108108
for prop, next_schema in schema.get('properties', {}).items():
109109
_convert(
110110
next_schema, '{name}.{prop}'.format(**locals()),
@@ -231,6 +231,8 @@ def openapihttpdomain(spec, **options):
231231

232232
for endpoint in paths:
233233
for method, properties in spec['paths'][endpoint].items():
234+
if options.get('methods') and method not in options.get('methods'):
235+
continue
234236
key = properties.get('tags', [''])[0]
235237
groups.setdefault(key, []).append(_httpresource(
236238
endpoint,
@@ -249,6 +251,8 @@ def openapihttpdomain(spec, **options):
249251
else:
250252
for endpoint in paths:
251253
for method, properties in spec['paths'][endpoint].items():
254+
if options.get('methods') and method not in options.get('methods'):
255+
continue
252256
generators.append(_httpresource(
253257
endpoint,
254258
method,

sphinxcontrib/openapi/openapi30.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
"""
1010

1111
import copy
12+
1213
import collections
14+
import collections.abc
15+
1316
from datetime import datetime
1417
import itertools
1518
import json
@@ -64,9 +67,9 @@ def _dict_merge(dct, merge_dct):
6467
dct: dict onto which the merge is executed
6568
merge_dct: dct merged into dct
6669
"""
67-
for k, v in merge_dct.items():
70+
for k in merge_dct.keys():
6871
if (k in dct and isinstance(dct[k], dict)
69-
and isinstance(merge_dct[k], collections.Mapping)):
72+
and isinstance(merge_dct[k], collections.abc.Mapping)):
7073
_dict_merge(dct[k], merge_dct[k])
7174
else:
7275
dct[k] = merge_dct[k]
@@ -105,11 +108,17 @@ def _parse_schema(schema, method):
105108
schema_type = schema.get('type', 'object')
106109

107110
if schema_type == 'array':
108-
# special case oneOf so that we can show examples for all possible
109-
# combinations
111+
# special case oneOf and anyOf so that we can show examples for all
112+
# possible combinations
110113
if 'oneOf' in schema['items']:
111114
return [
112-
_parse_schema(x, method) for x in schema['items']['oneOf']]
115+
_parse_schema(x, method) for x in schema['items']['oneOf']
116+
]
117+
118+
if 'anyOf' in schema['items']:
119+
return [
120+
_parse_schema(x, method) for x in schema['items']['anyOf']
121+
]
113122

114123
return [_parse_schema(schema['items'], method)]
115124

@@ -181,7 +190,8 @@ def _example(media_type_objects, method=None, endpoint=None, status=None,
181190
if examples is None:
182191
examples = {}
183192
if not example:
184-
if content_type != 'application/json':
193+
if re.match(r"application/[a-zA-Z\+]*json", content_type) is \
194+
None:
185195
LOG.info('skipping non-JSON example generation.')
186196
continue
187197
example = _parse_schema(content['schema'], method=method)

sphinxcontrib/openapi/renderers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
from . import abc
44
from ._httpdomain_old import HttpdomainOldRenderer
5+
from ._httpdomain import HttpdomainRenderer
56

67

78
__all__ = [
89
"abc",
910
"HttpdomainOldRenderer",
11+
"HttpdomainRenderer",
1012
]

0 commit comments

Comments
 (0)