Skip to content

Commit

Permalink
Merge pull request #336 from marshmallow-code/dev_helpers_return_none
Browse files Browse the repository at this point in the history
Allow helpers to return None
  • Loading branch information
lafrech committed Nov 6, 2018
2 parents d2ac1d1 + 9b89485 commit 3f25523
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 43 deletions.
14 changes: 12 additions & 2 deletions CHANGELOG.rst
Expand Up @@ -30,11 +30,21 @@ Features:
spec.path('/pets/', operations=...)
spec.components.schema('Pet', properties=...)

- Plugins can define ``parameter_helper`` and ``response_helper`` to modify
parameter and response components definitions.
- ``MarshmallowPlugin`` resolves schemas in parameters and responses components.
- Components helpers may return ``None`` as a no-op rather than an empty `dict`
(:pr:`336`).

Bug fixes:

- ``MarshmallowPlugin.schema_helper`` does not crash when no schema is passed
(:pr:`336`).

Deprecations/Removals:

- The ``response_helper`` feature is removed. The same can be achieved from
``operation_helper``.
- The legacy ``response_helper`` feature is removed. The same can be achieved
from ``operation_helper``.

1.0.0b4 (2018-10-28)
++++++++++++++++++++
Expand Down
6 changes: 3 additions & 3 deletions apispec/core.py
Expand Up @@ -127,7 +127,7 @@ def schema(
# Execute all helpers from plugins
for plugin in self._plugins:
try:
ret.update(plugin.schema_helper(name, definition=ret, **kwargs))
ret.update(plugin.schema_helper(name, definition=ret, **kwargs) or {})
except PluginMethodNotImplementedError:
continue
if properties:
Expand All @@ -153,7 +153,7 @@ def parameter(self, param_id, location, **kwargs):
# Execute all helpers from plugins
for plugin in self._plugins:
try:
ret.update(plugin.parameter_helper(**kwargs))
ret.update(plugin.parameter_helper(**kwargs) or {})
except PluginMethodNotImplementedError:
continue
self._parameters[param_id] = ret
Expand All @@ -168,7 +168,7 @@ def response(self, ref_id, **kwargs):
# Execute all helpers from plugins
for plugin in self._plugins:
try:
ret.update(plugin.response_helper(**kwargs))
ret.update(plugin.response_helper(**kwargs) or {})
except PluginMethodNotImplementedError:
continue
self._responses[ref_id] = ret
Expand Down
4 changes: 3 additions & 1 deletion apispec/ext/marshmallow/__init__.py
Expand Up @@ -155,13 +155,15 @@ class MyCustomFieldThatsKindaLikeAnInteger(Integer):
"""
return self.openapi.map_to_openapi_type(*args)

def schema_helper(self, name, schema, **kwargs):
def schema_helper(self, name, schema=None, **kwargs):
"""Definition helper that allows using a marshmallow
:class:`Schema <marshmallow.Schema>` to provide OpenAPI
metadata.
:param type|Schema schema: A marshmallow Schema class or instance.
"""
if schema is None:
return None

schema_cls = resolve_schema_cls(schema)
schema_instance = resolve_schema_instance(schema)
Expand Down
8 changes: 4 additions & 4 deletions apispec/plugin.py
Expand Up @@ -13,15 +13,15 @@ def init_spec(self, spec):
"""

def schema_helper(self, name, definition, **kwargs):
"""Must return definition as a dict."""
"""May return definition as a dict."""
raise PluginMethodNotImplementedError

def parameter_helper(self, **kwargs):
"""Must return parameter component description as a dict."""
"""May return parameter component description as a dict."""
raise PluginMethodNotImplementedError

def response_helper(self, **kwargs):
"""Must return response component description as a dict."""
"""May return response component description as a dict."""
raise PluginMethodNotImplementedError

def path_helper(self, path=None, operations=None, **kwargs):
Expand All @@ -41,7 +41,7 @@ def path_helper(self, path=None, operations=None, **kwargs):
raise PluginMethodNotImplementedError

def operation_helper(self, path=None, operations=None, **kwargs):
"""Should mutate operations.
"""May mutate operations.
:param str path: Path to the resource
:param dict operations: A `dict` mapping HTTP methods to operation object. See
Expand Down
89 changes: 57 additions & 32 deletions tests/test_core.py
Expand Up @@ -353,81 +353,106 @@ def test_path_check_invalid_http_method(self, spec):

class TestPlugins:

class TestPlugin(BasePlugin):
def schema_helper(self, name, definition, **kwargs):
return {'properties': {'name': {'type': 'string'}}}
@staticmethod
def test_plugin_factory(return_none=False):

def parameter_helper(self, **kwargs):
return {'name': 'parameter'}
class TestPlugin(BasePlugin):
def schema_helper(self, name, definition, **kwargs):
if not return_none:
return {'properties': {'name': {'type': 'string'}}}

def response_helper(self, **kwargs):
return {'description': '42'}
def parameter_helper(self, **kwargs):
if not return_none:
return {'description': 'some parameter'}

def path_helper(self, path, operations, **kwargs):
if path == '/path_1':
operations.update({'get': {'responses': {'200': {}}}})
return '/path_1_modified'
def response_helper(self, **kwargs):
if not return_none:
return {'description': '42'}

def operation_helper(self, path, operations, **kwargs):
if path == '/path_2':
operations['post'] = {'responses': {'201': {}}}
def path_helper(self, path, operations, **kwargs):
if not return_none:
if path == '/path_1':
operations.update({'get': {'responses': {'200': {}}}})
return '/path_1_modified'

def operation_helper(self, path, operations, **kwargs):
if path == '/path_2':
operations['post'] = {'responses': {'201': {}}}

return TestPlugin()

@pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0', ))
def test_plugin_schema_helper_is_used(self, openapi_version):
@pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0'))
@pytest.mark.parametrize('return_none', (True, False))
def test_plugin_schema_helper_is_used(self, openapi_version, return_none):
spec = APISpec(
title='Swagger Petstore',
version='1.0.0',
openapi_version=openapi_version,
plugins=(self.TestPlugin(), ),
plugins=(self.test_plugin_factory(return_none), ),
)
spec.components.schema('Pet', {})
definitions = get_definitions(spec)
assert definitions['Pet'] == {'properties': {'name': {'type': 'string'}}}
if return_none:
assert definitions['Pet'] == {}
else:
assert definitions['Pet'] == {'properties': {'name': {'type': 'string'}}}

@pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0', ))
def test_plugin_parameter_helper_is_used(self, openapi_version):
@pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0'))
@pytest.mark.parametrize('return_none', (True, False))
def test_plugin_parameter_helper_is_used(self, openapi_version, return_none):
spec = APISpec(
title='Swagger Petstore',
version='1.0.0',
openapi_version=openapi_version,
plugins=(self.TestPlugin(), ),
plugins=(self.test_plugin_factory(return_none), ),
)
spec.components.parameter('Pet', 'body', **{})
parameters = get_parameters(spec)
assert parameters['Pet'] == {'in': 'body', 'name': 'parameter'}
if return_none:
assert parameters['Pet'] == {'in': 'body', 'name': 'Pet'}
else:
assert parameters['Pet'] == {'in': 'body', 'name': 'Pet', 'description': 'some parameter'}

@pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0', ))
def test_plugin_response_helper_is_used(self, openapi_version):
@pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0'))
@pytest.mark.parametrize('return_none', (True, False))
def test_plugin_response_helper_is_used(self, openapi_version, return_none):
spec = APISpec(
title='Swagger Petstore',
version='1.0.0',
openapi_version=openapi_version,
plugins=(self.TestPlugin(), ),
plugins=(self.test_plugin_factory(return_none), ),
)
spec.components.response('Pet', **{})
responses = get_responses(spec)
assert responses['Pet'] == {'description': '42'}
if return_none:
assert responses['Pet'] == {}
else:
assert responses['Pet'] == {'description': '42'}

@pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0', ))
def test_plugin_path_helper_is_used(self, openapi_version):
@pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0'))
@pytest.mark.parametrize('return_none', (True, False))
def test_plugin_path_helper_is_used(self, openapi_version, return_none):
spec = APISpec(
title='Swagger Petstore',
version='1.0.0',
openapi_version=openapi_version,
plugins=(self.TestPlugin(), ),
plugins=(self.test_plugin_factory(return_none), ),
)
spec.path('/path_1')
paths = get_paths(spec)
assert len(paths) == 1
assert paths['/path_1_modified'] == {'get': {'responses': {'200': {}}}}
if return_none:
assert paths['/path_1'] == {}
else:
assert paths['/path_1_modified'] == {'get': {'responses': {'200': {}}}}

@pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0', ))
@pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0'))
def test_plugin_operation_helper_is_used(self, openapi_version):
spec = APISpec(
title='Swagger Petstore',
version='1.0.0',
openapi_version=openapi_version,
plugins=(self.TestPlugin(), ),
plugins=(self.test_plugin_factory(), ),
)
spec.path('/path_2', operations={'post': {'responses': {'200': {}}}})
paths = get_paths(spec)
Expand Down
6 changes: 5 additions & 1 deletion tests/test_ext_marshmallow.py
Expand Up @@ -31,12 +31,16 @@ class TestDefinitionHelper:
def test_can_use_schema_as_definition(self, spec, schema):
spec.components.schema('Pet', schema=schema)
definitions = get_definitions(spec)
assert 'Pet' in definitions
props = definitions['Pet']['properties']

assert props['id']['type'] == 'integer'
assert props['name']['type'] == 'string'

def test_schema_helper_without_schema(self, spec):
spec.components.schema('Pet', properties={'key': {'type': 'integer'}})
definitions = get_definitions(spec)
assert definitions['Pet']['properties'] == {'key': {'type': 'integer'}}

@pytest.mark.parametrize('schema', [AnalysisSchema, AnalysisSchema()])
def test_resolve_schema_dict_auto_reference(self, schema):
def resolver(schema):
Expand Down

0 comments on commit 3f25523

Please sign in to comment.