Repository: swistakm/graceful

Issue:
Improperly handled fields with many=True
Restrict "many" field handling to lists in similar way we handle serialization.

Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/11035390/badge)](https://coveralls.io/builds/11035390)

Coverage remained the same at 100.0% when pulling **1ff658e188518d178c0fd584f68598ce998c71bf on fix/issue-48-fix-many-fields-deserialization** into **957a1c3164b91c64f4b0e64e1901536aeb2aeecd on master**.


Patch:
diff --git a/src/graceful/serializers.py b/src/graceful/serializers.py
index 7af5fc3..14a0d4e 100644
--- a/src/graceful/serializers.py
+++ b/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
 
 
@@ -168,12 +168,25 @@ class BaseSerializer(metaclass=MetaSerializer):
                 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)
 
@@ -229,10 +242,17 @@ class BaseSerializer(metaclass=MetaSerializer):
         ]
 
         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]):


Test patch:
diff --git a/tests/test_serializers.py b/tests/test_serializers.py
index 7ec4804..5e7b353 100644
--- a/tests/test_serializers.py
+++ b/tests/test_serializers.py
@@ -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
 
 
@@ -245,15 +247,46 @@ def test_serializer_describe():
     ])
 
 
-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)