- Undocumented internal API for all type objects: `flatten`.
External type objects should now inherit from ``colander.SchemaType`` to get a default implementation.
This commit is contained in:
@@ -21,6 +21,9 @@ Changes
|
||||
- If ``default`` is deferred, the serialization default will be
|
||||
assumed to be ``colander.null``.
|
||||
|
||||
- Undocumented internal API for all type objects: ``flatten``.
|
||||
External type objects should now inherit from
|
||||
``colander.SchemaType`` to get a default implementation.
|
||||
|
||||
0.8 (2010/09/08)
|
||||
-----------------
|
||||
|
||||
@@ -329,7 +329,15 @@ class OneOf(object):
|
||||
mapping={'val':value, 'choices':choices})
|
||||
raise Invalid(node, err)
|
||||
|
||||
class Mapping(object):
|
||||
class SchemaType(object):
|
||||
""" Base class for all schema types """
|
||||
def flatten(self, node, appstruct, prefix=''):
|
||||
result = {}
|
||||
selfname = '%s%s' % (prefix, node.name)
|
||||
result[selfname] = appstruct
|
||||
return result
|
||||
|
||||
class Mapping(SchemaType):
|
||||
""" A type which represents a mapping of names to nodes.
|
||||
|
||||
The subnodes of the :class:`colander.SchemaNode` that wraps
|
||||
@@ -447,6 +455,19 @@ class Mapping(object):
|
||||
|
||||
return self._impl(node, cstruct, callback)
|
||||
|
||||
def flatten(self, node, appstruct, prefix=''):
|
||||
result = {}
|
||||
selfname = '%s%s' % (prefix, node.name)
|
||||
selfprefix = selfname + '.'
|
||||
result[selfname] = appstruct
|
||||
|
||||
for num, subnode in enumerate(node.children):
|
||||
name = subnode.name
|
||||
substruct = appstruct.get(name, null)
|
||||
result.update(subnode.typ.flatten(subnode, substruct,
|
||||
prefix=selfprefix))
|
||||
return result
|
||||
|
||||
class Positional(object):
|
||||
"""
|
||||
Marker abstract base class meaning 'this type has children which
|
||||
@@ -455,7 +476,7 @@ class Positional(object):
|
||||
creating a dictionary representation of an error tree.
|
||||
"""
|
||||
|
||||
class Tuple(Positional):
|
||||
class Tuple(Positional, SchemaType):
|
||||
""" A type which represents a fixed-length sequence of nodes.
|
||||
|
||||
The subnodes of the :class:`colander.SchemaNode` that wraps
|
||||
@@ -523,7 +544,19 @@ class Tuple(Positional):
|
||||
|
||||
return self._impl(node, cstruct, callback)
|
||||
|
||||
class Sequence(Positional):
|
||||
def flatten(self, node, appstruct, prefix=''):
|
||||
result = {}
|
||||
selfname = '%s%s' % (prefix, node.name)
|
||||
selfprefix = selfname + '.'
|
||||
result[selfname] = appstruct
|
||||
|
||||
for num, subnode in enumerate(node.children):
|
||||
substruct = appstruct[num]
|
||||
result.update(subnode.typ.flatten(subnode, substruct,
|
||||
prefix=selfprefix))
|
||||
return result
|
||||
|
||||
class Sequence(Positional, SchemaType):
|
||||
"""
|
||||
A type which represents a variable-length sequence of nodes,
|
||||
all of which must be of the same type.
|
||||
@@ -639,9 +672,26 @@ class Sequence(Positional):
|
||||
|
||||
return self._impl(node, cstruct, callback, accept_scalar)
|
||||
|
||||
def flatten(self, node, appstruct, prefix=''):
|
||||
result = {}
|
||||
selfname = '%s%s' % (prefix, node.name)
|
||||
selfprefix = selfname + '.'
|
||||
result[selfname] = appstruct
|
||||
|
||||
childnode = node.children[0]
|
||||
|
||||
for num, subval in enumerate(appstruct):
|
||||
subname = '%s%s' % (selfprefix, num)
|
||||
result[subname] = subval
|
||||
subprefix = subname + '.'
|
||||
result.update(childnode.typ.flatten(childnode, subval,
|
||||
prefix=subprefix))
|
||||
|
||||
return result
|
||||
|
||||
Seq = Sequence
|
||||
|
||||
class String(object):
|
||||
class String(SchemaType):
|
||||
""" A type representing a Unicode string.
|
||||
|
||||
This type constructor accepts one argument:
|
||||
@@ -740,10 +790,9 @@ class String(object):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
Str = String
|
||||
|
||||
class Number(object):
|
||||
class Number(SchemaType):
|
||||
""" Abstract base class for float, int, decimal """
|
||||
|
||||
num = None
|
||||
@@ -771,7 +820,6 @@ class Number(object):
|
||||
mapping={'val':cstruct})
|
||||
)
|
||||
|
||||
|
||||
class Integer(Number):
|
||||
""" A type representing an integer.
|
||||
|
||||
@@ -812,7 +860,7 @@ class Decimal(Number):
|
||||
def num(self, val):
|
||||
return decimal.Decimal(str(val))
|
||||
|
||||
class Boolean(object):
|
||||
class Boolean(SchemaType):
|
||||
""" A type representing a boolean object.
|
||||
|
||||
During deserialization, a value in the set (``false``, ``0``) will
|
||||
@@ -852,7 +900,7 @@ class Boolean(object):
|
||||
|
||||
Bool = Boolean
|
||||
|
||||
class GlobalObject(object):
|
||||
class GlobalObject(SchemaType):
|
||||
""" A type representing an importable Python object. This type
|
||||
serializes 'global' Python objects (objects which can be imported)
|
||||
to dotted Python names.
|
||||
@@ -976,8 +1024,7 @@ class GlobalObject(object):
|
||||
_('The dotted name "${name}" cannot be imported',
|
||||
mapping={'name':cstruct}))
|
||||
|
||||
|
||||
class DateTime(object):
|
||||
class DateTime(SchemaType):
|
||||
""" A type representing a Python ``datetime.datetime`` object.
|
||||
|
||||
This type serializes python ``datetime.datetime`` objects to a
|
||||
@@ -1058,7 +1105,7 @@ class DateTime(object):
|
||||
mapping={'val':cstruct, 'err':e}))
|
||||
return result
|
||||
|
||||
class Date(object):
|
||||
class Date(SchemaType):
|
||||
""" A type representing a Python ``datetime.date`` object.
|
||||
|
||||
This type serializes python ``datetime.date`` objects to a
|
||||
@@ -1243,6 +1290,20 @@ class SchemaNode(object):
|
||||
cstruct = self.typ.serialize(self, appstruct)
|
||||
return cstruct
|
||||
|
||||
def flatten(self, appstruct):
|
||||
""" Create an fstruct from the appstruct based on the schema
|
||||
represented by this node and return the fstruct. An fstruct
|
||||
is a flattened representation of an appstruct. An fstruct is
|
||||
a dictionary; its keys are dotted names. Each dotted name
|
||||
represents a location in the schema. The values of an fstruct
|
||||
dictionary are appstruct subvalues."""
|
||||
flat = self.typ.flatten(self, appstruct)
|
||||
return flat
|
||||
|
||||
def unflatten(self, fstruct):
|
||||
""" Create an appstruct based on the schema represented by
|
||||
this node using the fstruct passed. """
|
||||
|
||||
def deserialize(self, cstruct=null):
|
||||
""" Deserialize and validate the :term:`cstruct` into an
|
||||
:term:`appstruct` based on the schema, and return the
|
||||
|
||||
@@ -319,6 +319,17 @@ class TestOneOf(unittest.TestCase):
|
||||
e = invalid_exc(validator, None, None)
|
||||
self.assertEqual(e.msg.interpolate(), '"None" is not one of 1, 2')
|
||||
|
||||
class TestSchemaType(unittest.TestCase):
|
||||
def _makeOne(self, *arg, **kw):
|
||||
from colander import SchemaType
|
||||
return SchemaType(*arg, **kw)
|
||||
|
||||
def test_flatten(self):
|
||||
node = DummySchemaNode(None, name='node')
|
||||
typ = self._makeOne()
|
||||
result = typ.flatten(node, 'appstruct')
|
||||
self.assertEqual(result, {'node':'appstruct'})
|
||||
|
||||
|
||||
class TestMapping(unittest.TestCase):
|
||||
def _makeOne(self, *arg, **kw):
|
||||
@@ -439,6 +450,19 @@ class TestMapping(unittest.TestCase):
|
||||
result = typ.serialize(node, null)
|
||||
self.assertEqual(result, {'a':null})
|
||||
|
||||
def test_flatten(self):
|
||||
node = DummySchemaNode(None, name='node')
|
||||
int1 = DummyType()
|
||||
int2 = DummyType()
|
||||
node.children = [
|
||||
DummySchemaNode(int1, name='a'),
|
||||
DummySchemaNode(int2, name='b'),
|
||||
]
|
||||
typ = self._makeOne()
|
||||
result = typ.flatten(node, {'a':1, 'b':2})
|
||||
self.assertEqual(result,
|
||||
{'node': {'a': 1, 'b': 2}, 'node.appstruct': 2})
|
||||
|
||||
class TestTuple(unittest.TestCase):
|
||||
def _makeOne(self):
|
||||
from colander import Tuple
|
||||
@@ -550,6 +574,18 @@ class TestTuple(unittest.TestCase):
|
||||
self.assertEqual(e.msg, None)
|
||||
self.assertEqual(len(e.children), 2)
|
||||
|
||||
def test_flatten(self):
|
||||
node = DummySchemaNode(None, name='node')
|
||||
int1 = DummyType()
|
||||
int2 = DummyType()
|
||||
node.children = [
|
||||
DummySchemaNode(int1, name='a'),
|
||||
DummySchemaNode(int2, name='b'),
|
||||
]
|
||||
typ = self._makeOne()
|
||||
result = typ.flatten(node, (1, 2))
|
||||
self.assertEqual(result, {'node': (1, 2), 'node.appstruct': 2})
|
||||
|
||||
class TestSequence(unittest.TestCase):
|
||||
def _makeOne(self, **kw):
|
||||
from colander import Sequence
|
||||
@@ -646,6 +682,24 @@ class TestSequence(unittest.TestCase):
|
||||
self.assertEqual(e.msg, None)
|
||||
self.assertEqual(len(e.children), 2)
|
||||
|
||||
def test_flatten(self):
|
||||
node = DummySchemaNode(None, name='node')
|
||||
int1 = DummyType()
|
||||
int2 = DummyType()
|
||||
node.children = [
|
||||
DummySchemaNode(int1, name='a'),
|
||||
DummySchemaNode(int2, name='b'),
|
||||
]
|
||||
typ = self._makeOne()
|
||||
result = typ.flatten(node, [1, 2])
|
||||
self.assertEqual(
|
||||
result,
|
||||
{'node': [1, 2],
|
||||
'node.0': 1,
|
||||
'node.0.appstruct': 1,
|
||||
'node.1.appstruct': 2,
|
||||
'node.1': 2})
|
||||
|
||||
class TestString(unittest.TestCase):
|
||||
def _makeOne(self, encoding=None):
|
||||
from colander import String
|
||||
@@ -1617,20 +1671,73 @@ class TestFunctional(object):
|
||||
self.assertEqual(result['seq2'],
|
||||
[{'key':1, 'key2':2}, {'key':3, 'key2':4}])
|
||||
self.assertEqual(result['tup'], (1, 's'))
|
||||
|
||||
def test_flatten_ok(self):
|
||||
import colander
|
||||
appstruct = {
|
||||
'int':10,
|
||||
'ob':colander.tests,
|
||||
'seq':[(1, 's'),(2, 's'), (3, 's'), (4, 's')],
|
||||
'seq2':[{'key':1, 'key2':2}, {'key':3, 'key2':4}],
|
||||
'tup':(1, 's'),
|
||||
}
|
||||
schema = self._makeSchema()
|
||||
result = schema.flatten(appstruct)
|
||||
|
||||
expected = {
|
||||
'schema.seq.2.tup.tupstring': 's',
|
||||
'schema.seq2.0.mapping.key2': 2,
|
||||
'schema.seq.0': (1, 's'),
|
||||
'schema.seq.1': (2, 's'),
|
||||
'schema.seq.2': (3, 's'),
|
||||
'schema.seq.3': (4, 's'),
|
||||
'schema.seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')],
|
||||
'schema.ob': colander.tests,
|
||||
'schema.seq2.1.mapping.key2': 4,
|
||||
'schema.seq.0.tup': (1, 's'),
|
||||
'schema.seq.1.tup': (2, 's'),
|
||||
'schema.seq2.0.mapping': {'key2': 2, 'key': 1},
|
||||
'schema.seq2.1.mapping': {'key2': 4, 'key': 3},
|
||||
'schema.seq.1.tup.tupstring': 's',
|
||||
'schema.seq2.0.mapping.key': 1,
|
||||
'schema.seq.1.tup.tupint': 2,
|
||||
'schema.tup': (1, 's'),
|
||||
'schema.seq.3.tup': (4, 's'),
|
||||
'schema.seq.0.tup.tupstring': 's',
|
||||
'schema.seq.2.tup': (3, 's'),
|
||||
'schema.seq.3.tup.tupstring': 's',
|
||||
'schema.seq.3.tup.tupint': 4,
|
||||
'schema.seq2.1.mapping.key': 3,
|
||||
'schema.int': 10,
|
||||
'schema.seq2.0': {'key2': 2, 'key': 1},
|
||||
'schema.seq.0.tup.tupint': 1,
|
||||
'schema.tup.tupint': 1,
|
||||
'schema.tup.tupstring': 's',
|
||||
'schema.seq.2.tup.tupint': 3,
|
||||
'schema.seq2': [{'key2': 2, 'key': 1}, {'key2': 4, 'key': 3}],
|
||||
'schema.seq2.1': {'key2': 4, 'key': 3},
|
||||
'schema': {'int': 10,
|
||||
'seq2': [{'key2': 2, 'key': 1}, {'key2': 4, 'key': 3}],
|
||||
'tup': (1, 's'),
|
||||
'ob':colander.tests,
|
||||
'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')]}}
|
||||
|
||||
for k, v in result.items():
|
||||
self.assertEqual(v, expected[k])
|
||||
|
||||
def test_invalid_asdict(self):
|
||||
expected = {
|
||||
'int': '20 is greater than maximum value 10',
|
||||
'ob': 'The dotted name "no.way.this.exists" cannot be imported',
|
||||
'seq.0.0': '"q" is not a number',
|
||||
'seq.1.0': '"w" is not a number',
|
||||
'seq.2.0': '"e" is not a number',
|
||||
'seq.3.0': '"r" is not a number',
|
||||
'seq2.0.key': '"t" is not a number',
|
||||
'seq2.0.key2': '"y" is not a number',
|
||||
'seq2.1.key': '"u" is not a number',
|
||||
'seq2.1.key2': '"i" is not a number',
|
||||
'tup.0': '"s" is not a number'}
|
||||
'schema.int': '20 is greater than maximum value 10',
|
||||
'schema.ob': 'The dotted name "no.way.this.exists" cannot be imported',
|
||||
'schema.seq.0.0': '"q" is not a number',
|
||||
'schema.seq.1.0': '"w" is not a number',
|
||||
'schema.seq.2.0': '"e" is not a number',
|
||||
'schema.seq.3.0': '"r" is not a number',
|
||||
'schema.seq2.0.key': '"t" is not a number',
|
||||
'schema.seq2.0.key2': '"y" is not a number',
|
||||
'schema.seq2.1.key': '"u" is not a number',
|
||||
'schema.seq2.1.key2': '"i" is not a number',
|
||||
'schema.tup.0': '"s" is not a number'}
|
||||
data = {
|
||||
'int':'20',
|
||||
'ob':'no.way.this.exists',
|
||||
@@ -1701,7 +1808,8 @@ class TestImperative(unittest.TestCase, TestFunctional):
|
||||
ob,
|
||||
tup,
|
||||
seq,
|
||||
seq2)
|
||||
seq2,
|
||||
name='schema')
|
||||
|
||||
return schema
|
||||
|
||||
@@ -1721,7 +1829,7 @@ class TestDeclarative(unittest.TestCase, TestFunctional):
|
||||
key2 = colander.SchemaNode(colander.Int())
|
||||
|
||||
class SequenceOne(colander.SequenceSchema):
|
||||
tuple = TupleSchema()
|
||||
tup = TupleSchema()
|
||||
|
||||
class SequenceTwo(colander.SequenceSchema):
|
||||
mapping = MappingSchema()
|
||||
@@ -1734,7 +1842,7 @@ class TestDeclarative(unittest.TestCase, TestFunctional):
|
||||
tup = TupleSchema()
|
||||
seq2 = SequenceTwo()
|
||||
|
||||
schema = MainSchema()
|
||||
schema = MainSchema(name='schema')
|
||||
return schema
|
||||
|
||||
class Test_null(unittest.TestCase):
|
||||
@@ -1792,3 +1900,7 @@ class DummyType(object):
|
||||
def deserialize(self, node, value):
|
||||
return value
|
||||
|
||||
def flatten(self, node, appstruct, prefix=''):
|
||||
key = prefix + 'appstruct'
|
||||
return {key:appstruct}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user