Skip to content

Commit be39d48

Browse files
committed
Added swagger_merge_with_file parameter.
Allows redefine the swagger description in the code for needed endpoints
1 parent 6fa8ca9 commit be39d48

File tree

6 files changed

+225
-38
lines changed

6 files changed

+225
-38
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,6 @@ ENV/
132132

133133
# Pycharm
134134
.idea/
135+
136+
# pytest
137+
.pytest_cache/

aiohttp_swagger/__init__.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import asyncio
2-
from os.path import abspath, dirname, join
2+
from os.path import (
3+
abspath,
4+
dirname,
5+
join,
6+
)
37
from types import FunctionType
48

59
from aiohttp import web
610

7-
from .helpers import (generate_doc_from_each_end_point,
8-
load_doc_from_yaml_file, swagger_path)
11+
from .helpers import (
12+
generate_doc_from_each_end_point,
13+
load_doc_from_yaml_file,
14+
swagger_path,
15+
)
916

1017
try:
1118
import ujson as json
@@ -47,24 +54,44 @@ def setup_swagger(app: web.Application,
4754
contact: str = "",
4855
swagger_home_decor: FunctionType = None,
4956
swagger_def_decor: FunctionType = None,
57+
swagger_merge_with_file: bool = False,
58+
swagger_validate_schema: bool = False,
5059
swagger_info: dict = None):
5160
_swagger_url = ("/{}".format(swagger_url)
5261
if not swagger_url.startswith("/")
5362
else swagger_url)
5463
_base_swagger_url = _swagger_url.rstrip('/')
5564
_swagger_def_url = '{}/swagger.json'.format(_base_swagger_url)
5665

57-
# Build Swagget Info
66+
# Build Swagger Info
5867
if swagger_info is None:
5968
if swagger_from_file:
6069
swagger_info = load_doc_from_yaml_file(swagger_from_file)
70+
if swagger_merge_with_file:
71+
swagger_end_points_info = generate_doc_from_each_end_point(
72+
app, api_base_url=api_base_url, description=description,
73+
api_version=api_version, title=title, contact=contact
74+
)
75+
paths = swagger_end_points_info.pop('paths', None)
76+
swagger_info.update(swagger_end_points_info)
77+
if paths is not None:
78+
if 'paths' not in swagger_info:
79+
swagger_info['paths'] = {}
80+
for ph, description in paths.items():
81+
for method, desc in description.items():
82+
if ph not in swagger_info['paths']:
83+
swagger_info['paths'][ph] = {}
84+
swagger_info['paths'][ph][method] = desc
6185
else:
6286
swagger_info = generate_doc_from_each_end_point(
6387
app, api_base_url=api_base_url, description=description,
6488
api_version=api_version, title=title, contact=contact
6589
)
66-
else:
67-
swagger_info = json.dumps(swagger_info)
90+
91+
if swagger_validate_schema:
92+
pass
93+
94+
swagger_info = json.dumps(swagger_info)
6895

6996
_swagger_home_func = _swagger_home
7097
_swagger_def_func = _swagger_def

aiohttp_swagger/helpers/builders.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1+
from typing import (
2+
MutableMapping,
3+
Mapping,
4+
)
15
from collections import defaultdict
2-
from os.path import abspath, dirname, join
6+
from os.path import (
7+
abspath,
8+
dirname,
9+
join,
10+
)
311

412
import yaml
513
from aiohttp import web
@@ -8,7 +16,7 @@
816

917
try:
1018
import ujson as json
11-
except ImportError: # pragma: no cover
19+
except ImportError: # pragma: no cover
1220
import json
1321

1422

@@ -36,37 +44,41 @@ def _extract_swagger_docs(end_point_doc, method="get"):
3644
}
3745
return {method: end_point_swagger_doc}
3846

47+
3948
def _build_doc_from_func_doc(route):
4049

4150
out = {}
4251

4352
if issubclass(route.handler, web.View) and route.method == METH_ANY:
4453
method_names = {
45-
attr for attr in dir(route.handler) \
54+
attr for attr in dir(route.handler)
4655
if attr.upper() in METH_ALL
4756
}
4857
for method_name in method_names:
4958
method = getattr(route.handler, method_name)
5059
if method.__doc__ is not None and "---" in method.__doc__:
5160
end_point_doc = method.__doc__.splitlines()
52-
out.update(_extract_swagger_docs(end_point_doc, method=method_name))
61+
out.update(
62+
_extract_swagger_docs(end_point_doc, method=method_name))
5363

5464
else:
5565
try:
5666
end_point_doc = route.handler.__doc__.splitlines()
5767
except AttributeError:
5868
return {}
59-
out.update(_extract_swagger_docs(end_point_doc))
69+
out.update(_extract_swagger_docs(
70+
end_point_doc, method=route.method.lower()))
6071
return out
6172

73+
6274
def generate_doc_from_each_end_point(
6375
app: web.Application,
6476
*,
6577
api_base_url: str = "/",
6678
description: str = "Swagger API definition",
6779
api_version: str = "1.0.0",
6880
title: str = "Swagger API",
69-
contact: str = ""):
81+
contact: str = "") -> MutableMapping:
7082
# Clean description
7183
_start_desc = 0
7284
for i, word in enumerate(description):
@@ -92,8 +104,6 @@ def generate_doc_from_each_end_point(
92104

93105
for route in app.router.routes():
94106

95-
end_point_doc = None
96-
97107
# If route has a external link to doc, we use it, not function doc
98108
if getattr(route.handler, "swagger_file", False):
99109
try:
@@ -133,13 +143,14 @@ def generate_doc_from_each_end_point(
133143
url = url_info.get("formatter")
134144

135145
swagger["paths"][url].update(end_point_doc)
136-
137-
return json.dumps(swagger)
146+
return swagger
138147

139148

140-
def load_doc_from_yaml_file(doc_path: str):
141-
loaded_yaml = yaml.load(open(doc_path, "r").read())
142-
return json.dumps(loaded_yaml)
149+
def load_doc_from_yaml_file(doc_path: str) -> MutableMapping:
150+
return yaml.load(open(doc_path, "r").read())
143151

144152

145-
__all__ = ("generate_doc_from_each_end_point", "load_doc_from_yaml_file")
153+
__all__ = (
154+
"generate_doc_from_each_end_point",
155+
"load_doc_from_yaml_file"
156+
)

doc/source/customizing.rst

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,50 @@ Global Swagger YAML
168168
169169
web.run_app(app, host="127.0.0.1")
170170
171+
172+
:samp:`aiohttp-swagger` also allow to build an external YAML Swagger file
173+
and merge swagger endpoint definitions to it:
174+
175+
.. code-block:: python
176+
177+
from aiohttp import web
178+
from aiohttp_swagger import *
179+
180+
async def ping(request):
181+
"""
182+
---
183+
tags:
184+
- user
185+
summary: Create user
186+
description: This can only be done by the logged in user.
187+
operationId: examples.api.api.createUser
188+
produces:
189+
- application/json
190+
parameters:
191+
- in: body
192+
name: body
193+
description: Created user object
194+
required: false
195+
196+
responses:
197+
"201":
198+
description: successful operation
199+
"""
200+
return web.Response(text="pong")
201+
202+
app = web.Application()
203+
204+
app.router.add_route('GET', "/ping", ping)
205+
206+
setup_swagger(
207+
app,
208+
swagger_from_file="example_swagger.yaml", # <-- Loaded Swagger from external YAML file
209+
swagger_merge_with_file=True # <-- Merge
210+
)
211+
212+
web.run_app(app, host="127.0.0.1")
213+
214+
171215
Nested applications
172216
+++++++++++++++++++
173217

tests/conftest.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from os.path import (
2+
abspath,
3+
dirname,
4+
join,
5+
)
6+
7+
import yaml
8+
import pytest
9+
10+
11+
@pytest.fixture
12+
def swagger_file():
13+
tests_path = abspath(join(dirname(__file__)))
14+
return join(tests_path, "data", "example_swagger.yaml")
15+
16+
17+
@pytest.fixture
18+
def swagger_info():
19+
filename = abspath(join(dirname(__file__))) + "/data/example_swagger.yaml"
20+
return yaml.load(open(filename).read())
21+
22+

0 commit comments

Comments
 (0)