Skip to content

Commit 197d1f2

Browse files
committed
fix: return empty flat map when container appstruct is null
Mapping/Sequence/Tuple flatten assumed a real container when a missing=drop child was absent from the appstruct, so SchemaNode.flatten raised TypeError/AttributeError on colander.null (issue #299).
1 parent 4557c01 commit 197d1f2

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

src/colander/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,8 @@ def callback(subnode, subcstruct):
957957

958958
def flatten(self, node, appstruct, prefix='', listitem=False):
959959
result = {}
960+
if appstruct is null:
961+
return result
960962
if listitem:
961963
selfprefix = prefix
962964
else:
@@ -1096,6 +1098,8 @@ def callback(subnode, subval):
10961098

10971099
def flatten(self, node, appstruct, prefix='', listitem=False):
10981100
result = {}
1101+
if appstruct is null:
1102+
return result
10991103
if listitem:
11001104
selfprefix = prefix
11011105
else:
@@ -1362,6 +1366,8 @@ def callback(subnode, subcstruct):
13621366

13631367
def flatten(self, node, appstruct, prefix='', listitem=False):
13641368
result = {}
1369+
if appstruct is null:
1370+
return result
13651371
if listitem:
13661372
selfprefix = prefix
13671373
else:

tests/test_colander.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,14 @@ def test_flatten(self):
11241124
result = typ.flatten(node, {'a': 1, 'b': 2})
11251125
self.assertEqual(result, {'node.appstruct': 2})
11261126

1127+
def test_flatten_null(self):
1128+
from colander import null
1129+
1130+
node = DummySchemaNode(None, name='node')
1131+
node.children = [DummySchemaNode(DummyType(), name='a')]
1132+
typ = self._makeOne()
1133+
self.assertEqual(typ.flatten(node, null), {})
1134+
11271135
def test_flatten_listitem(self):
11281136
node = DummySchemaNode(None, name='node')
11291137
int1 = DummyType()
@@ -1371,6 +1379,17 @@ def test_flatten(self):
13711379
result = typ.flatten(node, (1, 2))
13721380
self.assertEqual(result, {'node.appstruct': 2})
13731381

1382+
def test_flatten_null(self):
1383+
from colander import null
1384+
1385+
node = DummySchemaNode(None, name='node')
1386+
node.children = [
1387+
DummySchemaNode(DummyType(), name='a'),
1388+
DummySchemaNode(DummyType(), name='b'),
1389+
]
1390+
typ = self._makeOne()
1391+
self.assertEqual(typ.flatten(node, null), {})
1392+
13741393
def test_flatten_listitem(self):
13751394
node = DummySchemaNode(None, name='node')
13761395
int1 = DummyType()
@@ -1732,6 +1751,14 @@ def test_flatten(self):
17321751
result = typ.flatten(node, [1, 2])
17331752
self.assertEqual(result, {'node.0': 1, 'node.1': 2})
17341753

1754+
def test_flatten_null(self):
1755+
from colander import null
1756+
1757+
node = DummySchemaNode(None, name='node')
1758+
node.children = [DummySchemaNode(DummyType(), name='foo')]
1759+
typ = self._makeOne()
1760+
self.assertEqual(typ.flatten(node, null), {})
1761+
17351762
def test_flatten_with_integer(self):
17361763
from colander import Integer
17371764

@@ -4080,6 +4107,30 @@ class MySchema(colander.Schema):
40804107
result = node.deserialize(expected)
40814108
self.assertEqual(result, expected)
40824109

4110+
def test_flatten_after_deserialize_drop_containers(self):
4111+
# missing=drop omits the key from the appstruct; flatten must not
4112+
# raise when Mapping walks that absent child as colander.null.
4113+
class Seq(colander.SequenceSchema):
4114+
item = colander.SchemaNode(colander.String())
4115+
4116+
class Tup(colander.TupleSchema):
4117+
x = colander.SchemaNode(colander.Int())
4118+
y = colander.SchemaNode(colander.Int())
4119+
4120+
class Inner(colander.MappingSchema):
4121+
z = colander.SchemaNode(colander.String())
4122+
4123+
class MySchema(colander.Schema):
4124+
title = colander.SchemaNode(colander.String())
4125+
items = Seq(missing=colander.drop)
4126+
point = Tup(missing=colander.drop)
4127+
inner = Inner(missing=colander.drop)
4128+
4129+
node = MySchema()
4130+
appstruct = node.deserialize({'title': 't'})
4131+
self.assertEqual(appstruct, {'title': 't'})
4132+
self.assertEqual(node.flatten(appstruct), {'title': 't'})
4133+
40834134
def test_serialize_drop_default(self):
40844135
class MySchema(colander.Schema):
40854136
a = colander.SchemaNode(colander.String())

0 commit comments

Comments
 (0)