Skip to content

Commit

Permalink
serializers: fix handling deserialization of many=True fields (fixes #48
Browse files Browse the repository at this point in the history
)
  • Loading branch information
swistakm committed Apr 11, 2017
1 parent 957a1c3 commit 1ff658e
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 14 deletions.
40 changes: 30 additions & 10 deletions src/graceful/serializers.py
@@ -1,7 +1,7 @@
from collections import OrderedDict
from collections.abc import Mapping, MutableMapping

from graceful.errors import DeserializationError, ValidationError
from graceful.errors import DeserializationError
from graceful.fields import BaseField


Expand Down Expand Up @@ -168,12 +168,25 @@ def from_representation(self, representation):
continue

try:
# if field has explicitly specified source then use it
# else fallback to field name.
# Note: field does not know its name
object_dict[_source(name, field)] = field.from_representation(
representation[name]
)
if (
# note: we cannot check for any sequence or iterable
# because of strings and nested dicts.
not isinstance(representation[name], (list, tuple)) and
field.many
):
raise ValueError("field should be sequence")

source = _source(name, field)
value = representation[name]

if field.many:
object_dict[source] = [
field.from_representation(single_value)
for single_value in value
]
else:
object_dict[source] = field.from_representation(value)

except ValueError as err:
failed[name] = str(err)

Expand Down Expand Up @@ -229,10 +242,17 @@ def validate(self, object_dict, partial=False):
]

invalid = {}
for name, value in object_dict.items():
for index, (name, value) in enumerate(object_dict.items()):
try:
sources[name].validate(value)
except ValidationError as err:
field = sources[name]

if field.many:
for single_value in value:
field.validate(single_value)
else:
field.validate(value)

except ValueError as err:
invalid[name] = str(err)

if any([missing, forbidden, invalid]):
Expand Down
41 changes: 37 additions & 4 deletions tests/test_serializers.py
Expand Up @@ -3,7 +3,9 @@
All tested serializer classes should be defined within tests because
we test how whole framework for defining new serializers works.
"""
from graceful.fields import BaseField
import pytest

from graceful.fields import BaseField, StringField
from graceful.serializers import BaseSerializer


Expand Down Expand Up @@ -245,15 +247,46 @@ class ExampleSerializer(BaseSerializer):
])


def test_serialiser_representation_with_field_many():
def test_serialiser_with_field_many():
class UpperField(BaseField):
def to_representation(self, value):
return value.upper()

def from_representation(self, data):
return data.upper()

class ExampleSerializer(BaseSerializer):
up = UpperField(details='multiple values field', many=True)

serializer = ExampleSerializer()
instance = {'up': ["aa", "bb", "cc"]}
obj = {'up': ["aa", "bb", "cc"]}
desired = {'up': ["AA", "BB", "CC"]}

assert serializer.to_representation(obj) == desired
assert serializer.from_representation(obj) == desired

with pytest.raises(ValueError):
serializer.from_representation({"up": "definitely not a sequence"})


def test_serializer_many_validation():
def is_upper(value):
if value.upper() != value:
raise ValueError("should be upper")

class ExampleSerializer(BaseSerializer):
up = StringField(
details='multiple values field',
many=True,
validators=[is_upper]
)

invalid = {'up': ["aa", "bb", "cc"]}
valid = {'up': ["AA", "BB", "CC"]}

serializer = ExampleSerializer()

with pytest.raises(ValueError):
serializer.validate(invalid)

assert serializer.to_representation(instance) == {"up": ["AA", "BB", "CC"]}
serializer.validate(valid)

0 comments on commit 1ff658e

Please sign in to comment.