- 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
|
- If ``default`` is deferred, the serialization default will be
|
||||||
assumed to be ``colander.null``.
|
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)
|
0.8 (2010/09/08)
|
||||||
-----------------
|
-----------------
|
||||||
|
|||||||
@@ -329,7 +329,15 @@ class OneOf(object):
|
|||||||
mapping={'val':value, 'choices':choices})
|
mapping={'val':value, 'choices':choices})
|
||||||
raise Invalid(node, err)
|
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.
|
""" A type which represents a mapping of names to nodes.
|
||||||
|
|
||||||
The subnodes of the :class:`colander.SchemaNode` that wraps
|
The subnodes of the :class:`colander.SchemaNode` that wraps
|
||||||
@@ -447,6 +455,19 @@ class Mapping(object):
|
|||||||
|
|
||||||
return self._impl(node, cstruct, callback)
|
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):
|
class Positional(object):
|
||||||
"""
|
"""
|
||||||
Marker abstract base class meaning 'this type has children which
|
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.
|
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.
|
""" A type which represents a fixed-length sequence of nodes.
|
||||||
|
|
||||||
The subnodes of the :class:`colander.SchemaNode` that wraps
|
The subnodes of the :class:`colander.SchemaNode` that wraps
|
||||||
@@ -523,7 +544,19 @@ class Tuple(Positional):
|
|||||||
|
|
||||||
return self._impl(node, cstruct, callback)
|
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,
|
A type which represents a variable-length sequence of nodes,
|
||||||
all of which must be of the same type.
|
all of which must be of the same type.
|
||||||
@@ -639,9 +672,26 @@ class Sequence(Positional):
|
|||||||
|
|
||||||
return self._impl(node, cstruct, callback, accept_scalar)
|
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
|
Seq = Sequence
|
||||||
|
|
||||||
class String(object):
|
class String(SchemaType):
|
||||||
""" A type representing a Unicode string.
|
""" A type representing a Unicode string.
|
||||||
|
|
||||||
This type constructor accepts one argument:
|
This type constructor accepts one argument:
|
||||||
@@ -740,10 +790,9 @@ class String(object):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
Str = String
|
Str = String
|
||||||
|
|
||||||
class Number(object):
|
class Number(SchemaType):
|
||||||
""" Abstract base class for float, int, decimal """
|
""" Abstract base class for float, int, decimal """
|
||||||
|
|
||||||
num = None
|
num = None
|
||||||
@@ -771,7 +820,6 @@ class Number(object):
|
|||||||
mapping={'val':cstruct})
|
mapping={'val':cstruct})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Integer(Number):
|
class Integer(Number):
|
||||||
""" A type representing an integer.
|
""" A type representing an integer.
|
||||||
|
|
||||||
@@ -812,7 +860,7 @@ class Decimal(Number):
|
|||||||
def num(self, val):
|
def num(self, val):
|
||||||
return decimal.Decimal(str(val))
|
return decimal.Decimal(str(val))
|
||||||
|
|
||||||
class Boolean(object):
|
class Boolean(SchemaType):
|
||||||
""" A type representing a boolean object.
|
""" A type representing a boolean object.
|
||||||
|
|
||||||
During deserialization, a value in the set (``false``, ``0``) will
|
During deserialization, a value in the set (``false``, ``0``) will
|
||||||
@@ -852,7 +900,7 @@ class Boolean(object):
|
|||||||
|
|
||||||
Bool = Boolean
|
Bool = Boolean
|
||||||
|
|
||||||
class GlobalObject(object):
|
class GlobalObject(SchemaType):
|
||||||
""" A type representing an importable Python object. This type
|
""" A type representing an importable Python object. This type
|
||||||
serializes 'global' Python objects (objects which can be imported)
|
serializes 'global' Python objects (objects which can be imported)
|
||||||
to dotted Python names.
|
to dotted Python names.
|
||||||
@@ -976,8 +1024,7 @@ class GlobalObject(object):
|
|||||||
_('The dotted name "${name}" cannot be imported',
|
_('The dotted name "${name}" cannot be imported',
|
||||||
mapping={'name':cstruct}))
|
mapping={'name':cstruct}))
|
||||||
|
|
||||||
|
class DateTime(SchemaType):
|
||||||
class DateTime(object):
|
|
||||||
""" A type representing a Python ``datetime.datetime`` object.
|
""" A type representing a Python ``datetime.datetime`` object.
|
||||||
|
|
||||||
This type serializes python ``datetime.datetime`` objects to a
|
This type serializes python ``datetime.datetime`` objects to a
|
||||||
@@ -1058,7 +1105,7 @@ class DateTime(object):
|
|||||||
mapping={'val':cstruct, 'err':e}))
|
mapping={'val':cstruct, 'err':e}))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
class Date(object):
|
class Date(SchemaType):
|
||||||
""" A type representing a Python ``datetime.date`` object.
|
""" A type representing a Python ``datetime.date`` object.
|
||||||
|
|
||||||
This type serializes python ``datetime.date`` objects to a
|
This type serializes python ``datetime.date`` objects to a
|
||||||
@@ -1243,6 +1290,20 @@ class SchemaNode(object):
|
|||||||
cstruct = self.typ.serialize(self, appstruct)
|
cstruct = self.typ.serialize(self, appstruct)
|
||||||
return cstruct
|
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):
|
def deserialize(self, cstruct=null):
|
||||||
""" Deserialize and validate the :term:`cstruct` into an
|
""" Deserialize and validate the :term:`cstruct` into an
|
||||||
:term:`appstruct` based on the schema, and return the
|
:term:`appstruct` based on the schema, and return the
|
||||||
|
|||||||
@@ -319,6 +319,17 @@ class TestOneOf(unittest.TestCase):
|
|||||||
e = invalid_exc(validator, None, None)
|
e = invalid_exc(validator, None, None)
|
||||||
self.assertEqual(e.msg.interpolate(), '"None" is not one of 1, 2')
|
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):
|
class TestMapping(unittest.TestCase):
|
||||||
def _makeOne(self, *arg, **kw):
|
def _makeOne(self, *arg, **kw):
|
||||||
@@ -439,6 +450,19 @@ class TestMapping(unittest.TestCase):
|
|||||||
result = typ.serialize(node, null)
|
result = typ.serialize(node, null)
|
||||||
self.assertEqual(result, {'a':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):
|
class TestTuple(unittest.TestCase):
|
||||||
def _makeOne(self):
|
def _makeOne(self):
|
||||||
from colander import Tuple
|
from colander import Tuple
|
||||||
@@ -550,6 +574,18 @@ class TestTuple(unittest.TestCase):
|
|||||||
self.assertEqual(e.msg, None)
|
self.assertEqual(e.msg, None)
|
||||||
self.assertEqual(len(e.children), 2)
|
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):
|
class TestSequence(unittest.TestCase):
|
||||||
def _makeOne(self, **kw):
|
def _makeOne(self, **kw):
|
||||||
from colander import Sequence
|
from colander import Sequence
|
||||||
@@ -646,6 +682,24 @@ class TestSequence(unittest.TestCase):
|
|||||||
self.assertEqual(e.msg, None)
|
self.assertEqual(e.msg, None)
|
||||||
self.assertEqual(len(e.children), 2)
|
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):
|
class TestString(unittest.TestCase):
|
||||||
def _makeOne(self, encoding=None):
|
def _makeOne(self, encoding=None):
|
||||||
from colander import String
|
from colander import String
|
||||||
@@ -1618,19 +1672,72 @@ class TestFunctional(object):
|
|||||||
[{'key':1, 'key2':2}, {'key':3, 'key2':4}])
|
[{'key':1, 'key2':2}, {'key':3, 'key2':4}])
|
||||||
self.assertEqual(result['tup'], (1, 's'))
|
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):
|
def test_invalid_asdict(self):
|
||||||
expected = {
|
expected = {
|
||||||
'int': '20 is greater than maximum value 10',
|
'schema.int': '20 is greater than maximum value 10',
|
||||||
'ob': 'The dotted name "no.way.this.exists" cannot be imported',
|
'schema.ob': 'The dotted name "no.way.this.exists" cannot be imported',
|
||||||
'seq.0.0': '"q" is not a number',
|
'schema.seq.0.0': '"q" is not a number',
|
||||||
'seq.1.0': '"w" is not a number',
|
'schema.seq.1.0': '"w" is not a number',
|
||||||
'seq.2.0': '"e" is not a number',
|
'schema.seq.2.0': '"e" is not a number',
|
||||||
'seq.3.0': '"r" is not a number',
|
'schema.seq.3.0': '"r" is not a number',
|
||||||
'seq2.0.key': '"t" is not a number',
|
'schema.seq2.0.key': '"t" is not a number',
|
||||||
'seq2.0.key2': '"y" is not a number',
|
'schema.seq2.0.key2': '"y" is not a number',
|
||||||
'seq2.1.key': '"u" is not a number',
|
'schema.seq2.1.key': '"u" is not a number',
|
||||||
'seq2.1.key2': '"i" is not a number',
|
'schema.seq2.1.key2': '"i" is not a number',
|
||||||
'tup.0': '"s" is not a number'}
|
'schema.tup.0': '"s" is not a number'}
|
||||||
data = {
|
data = {
|
||||||
'int':'20',
|
'int':'20',
|
||||||
'ob':'no.way.this.exists',
|
'ob':'no.way.this.exists',
|
||||||
@@ -1701,7 +1808,8 @@ class TestImperative(unittest.TestCase, TestFunctional):
|
|||||||
ob,
|
ob,
|
||||||
tup,
|
tup,
|
||||||
seq,
|
seq,
|
||||||
seq2)
|
seq2,
|
||||||
|
name='schema')
|
||||||
|
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
@@ -1721,7 +1829,7 @@ class TestDeclarative(unittest.TestCase, TestFunctional):
|
|||||||
key2 = colander.SchemaNode(colander.Int())
|
key2 = colander.SchemaNode(colander.Int())
|
||||||
|
|
||||||
class SequenceOne(colander.SequenceSchema):
|
class SequenceOne(colander.SequenceSchema):
|
||||||
tuple = TupleSchema()
|
tup = TupleSchema()
|
||||||
|
|
||||||
class SequenceTwo(colander.SequenceSchema):
|
class SequenceTwo(colander.SequenceSchema):
|
||||||
mapping = MappingSchema()
|
mapping = MappingSchema()
|
||||||
@@ -1734,7 +1842,7 @@ class TestDeclarative(unittest.TestCase, TestFunctional):
|
|||||||
tup = TupleSchema()
|
tup = TupleSchema()
|
||||||
seq2 = SequenceTwo()
|
seq2 = SequenceTwo()
|
||||||
|
|
||||||
schema = MainSchema()
|
schema = MainSchema(name='schema')
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
class Test_null(unittest.TestCase):
|
class Test_null(unittest.TestCase):
|
||||||
@@ -1792,3 +1900,7 @@ class DummyType(object):
|
|||||||
def deserialize(self, node, value):
|
def deserialize(self, node, value):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def flatten(self, node, appstruct, prefix=''):
|
||||||
|
key = prefix + 'appstruct'
|
||||||
|
return {key:appstruct}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user