Commits

Adam Lowry committed 793f380

Added support for strict adaptation when setting container values

  • Participants
  • Parent commits 6c5d262

Comments (0)

Files changed (4)

flatland/schema/containers.py

     :param validators: optional, a sequence of validators that will be
       run after contained elements are validated.
 
+    :param strict: If true, fail to validate if set to an invalid type.
+      Sequences will also fail if set to a string or unicode object.
+
     :param \*\*kw: other arguments common to
       :class:`~flatland.schema.base.FieldSchema`.
 
     descent_validators = ()
     """TODO: doc descent_validators"""
 
+    strict = False
+
+    converted = True
+
     @class_cloner
     def descent_validated_by(cls, *validators):
         """Return a class with descent validators set to *\*validators*.
 
         del self[:]
         values, converted = [], True
+        self.converted = False
         try:
+            if self.strict and isinstance(iterable, basestring):
+                return False
             for v in iterable:
                 el = self.member_schema()
                 converted &= el.set(v)
         except TypeError:
             return False
         else:
+            self.converted = True
             return converted
 
     def set_default(self):
         return repr(list(self))
 
 
+#class StrictList(List):
+#    def set(self, iterable):
+#        del self[:]
+#        values, converted = [], True
+#        try:
+#            if isinstance(iterable, basestring):
+#                return False
+#            for v in iterable:
+#                el = self.member_schema()
+#                converted &= el.set(v)
+#                values.append(el)
+#            self.extend(values)
+#        except TypeError:
+#            return False
+#        else:
+#            return converted
+
+
+
 class Array(Sequence):
     """A transparent homogeneous Container, for multivalued form elements.
 
         """TODO: doc set()"""
         pairs = to_pairs(value)
         self._reset()
+        self.converted = False
 
         seen = set()
         converted = True
             raise TypeError(
                 'all keys required for a set() operation, missing %s.' % (
                     ','.join(repr(key) for key in missing)))
+        self.converted = True
         return converted
 
     def _set_flat(self, pairs, sep):
         """TODO: doc set()"""
         pairs = to_pairs(value)
         self._reset()
+        self.converted = False
 
         if policy is not None:
             assert policy in ('strict', 'subset', 'duck')
         fields = self.field_schema_mapping
         seen = set()
         converted = True
-        for key, value in pairs:
-            if key not in fields:
-                if policy != 'duck':
-                    raise KeyError(
-                        'Dict %r schema does not allow key %r' % (
-                            self.name, key))
-                continue
-            if dict.__contains__(self, key):
-                converted &= self[key].set(value)
-            else:
-                self[key] = el = fields[key]()
-                converted &= el.set(value)
-            seen.add(key)
+        try:
+            for key, value in pairs:
+                if key not in fields:
+                    if policy != 'duck':
+                        raise KeyError(
+                            'Dict %r schema does not allow key %r' % (
+                                self.name, key))
+                    continue
+                if dict.__contains__(self, key):
+                    converted &= self[key].set(value)
+                else:
+                    self[key] = el = fields[key]()
+                    converted &= el.set(value)
+                seen.add(key)
+        except ValueError:
+            return False
 
         if policy == 'strict':
             required = set(fields.iterkeys())
                     'strict-mode Dict requires all keys for '
                     'a set() operation, missing %s.' % (
                         ','.join(repr(key) for key in missing)))
+        self.converted = True
         return converted
 
     def set_by_object(self, obj, include=None, omit=None, rename=None):

flatland/validation/__init__.py

     HasAtMost,
     HasBetween,
     NotDuplicated,
+    ContainerConverted,
     )
 from network import (
     HTTPURLValidator,

flatland/validation/containers.py

         child_label = element.member_schema.label
         return self.note_error(element, state, message,
                                child_label=child_label)
+
+
+class ContainerConverted(Validator):
+    """Validates that a container was converted to the correct type."""
+
+    incorrect = N_(u'%(label)s is the wrong type.')
+
+    def validate(self, element, state):
+        if element.converted:
+            return True
+        return self.note_error(element, state, 'incorrect')

tests/validation/test_containers.py

     HasAtMost,
     HasBetween,
     NotDuplicated,
+    ContainerConverted,
     )
 
 from tests._util import assert_raises
     el = schema(['a', 'b', 'c'])
     assert not el.validate()
     assert el.errors == [u'outer must contain at least 1 and at most 2 inners']
+
+
+def test_strict_list():
+    schema = List.named('outer').using(
+        strict=True, validators=[ContainerConverted()]).of(
+        Integer.named('inner'))
+
+    el = schema('foo')
+    assert not el.validate()
+    assert el.errors == [u'outer is the wrong type.']
+
+    el = schema([1, 2, 3])
+    assert el.validate()
+
+
+def test_strict_dict():
+    schema = Dict.named('xy').using(
+        strict=True, validators=[ContainerConverted()]).of(
+        Integer.named('x'),
+        Integer.named('y'))
+
+    el = schema('foo')
+    assert not el.validate()
+    assert el.errors == [u'xy is the wrong type.']
+
+    el = schema({'x': 1, 'y': 2})
+    assert el.validate()