Skip to content

Commit

Permalink
Merge pull request #4 from aubergine-developers/dev/required-as-a-list
Browse files Browse the repository at this point in the history
Solves #3 and fixes previously invalid handling of the required field.
  • Loading branch information
dexter2206 committed Mar 21, 2018
2 parents af0689a + c183579 commit 1087ea4
Show file tree
Hide file tree
Showing 8 changed files with 36 additions and 32 deletions.
6 changes: 3 additions & 3 deletions nadia/array.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Functionallities related to building schemas for array type."""
"""Functionalities related to building schemas for array type."""
from marshmallow import fields
from nadia.common import Builder


class ArrayBuilder(Builder):
"""Schema builder for array datatype."""

def build_schema(self, spec):
def build_schema(self, spec, **kwargs):
"""Build Marshmallow schema for array datatype.
:param spec: specification of array for which Schema should be build.
Expand All @@ -17,4 +17,4 @@ def build_schema(self, spec):
"""
item_body = spec['items']
item_builder = self.builder_provider.get_builder(item_body['type'])
return fields.List(item_builder.build_schema(item_body), **self.translate_args(spec))
return fields.List(item_builder.build_schema(item_body), **self.translate_args(spec, **kwargs))
15 changes: 8 additions & 7 deletions nadia/common.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Functionallities related to all builders."""
"""Functionalities related to all builders."""
from abc import ABC, abstractmethod


Expand All @@ -14,24 +14,25 @@ class Builder(ABC):
def __init__(self, builder_provider):
self.builder_provider = builder_provider

@classmethod
def translate_args(cls, spec):
@staticmethod
def translate_args(spec, **kwargs):
"""Translate arguments given in OpenAPI spec to keyword arguments for marshmallow classes.
:param spec: a dictionary extracted from OpenAPI spec containing definition of some
object.
:type spec: dict
:return: A mapping containing keyword arguments used for constructing marshmalow objects.
:return: A mapping containing keyword arguments used for constructing marshmallow objects.
:rtype: dict
"""

return {
'allow_none': spec.get('nullable', False),
'required': spec.get('required', False)
'required': kwargs.get('required', False)
}

@abstractmethod
def build_schema(self, spec):
"""Build marshmallow schema from definition extracted from OpenAPI specs.
def build_schema(self, spec, **kwargs):
"""Build marshmallow schema from definition extracted from OpenAPI specs.
:param spec: an object definition extracted from OpenAPI specification.
:return: Schema or a Field constructed from the spec dictionary.
Expand Down
2 changes: 1 addition & 1 deletion nadia/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class UnknownTypeException(Exception):
"""Exception raised when we encounter unknown datatype in schema.
Note that circumstances under which this Exception is raised should not
happend when the schema dict passed was created from valid OpenAPI specification.
happen when the schema dict passed was created from valid OpenAPI specification.
:param typename: name of type that resulted in raising exception.
:type typename: str
Expand Down
22 changes: 12 additions & 10 deletions nadia/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,35 @@
class ObjectBuilder(Builder):
"""Schema builder for object datatype."""

def build_schema(self, spec):
def build_schema(self, spec, **kwargs):
"""Build Schema from a definition of OpenAPI object.
:param spec: a mapping containing object definition extracted from OpenAPI spec.
:type spec: dict
:return: Schema constructed from `spec`.
:rtype: :py:class:`marshmalow.Schema`
:rtype: :py:class:`marshmallow.Schema`
"""
attrs = self.construct_attributes_schemas(spec['properties'])
attrs = self.construct_attributes_schemas(spec)
obj_schema = self.create_schema_type(attrs)
return fields.Nested(obj_schema, **self.translate_args(spec))
return fields.Nested(obj_schema, **self.translate_args(spec, **kwargs))

def construct_attributes_schemas(self, properties):
"""Construct Schemas corresponding to object properties.
def construct_attributes_schemas(self, spec):
"""Construct Schemas corresponding to object definition.
This method is used to construct attributes of the object schema being constructed.
:param properties: a dictionary of properties extracted from OpenAPI object's definition.
:type properties: dict
:param spec: an OpenAPI object's definition.
:type spec: dict
:return: a mapping property-name -> Schema or Field.
:rtype: dict
"""
properties = spec['properties']
attr_schemas = {}
for prop_name, prop_content in properties.items():
attr_type = prop_content['type']
attr_schemas_builder = self.builder_provider.get_builder(attr_type)
attr_schemas[prop_name] = attr_schemas_builder.build_schema(prop_content)
attr_required = prop_name in spec.get('required', [])
attr_schemas[prop_name] = attr_schemas_builder.build_schema(prop_content, required=attr_required)
return attr_schemas

@staticmethod
Expand All @@ -43,7 +45,7 @@ def create_schema_type(attrs):
:param attrs: mapping of attributes of the newly created Python type.
:type attrs: dict
:return: newly created type with randomly choosen name and single base class -
:return: newly created type with randomly chosen name and single base class -
:py:class:`nadia.NadiaSchema`.
:rtype: type
"""
Expand Down
8 changes: 4 additions & 4 deletions nadia/primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ class PrimitiveBuilder(Builder):
marshmallow_class = None

@classmethod
def build_schema(cls, spec):
def build_schema(cls, spec, **kwargs):
"""Build a Field for a primitive object.
.. seealso: :py:meth:`nadia.common.Builder.build_schema`
.. note :: Conforming to the base class documentation, this method returns
instances of :py:class:`marshmallow.Field`.
"""
return cls.marshmallow_class(**cls.translate_args(spec))
return cls.marshmallow_class(**cls.translate_args(spec, **kwargs))

class FloatBuilder(PrimitiveBuilder):
"""Float schema builder.
Expand All @@ -31,7 +31,7 @@ class FloatBuilder(PrimitiveBuilder):
class IntegerBuilder(PrimitiveBuilder):
"""Integer schema builder.
This bulder is designed for constructing schemas for OpenAPI 'integer' type.
This builder is designed for constructing schemas for OpenAPI 'integer' type.
"""
key = 'integer'
marshmallow_class = fields.Integer
Expand All @@ -40,7 +40,7 @@ class IntegerBuilder(PrimitiveBuilder):
class StringBuilder(PrimitiveBuilder):
"""Str schema builder.
Thsi builder is designed for constructing schemas for OpenAPI 'string' type.
This builder is designed for constructing schemas for OpenAPI 'string' type.
"""
key = 'string'
marshmallow_class = fields.String
3 changes: 1 addition & 2 deletions tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,5 @@ def test_required(self):
arr_def = {'type': 'array', 'items': {'type': 'number'}}
builder = ArrayBuilder(provider)
for required in (True, False):
arr_def['required'] = required
schema = builder.build_schema(arr_def)
schema = builder.build_schema(arr_def, required=required)
self.assertEqual(required, schema.required)
2 changes: 1 addition & 1 deletion tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ def test_nullable(self, builder):
def test_required(self, builder):
"""Building a Float field should respect required settings."""
for required in (True, False):
field = builder.build_schema({'required': required})
field = builder.build_schema({}, required=required)
self.assertEqual(required, field.required)
10 changes: 6 additions & 4 deletions tests/test_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class TestObjectBuilder(unittest.TestCase):

provider = None
properties = None
required = None

def setUp(self):
number_mock = mock.Mock()
Expand All @@ -28,6 +29,7 @@ def setUp(self):
self.properties = {'x': {'type': 'number'},
'y': {'type': 'integer'},
'name': {'type': 'string'}}
self.required = ['x', 'y']

def test_return_type(self):
"""Building schema with ObjectBuilder should return marshmallow.fields.Nested instance."""
Expand All @@ -49,13 +51,13 @@ def test_required(self):
obj_def = {'type': 'object', 'properties': self.properties}
builder = ObjectBuilder(self.provider)
for required in (True, False):
obj_def['required'] = required
self.assertEqual(required, builder.build_schema(obj_def).required)
self.assertEqual(required, builder.build_schema(obj_def, required=required).required)

def test_properties_building(self):
"""Constructing nested objects attributes should correctly call provided schema builders."""
obj_def = {'type': 'object', 'properties': self.properties}
builder = ObjectBuilder(self.provider)
props_schemas = builder.construct_attributes_schemas(self.properties)
props_schemas = builder.construct_attributes_schemas(obj_def)
for prop_name, prop in self.properties.items():
prop_builder = self.provider.get_builder(prop['type'])
prop_builder.build_schema.assert_called_once()
Expand All @@ -69,6 +71,6 @@ def test_build_schema(self, cas, cst, nested):
obj_def = {'type': 'object', 'properties': self.properties}
builder = ObjectBuilder(self.provider)
schema = builder.build_schema(obj_def)
cas.assert_called_once_with(obj_def['properties'])
cas.assert_called_once_with(obj_def)
cst.assert_called_once_with(cas.return_value)
nested.assert_called_once_with(cst.return_value, allow_none=False, required=False)

0 comments on commit 1087ea4

Please sign in to comment.