replace schema_order with insert_before. closes #58

This commit is contained in:
Chris McDonough
2012-08-31 01:56:15 -04:00
parent 2fed561c71
commit 9962e0375d
4 changed files with 51 additions and 27 deletions

View File

@@ -16,18 +16,17 @@ Features
- Add an ``insert`` method to SchemaNode objects. - Add an ``insert`` method to SchemaNode objects.
- Add an ``insert_before`` method to SchemaNode objects.
- Better class-based mapping schema inheritance model. - Better class-based mapping schema inheritance model.
* A node declared in a subclass of a mapping schema superclass now * A node declared in a subclass of a mapping schema superclass now
overrides any node with the same name inherited from any superclass. overrides any node with the same name inherited from any superclass.
Previously, it just repeated and didn't override. Previously, it just repeated and didn't override.
* A ``schema_order`` attribute may be passed to SchemaNode constructor. * An ``insert_before`` keyword argument may be passed to a SchemaNode
This is an integer which defines the position in a parent node's child constructor. This is an string naming a node in a superclass which this
ordering. node should be placed before to influence schema ordering.
* ``colander.FIRST`` and ``colander.LAST`` constants are available for
passing in as ``schema_order``.
Backwards Incompatibilities Backwards Incompatibilities
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -1696,6 +1696,15 @@ class SchemaNode(object):
a SchemaNode.""" a SchemaNode."""
self.children.insert(index, node) self.children.insert(index, node)
def add_before(self, name, node):
""" Insert a subnode into the position before the node named ``name``
"""
for pos, sub in enumerate(self.children[:]):
if sub.name == name:
self.insert(pos, node)
return
raise KeyError('No such node named %s' % name)
def get(self, name, default=None): def get(self, name, default=None):
""" Return the subnode associated with ``name`` or ``default`` if no """ Return the subnode associated with ``name`` or ``default`` if no
such node exists.""" such node exists."""
@@ -1804,12 +1813,12 @@ def _Schema__new__(cls, *args, **kw):
typ = cls.schema_type() typ = cls.schema_type()
node.__init__(typ, *args, **kw) node.__init__(typ, *args, **kw)
for n in cls.nodes: for n in cls.nodes:
order = getattr(n, 'schema_order', None) insert_before = getattr(n, 'insert_before', None)
exists = node.get(n.name, _marker) is not _marker exists = node.get(n.name, _marker) is not _marker
# use exists for microspeed; we could just call __setitem__ # use exists for microspeed; we could just call __setitem__
# exclusively, but it does an enumeration that's unnecessary in the # exclusively, but it does an enumeration that's unnecessary in the
# common (nonexisting) case (.add is faster) # common (nonexisting) case (.add is faster)
if order is None: if insert_before is None:
if exists: if exists:
node[n.name] = n node[n.name] = n
else: else:
@@ -1817,7 +1826,7 @@ def _Schema__new__(cls, *args, **kw):
else: else:
if exists: if exists:
del node[n.name] del node[n.name]
node.insert(order, n) node.add_before(insert_before, n)
return node return node
Schema = _SchemaMeta('Schema', (object,), Schema = _SchemaMeta('Schema', (object,),

View File

@@ -2276,7 +2276,7 @@ class TestMappingSchemaInheritance(unittest.TestCase):
) )
iwannacomefirst1_node = colander.SchemaNode( iwannacomefirst1_node = colander.SchemaNode(
colander.String(), colander.String(),
schema_order=colander.FIRST insert_before='rank',
) )
another_node = colander.SchemaNode( another_node = colander.SchemaNode(
colander.String(), colander.String(),
@@ -2289,7 +2289,7 @@ class TestMappingSchemaInheritance(unittest.TestCase):
) )
serial2_node = colander.SchemaNode( serial2_node = colander.SchemaNode(
colander.Bool(), colander.Bool(),
schema_order=colander.LAST, insert_before='name',
) )
class Friend(colander.Schema): class Friend(colander.Schema):
@@ -2311,9 +2311,9 @@ class TestMappingSchemaInheritance(unittest.TestCase):
[ [
iwannacomefirst2_node, iwannacomefirst2_node,
rank_node, rank_node,
serial2_node,
name_node, name_node,
another_node, another_node,
serial2_node
] ]
) )
@@ -2355,6 +2355,19 @@ class TestMappingSchemaInheritance(unittest.TestCase):
[a2_node, b3_node, c2_node, d3_node] [a2_node, b3_node, c2_node, d3_node]
) )
def test_insert_before_failure(self):
import colander
a_node = colander.SchemaNode(
colander.Int(),
)
b_node = colander.SchemaNode(
colander.Int(),
insert_before='c'
)
class One(colander.Schema):
a = a_node
b = b_node
self.assertRaises(KeyError, One)
class TestDeferred(unittest.TestCase): class TestDeferred(unittest.TestCase):
def _makeOne(self, wrapped): def _makeOne(self, wrapped):

View File

@@ -537,7 +537,7 @@ One class-based schema can be inherited from another. For example:
class SpecialFriend(Friend): class SpecialFriend(Friend):
iwannacomefirst = colander.SchemaNode( iwannacomefirst = colander.SchemaNode(
colander.String(), colander.String(),
schema_order=colander.FIRST insert_before='rank',
) )
another = colander.SchemaNode( another = colander.SchemaNode(
colander.String(), colander.String(),
@@ -549,7 +549,7 @@ One class-based schema can be inherited from another. For example:
) )
friend = SuperSpecialFriend() friend = SuperSpecialFriend()
pprint.print([(x, x.typ) for x in friend.children]) pprint.pprint([(x, x.typ) for x in friend.children])
Here's what's printed when the above is run: Here's what's printed when the above is run:
@@ -619,22 +619,25 @@ The behavior of subclassing one mapping schema using another is as follows:
* A node declared in a subclass of a mapping schema overrides any node with * A node declared in a subclass of a mapping schema overrides any node with
the same name inherited from any superclass. The node remains at the child the same name inherited from any superclass. The node remains at the child
order of the superclass node unless the subclass node defines a order of the superclass node unless the subclass node defines an
``schema_order``. ``insert_before`` value.
* A node declared in a subclass of a mapping schema with a name that doesn't * A node declared in a subclass of a mapping schema with a name that doesn't
override any node in a superclass will be placed *after* all nodes defined override any node in a superclass will be placed *after* all nodes defined
in all superclasses unless the subclass node defines a ``schema_order``. in all superclasses unless the subclass node defines an ``insert_before``
You can think of it like this: nodes added in subclasses will *follow* value. You can think of it like this: nodes added in subclasses will
nodes added in superclasses. *follow* nodes added in superclasses.
A ``schema_order`` attribute may be passed to the SchemaNode constructor of An ``insert_before`` keyword argument may be passed to the SchemaNode
mapping schema child nodes. This is an integer which influences the position constructor of mapping schema child nodes. This is a string which influences
in its mapping schema's child ordering. ``colander.FIRST`` and the node's position in its mapping schema. The node will be inserted into
``colander.LAST`` constants are available for passing in as ``schema_order``. the mapping schema before the node named by ``insert_before``. An
The former tries to place the node in the very first position. The latter, ``insert_before`` value must match the name of a schema node in a superclass
the very last. If ``schema_order`` is any other integer, the system will or it must match the name of a schema node already defined in the class; it
attempt to place the node at the integer position in the ordering. cannot name a schema node in a subclass, and it cannot name a schema node in
the same class that hasn't already been defined. If an ``insert_before`` is
provided that doesn't match any existing node name, a :exc:`KeyError` is
raised.
Defining A Schema Imperatively Defining A Schema Imperatively
------------------------------ ------------------------------