Skip to content

Commit

Permalink
PR for adding example for request schema
Browse files Browse the repository at this point in the history
  • Loading branch information
Niccolum authored and maximdanilchenko committed Jun 28, 2020
1 parent 7b20423 commit 4697941
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 8 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ python:
- 3.5
- 3.6
- 3.7
- 3.8
- nightly
- pypy3.5-6.0
matrix:
Expand Down
19 changes: 19 additions & 0 deletions aiohttp_apispec/aiohttp_apispec.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ def _update_paths(self, data: dict, method: str, url_path: str):
parameters = self.plugin.converter.schema2parameters(
schema["schema"], **schema["options"]
)
self._add_examples(schema["schema"], parameters, schema["example"])
data["parameters"].extend(parameters)

existing = [p["name"] for p in data["parameters"] if p["in"] == "path"]
Expand Down Expand Up @@ -193,6 +194,24 @@ def _update_paths(self, data: dict, method: str, url_path: str):
operations = copy.deepcopy(data)
self.spec.path(path=url_path, operations={method: operations})

def _add_examples(self, ref_schema, endpoint_schema, example):
def add_to_endpoint_or_ref():
if add_to_refs:
self.spec.components._schemas[name]["example"] = example
else:
endpoint_schema[0]['schema']['allOf'] = [endpoint_schema[0]['schema'].pop('$ref')]
endpoint_schema[0]['schema']["example"] = example
if not example:
return
schema_instance = common.resolve_schema_instance(ref_schema)
name = self.plugin.converter.schema_name_resolver(schema_instance)
add_to_refs = example.pop('add_to_refs')
if self.spec.components.openapi_version.major < 3:
if name and name in self.spec.components._schemas:
add_to_endpoint_or_ref()
else:
add_to_endpoint_or_ref()


def setup_aiohttp_apispec(
app: web.Application,
Expand Down
17 changes: 13 additions & 4 deletions aiohttp_apispec/decorators/request.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from functools import partial
import copy


def request_schema(schema, locations=None, put_into=None, **kwargs):
def request_schema(schema, locations=None, put_into=None, example=None, add_to_refs=False, **kwargs):
"""
Add request info into the swagger spec and
prepare injection keyword arguments from the specified
Expand Down Expand Up @@ -32,28 +33,36 @@ async def index(request):
:param put_into: name of the key in Request object
where validated data will be placed.
If None (by default) default key will be used
:param dict example: Adding example for current schema
:param bool add_to_refs: Working only if example not None,
if True, add example for ref schema.
Otherwise add example to endpoint.
Default False
"""
if callable(schema):
schema = schema()
# location kwarg added for compatibility with old versions
locations = locations or []
if not locations:
locations = kwargs.get("location")
locations = kwargs.pop("location", None)
if locations:
locations = [locations]
else:
locations = None

options = {"required": kwargs.get("required", False)}
options = {"required": kwargs.pop("required", False)}
if locations:
options["default_in"] = locations[0]

def wrapper(func):
if not hasattr(func, "__apispec__"):
func.__apispec__ = {"schemas": [], "responses": {}, "parameters": []}
func.__schemas__ = []
func.__apispec__["schemas"].append({"schema": schema, "options": options})

_example = copy.copy(example) or {}
if _example:
_example['add_to_refs'] = add_to_refs
func.__apispec__["schemas"].append({"schema": schema, "options": options, "example": _example})
# TODO: Remove this block?
if locations and "body" in locations:
body_schema_exists = (
Expand Down
3 changes: 3 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Usage
Quickstart
----------

.. note::
Using strict=True need only for marshmallow < 3.0.0

.. code-block:: python
from aiohttp_apispec import (docs,
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def read(file_name):
url='https://github.com/maximdanilchenko/aiohttp-apispec',
zip_safe=False,
keywords='aiohttp marshmallow apispec swagger',
python_requires='>=3.5',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
Expand All @@ -31,6 +32,7 @@ def read(file_name):
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
],
test_suite='tests',
)
23 changes: 22 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ def __init__(self, message):
self.message = message


@pytest.fixture
def example_for_request_schema():
return {
'id': 1,
'name': 'test',
'bool_field': True,
'list_field': [1, 2, 3]
}

@pytest.fixture(
params=[
({"locations": ["query"]}, True),
Expand All @@ -64,7 +73,7 @@ def __init__(self, message):
({"location": "query"}, False),
]
)
def aiohttp_app(loop, aiohttp_client, request):
def aiohttp_app(loop, aiohttp_client, request, example_for_request_schema):
locations, nested = request.param

@docs(
Expand All @@ -82,6 +91,14 @@ async def handler_get(request):
async def handler_post(request):
return web.json_response({"msg": "done", "data": {}})

@request_schema(RequestSchema, example=example_for_request_schema)
async def handler_post_with_example_to_endpoint(request):
return web.json_response({"msg": "done", "data": {}})

@request_schema(RequestSchema, example=example_for_request_schema, add_to_refs=True)
async def handler_post_with_example_to_ref(request):
return web.json_response({"msg": "done", "data": {}})

@request_schema(RequestSchema(partial=True))
async def handler_post_partial(request):
return web.json_response({"msg": "done", "data": {}})
Expand Down Expand Up @@ -167,6 +184,8 @@ async def validated_view(request: web.Request):
[
web.get("/test", handler_get),
web.post("/test", handler_post),
web.post("/example_endpoint", handler_post_with_example_to_endpoint),
web.post("/example_ref", handler_post_with_example_to_ref),
web.post("/test_partial", handler_post_partial),
web.post("/test_call", handler_post_callable_schema),
web.get("/other", other),
Expand All @@ -190,6 +209,8 @@ async def validated_view(request: web.Request):
[
web.get("/v1/test", handler_get),
web.post("/v1/test", handler_post),
web.post("/v1/example_endpoint", handler_post_with_example_to_endpoint),
web.post("/v1/example_ref", handler_post_with_example_to_ref),
web.post("/v1/test_partial", handler_post_partial),
web.post("/v1/test_call", handler_post_callable_schema),
web.get("/v1/other", other),
Expand Down
34 changes: 34 additions & 0 deletions tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,22 @@ async def index(request, **data):

return index

@pytest.fixture
def aiohttp_view_request_schema_with_example_without_refs(self, example_for_request_schema):
@request_schema(RequestSchema, example=example_for_request_schema)
async def index(request, **data):
return web.json_response({"msg": "done", "data": {}})

return index

@pytest.fixture
def aiohttp_view_request_schema_with_example(self, example_for_request_schema):
@request_schema(RequestSchema, example=example_for_request_schema, add_to_refs=True)
async def index(request, **data):
return web.json_response({"msg": "done", "data": {}})

return index

def test_docs_view(self, aiohttp_view_docs):
assert hasattr(aiohttp_view_docs, "__apispec__")
assert aiohttp_view_docs.__apispec__["tags"] == ["mytag"]
Expand Down Expand Up @@ -115,6 +131,24 @@ def test_marshalling(self, aiohttp_view_marshal):
assert param in aiohttp_view_marshal.__apispec__
assert "200" in aiohttp_view_marshal.__apispec__["responses"]

def test_request_schema_with_example_without_refs(
self,
aiohttp_view_request_schema_with_example_without_refs,
example_for_request_schema):
schema = aiohttp_view_request_schema_with_example_without_refs.__apispec__["schemas"][0]
expacted_result = example_for_request_schema.copy()
expacted_result['add_to_refs'] = False
assert schema['example'] == expacted_result

def test_request_schema_with_example(
self,
aiohttp_view_request_schema_with_example,
example_for_request_schema):
schema = aiohttp_view_request_schema_with_example.__apispec__["schemas"][0]
expacted_result = example_for_request_schema.copy()
expacted_result['add_to_refs'] = True
assert schema['example'] == expacted_result

def test_all(self, aiohttp_view_all):
assert hasattr(aiohttp_view_all, "__apispec__")
assert hasattr(aiohttp_view_all, "__schemas__")
Expand Down
19 changes: 16 additions & 3 deletions tests/test_documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def safe_url_for(route):
assert URL("/v1/api/docs/api-docs") in urls


async def test_app_swagger_json(aiohttp_app):
async def test_app_swagger_json(aiohttp_app, example_for_request_schema):
resp = await aiohttp_app.get("/v1/api/docs/api-docs")
docs = await resp.json()
assert docs["info"]["title"] == "API documentation"
Expand Down Expand Up @@ -119,6 +119,19 @@ async def test_app_swagger_json(aiohttp_app):
},
sort_keys=True,
)
assert docs["paths"]["/v1/example_endpoint"]["post"]["parameters"] == [
{
'in': 'body',
'required': False,
'name': 'body',
'schema': {
'allOf': [
'#/definitions/Request'
],
'example': example_for_request_schema
}
}
]

_request_properties = {
"properties": {
Expand All @@ -132,9 +145,10 @@ async def test_app_swagger_json(aiohttp_app):
},
"type": "object",
}

assert json.dumps(docs["definitions"], sort_keys=True) == json.dumps(
{
"Request": _request_properties,
"Request": {**_request_properties, 'example': example_for_request_schema},
"Partial-Request": _request_properties,
"Response": {
"properties": {"data": {"type": "object"}, "msg": {"type": "string"}},
Expand All @@ -144,7 +158,6 @@ async def test_app_swagger_json(aiohttp_app):
sort_keys=True,
)


async def test_not_register_route_for_none_url():
app = web.Application()
routes_count = len(app.router.routes())
Expand Down

0 comments on commit 4697941

Please sign in to comment.