~~~~~~~~

- Calling ``bind`` on a schema node e.g. ``cloned_node = somenode.bind(a=1,
  b=2)`` on a schema node now results in the cloned node having a
  ``bindings`` attribute of the value ``{'a':1, 'b':2}``.

- It is no longer necessary to pass a ``typ`` argument to a SchemaNode
  constructor if the node class has a ``__schema_type__`` callable as a class
  attribute which, when called with no arguments, returns a schema type.
  This callable will be called to obtain the schema type if a ``typ`` is not
  supplied to the constructor.  The default ``SchemaNode`` object's
  ``__schema_type__`` callable raises a ``NotImplementedError`` when it is
  called.

- SchemaNode now has a ``raise_invalid`` method which accepts a message and
  raises a colander.Invalid exception using ``self`` as the node and the
  message as its message.

- It is now possible and advisable to subclass ``SchemaNode`` in order to
  create a bundle of default node behavior.  The subclass can define the
  following methods and attributes: ``preparer``, ``validator``, ``default``,
  ``missing``, ``name``, ``title``, ``description``, ``widget``, and
  ``after_bind``.  For example, the older, more imperative style that
  looked like this still works::

     from colander import SchemaNode

     ranged_int = colander.SchemaNode(
         validator=colander.Range(0, 10),
         default = 10,
         title='Ranged Int'
         )

  But you can alternately now do something like::

     from colander import SchemaNode

     class RangedIntSchemaNode(SchemaNode):
         validator = colander.range(0, 10)
         default = 10
         title = 'Ranged Int'

     ranged_int = RangedInt()

   Values that are expected to be callables can be methods of the schemanode
   subclass instead of plain attributes::

     from colander import SchemaNode

     class RangedIntSchemaNode(SchemaNode):
         default = 10
         title = 'Ranged Int'

         def validator(self, node, cstruct):
            if not 0 < cstruct < 10:
                raise colander.Invalid(node, 'Must be between 0 and 10')

     ranged_int = RangedInt()

   When implementing a method value that expects ``node``, ``node`` must be
   provided in the call signature, even though ``node`` will almost always be
   the same as ``self``.  This is because Colander simply treats the method
   as another kind of callable, be it a method, or a function, or an instance
   that has a ``__call__`` method.  It doesn't care that it happens to be a
   method of ``self``, and it needs to support callables that are not
   methods, so it sends ``node`` in regardless.

   Normal inheritance rules apply to class attributes and methods defined in
   a schemanode subclass.  If your schemanode subclass inherits from another
   schemanode class, your schemanode subclass' methods and class attributes
   will override the superclass' methods and class attributes.

   Method values that need to be deferred for binding cannot currently be
   implemented as ``colander.deferred`` callables.  For example this will
   *not* work::

     from colander import SchemaNode

     class RangedIntSchemaNode(SchemaNode):
         default = 10
         title = 'Ranged Int'

         @colander.deferred
         def validator(self, node, kw):
            request = kw['request']
            def avalidator(node, cstruct):
                if not 0 < cstruct < 10:
                    if request.user != 'admin':
                        raise colander.Invalid(node, 'Must be between 0 and 10')
            return avalidator

     ranged_int = RangedInt()
     bound_ranged_int = ranged_int.bind(request=request)

   This will result in::

        TypeError: avalidator() takes exactly 3 arguments (2 given)

   Instead of trying to defer methods via a decorator, you can instead use
   the ``bindings`` attribute of ``self`` to obtain access to the bind
   parameters within values that are methody::

     from colander import SchemaNode

     class RangedIntSchemaNode(SchemaNode):
         default = 10
         title = 'Ranged Int'

         def validator(self, node, cstruct):
            request = self.bindings['request']
            if not 0 < cstruct < 10:
                if request.user != 'admin':
                    raise colander.Invalid(node, 'Must be between 0 and 10')

     ranged_int = RangedInt()
     bound_range_int = ranged_int.bind(request=request)

   You can use ``after_bind`` to set attributes of the schemanode that rely
   on binding variables, such as ``missing`` and ``default``::

     from colander import SchemaNode

     class RangedIntSchemaNode(SchemaNode):
         default = 10
         title = 'Ranged Int'

         def validator(self, node, cstruct):
            request = self.bindings['request']
            if not 0 < cstruct < 10:
                if request.user != 'admin':
                    raise colander.Invalid(node, 'Must be between 0 and 10')

         def after_bind(self, node, kw):
             self.request = kw['request']
             self.default = self.request.user.id

   Non-method values can still be implemented as ``colander.deferred``
   however::

     from colander import SchemaNode

     def _missing(node, kw):
         request = kw['request']
         if request.user.name == 'admin':
             return 10
          return 20

     class RangedIntSchemaNode(SchemaNode):
         default = 10
         title = 'Ranged Int'
         missing = colander.deferred(_missing)

     ranged_int = RangedInt()

   You can override the default values of a schemanode subclass in its
   constructor::

     from colander import SchemaNode

     class RangedIntSchemaNode(SchemaNode):
         default = 10
         title = 'Ranged Int'
         validator = colander.Range(0, 10)

     ranged_int = RangedInt(validator=colander.Range(0, 20))

   In the above example, the validation will be done on 0-20, not 0-10.

   If your schema node names conflict with schema value attribute names, you
   can work around it with the ``name`` argument to the schema node::

     from colander import SchemaNode, Schema

     class TitleNode(SchemaNode):
         validator = colander.range(0, 10)
         default = 10

     class SomeSchema(Schema):
         title = 'Some Schema'
         thisnamewontmatter = TitleNode(name='title')

Backwards Incompatibilities
~~~~~~~~~~~~~~~~~~~~~~~~~~~

- Passing non-SchemaNode derivative instances as ``*children`` into a
  SchemaNode constructor is no longer supported.  Symptom: ``AttributeError:
  name`` when constructing a SchemaNode.
This commit is contained in:
Chris McDonough
2012-10-07 17:25:08 -04:00
parent f180c6a191
commit 84004d0286
4 changed files with 466 additions and 88 deletions

View File

@@ -14,6 +14,192 @@ Features
- Add Python 3.3 to tox configuration and use newer tox testing regime
(setup.py dev).
- Calling ``bind`` on a schema node e.g. ``cloned_node = somenode.bind(a=1,
b=2)`` on a schema node now results in the cloned node having a
``bindings`` attribute of the value ``{'a':1, 'b':2}``.
- It is no longer necessary to pass a ``typ`` argument to a SchemaNode
constructor if the node class has a ``__schema_type__`` callable as a class
attribute which, when called with no arguments, returns a schema type.
This callable will be called to obtain the schema type if a ``typ`` is not
supplied to the constructor. The default ``SchemaNode`` object's
``__schema_type__`` callable raises a ``NotImplementedError`` when it is
called.
- SchemaNode now has a ``raise_invalid`` method which accepts a message and
raises a colander.Invalid exception using ``self`` as the node and the
message as its message.
- It is now possible and advisable to subclass ``SchemaNode`` in order to
create a bundle of default node behavior. The subclass can define the
following methods and attributes: ``preparer``, ``validator``, ``default``,
``missing``, ``name``, ``title``, ``description``, ``widget``, and
``after_bind``. For example, the older, more imperative style that
looked like this still works::
from colander import SchemaNode
ranged_int = colander.SchemaNode(
validator=colander.Range(0, 10),
default = 10,
title='Ranged Int'
)
But you can alternately now do something like::
from colander import SchemaNode
class RangedIntSchemaNode(SchemaNode):
validator = colander.range(0, 10)
default = 10
title = 'Ranged Int'
ranged_int = RangedInt()
Values that are expected to be callables can be methods of the schemanode
subclass instead of plain attributes::
from colander import SchemaNode
class RangedIntSchemaNode(SchemaNode):
default = 10
title = 'Ranged Int'
def validator(self, node, cstruct):
if not 0 < cstruct < 10:
raise colander.Invalid(node, 'Must be between 0 and 10')
ranged_int = RangedInt()
When implementing a method value that expects ``node``, ``node`` must be
provided in the call signature, even though ``node`` will almost always be
the same as ``self``. This is because Colander simply treats the method
as another kind of callable, be it a method, or a function, or an instance
that has a ``__call__`` method. It doesn't care that it happens to be a
method of ``self``, and it needs to support callables that are not
methods, so it sends ``node`` in regardless.
Normal inheritance rules apply to class attributes and methods defined in
a schemanode subclass. If your schemanode subclass inherits from another
schemanode class, your schemanode subclass' methods and class attributes
will override the superclass' methods and class attributes.
Method values that need to be deferred for binding cannot currently be
implemented as ``colander.deferred`` callables. For example this will
*not* work::
from colander import SchemaNode
class RangedIntSchemaNode(SchemaNode):
default = 10
title = 'Ranged Int'
@colander.deferred
def validator(self, node, kw):
request = kw['request']
def avalidator(node, cstruct):
if not 0 < cstruct < 10:
if request.user != 'admin':
raise colander.Invalid(node, 'Must be between 0 and 10')
return avalidator
ranged_int = RangedInt()
bound_ranged_int = ranged_int.bind(request=request)
This will result in::
TypeError: avalidator() takes exactly 3 arguments (2 given)
Instead of trying to defer methods via a decorator, you can instead use
the ``bindings`` attribute of ``self`` to obtain access to the bind
parameters within values that are methody::
from colander import SchemaNode
class RangedIntSchemaNode(SchemaNode):
default = 10
title = 'Ranged Int'
def validator(self, node, cstruct):
request = self.bindings['request']
if not 0 < cstruct < 10:
if request.user != 'admin':
raise colander.Invalid(node, 'Must be between 0 and 10')
ranged_int = RangedInt()
bound_range_int = ranged_int.bind(request=request)
You can use ``after_bind`` to set attributes of the schemanode that rely
on binding variables, such as ``missing`` and ``default``::
from colander import SchemaNode
class RangedIntSchemaNode(SchemaNode):
default = 10
title = 'Ranged Int'
def validator(self, node, cstruct):
request = self.bindings['request']
if not 0 < cstruct < 10:
if request.user != 'admin':
raise colander.Invalid(node, 'Must be between 0 and 10')
def after_bind(self, node, kw):
self.request = kw['request']
self.default = self.request.user.id
Non-method values can still be implemented as ``colander.deferred``
however::
from colander import SchemaNode
def _missing(node, kw):
request = kw['request']
if request.user.name == 'admin':
return 10
return 20
class RangedIntSchemaNode(SchemaNode):
default = 10
title = 'Ranged Int'
missing = colander.deferred(_missing)
ranged_int = RangedInt()
You can override the default values of a schemanode subclass in its
constructor::
from colander import SchemaNode
class RangedIntSchemaNode(SchemaNode):
default = 10
title = 'Ranged Int'
validator = colander.Range(0, 10)
ranged_int = RangedInt(validator=colander.Range(0, 20))
In the above example, the validation will be done on 0-20, not 0-10.
If your schema node names conflict with schema value attribute names, you
can work around it with the ``name`` argument to the schema node::
from colander import SchemaNode, Schema
class TitleNode(SchemaNode):
validator = colander.range(0, 10)
default = 10
class SomeSchema(Schema):
title = 'Some Schema'
thisnamewontmatter = TitleNode(name='title')
Backwards Incompatibilities
~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Passing non-SchemaNode derivative instances as ``*children`` into a
SchemaNode constructor is no longer supported. Symptom: ``AttributeError:
name`` when constructing a SchemaNode.
0.9.9 (2012-09-24)
------------------

View File

@@ -1540,17 +1540,35 @@ class Time(SchemaType):
def timeparse(t, format):
return datetime.datetime(*time.strptime(t, format)[0:6]).time()
class SchemaNode(object):
def _add_node_children(node, children):
for n in children:
insert_before = getattr(n, 'insert_before', None)
exists = node.get(n.name, _marker) is not _marker
# use exists for microspeed; we could just call __setitem__
# exclusively, but it does an enumeration that's unnecessary in the
# common (nonexisting) case (.add is faster)
if insert_before is None:
if exists:
node[n.name] = n
else:
node.add(n)
else:
if exists:
del node[n.name]
node.add_before(insert_before, n)
class _SchemaNode(object):
"""
Fundamental building block of schemas.
The constructor accepts these positional arguments:
- ``typ`` (required): The 'type' for this node. It should be an
- ``typ``: The 'type' for this node. It should be an
instance of a class that implements the
:class:`colander.interfaces.Type` interface.
:class:`colander.interfaces.Type` interface. If ``typ`` is not passed,
it defaults to ``colander.Mapping()``.
- ``children``: a sequence of subnodes. If the subnodes of this
- ``*children``: a sequence of subnodes. If the subnodes of this
node are not known at construction time, they can later be added
via the ``add`` method.
@@ -1607,29 +1625,49 @@ class SchemaNode(object):
"""
_counter = itertools.count()
preparer = None
validator = None
default = null
missing = required
name = ''
raw_title = _marker
title = ''
description = ''
widget = None
after_bind = None
bindings = None
def __new__(cls, *arg, **kw):
inst = object.__new__(cls)
inst._order = next(cls._counter)
return inst
def __new__(cls, *args, **kw):
node = object.__new__(cls)
node._order = next(cls._counter)
node.children = []
_add_node_children(node, cls.__all_schema_nodes__)
return node
def __init__(self, typ, *children, **kw):
self.typ = typ
self.preparer = kw.pop('preparer', None)
self.validator = kw.pop('validator', None)
self.default = kw.pop('default', null)
self.missing = kw.pop('missing', required)
self.name = kw.pop('name', '')
self.raw_title = kw.pop('title', _marker)
if self.raw_title is _marker:
self.title = self.name.replace('_', ' ').title()
def __init__(self, *arg, **kw):
# bw compat forces us to treat first arg as type always
if arg:
self.typ = arg[0]
_add_node_children(self, arg[1:])
else:
self.title = self.raw_title
self.description = kw.pop('description', '')
self.widget = kw.pop('widget', None)
self.after_bind = kw.pop('after_bind', None)
self.typ = self.__schema_type__()
# bw compat forces us to manufacture a title if one is not supplied
title = kw.get('title', _marker)
if title is _marker:
name = kw.get('name', self.name)
kw['title'] = name.replace('_', ' ').title()
else:
kw['raw_title'] = title
self.__dict__.update(kw)
self.children = list(children)
@staticmethod
def __schema_type__():
raise NotImplementedError(
'Schema node construction without a typ argument or '
'a __schema_node__ callable present on the node class '
)
@property
def required(self):
@@ -1787,9 +1825,12 @@ class SchemaNode(object):
return cloned
def _bind(self, kw):
self.bindings = kw
for child in self.children:
child._bind(kw)
for k, v in self.__dict__.items():
names = dir(self)
for k in names:
v = getattr(self, k)
if isinstance(v, deferred):
v = v(self, kw)
setattr(self, k, v)
@@ -1857,77 +1898,66 @@ class SchemaNode(object):
self.name,
)
def raise_invalid(self, msg, node=None):
""" Raise a :exc:`colander.Invalid` exception with the message
``msg``. ``node``, if supplied, should be an instance of a
:class:`colander.SchemaNode`. If it is not supplied, ``node`` will
be this node. Example usage::
class CustomSchemaNode(SchemaNode):
def validator(self, node, cstruct):
if cstruct != 'the_right_thing':
self.raise_invalid('Not the right thing')
"""
if node is None:
node = self
raise Invalid(node, msg)
class _SchemaMeta(type):
def __init__(cls, name, bases, clsattrs):
nodes = []
for name, value in clsattrs.items():
if isinstance(value, SchemaNode):
if isinstance(value, _SchemaNode):
delattr(cls, name)
if not value.name:
value.name = name
if value.raw_title is _marker:
value.title = name.replace('_', ' ').title()
nodes.append((value._order, value))
cls.__schema_nodes__ = nodes
cls.__class_schema_nodes__ = nodes
# Combine all attrs from this class and its subclasses.
extended = []
for c in cls.__mro__:
extended.extend(getattr(c, '__schema_nodes__', []))
extended.extend(getattr(c, '__class_schema_nodes__', []))
# Sort the attrs to maintain the order as defined, and assign to the
# class.
extended.sort()
cls.nodes = [x[1] for x in extended]
cls.__all_schema_nodes__ = [x[1] for x in extended]
def _Schema__new__(cls, *args, **kw):
node = object.__new__(cls.node_type)
node.name = None
node._order = next(SchemaNode._counter)
typ = cls.schema_type()
node.__init__(typ, *args, **kw)
for n in cls.nodes:
insert_before = getattr(n, 'insert_before', None)
exists = node.get(n.name, _marker) is not _marker
# use exists for microspeed; we could just call __setitem__
# exclusively, but it does an enumeration that's unnecessary in the
# common (nonexisting) case (.add is faster)
if insert_before is None:
if exists:
node[n.name] = n
else:
node.add(n)
else:
if exists:
del node[n.name]
node.add_before(insert_before, n)
return node
Schema = _SchemaMeta('Schema', (object,),
dict(schema_type=Mapping,
node_type=SchemaNode,
__new__=_Schema__new__))
# metaclass spelling compatibility across Python 2 and Python 3
SchemaNode = _SchemaMeta(
'SchemaNode',
(_SchemaNode,),
{}
)
class Schema(SchemaNode):
__schema_type__ = Mapping
MappingSchema = Schema
class TupleSchema(SchemaNode):
__schema_type__ = Tuple
def _SequenceSchema__new__(cls, *args, **kw):
node = object.__new__(cls.node_type)
node.name = None
node._order = next(SchemaNode._counter)
typ = cls.schema_type()
node.__init__(typ, *args, **kw)
if not len(cls.nodes) == 1:
raise Invalid(node,
'Sequence schemas must have exactly one child node')
for n in cls.nodes:
node.add(n)
return node
class SequenceSchema(SchemaNode):
__schema_type__ = Sequence
SequenceSchema = _SchemaMeta('SequenceSchema', (object,),
dict(schema_type=Sequence,
node_type=SchemaNode,
__new__=_SequenceSchema__new__))
class TupleSchema(Schema):
schema_type = Tuple
def __init__(self, *args, **kw):
SchemaNode.__init__(self, *args, **kw)
if len(self.children) != 1:
raise Invalid(self,
'Sequence schemas must have exactly one child node')
class deferred(object):
""" A decorator which can be used to define deferred schema values

View File

@@ -5,7 +5,7 @@ PY3 = sys.version_info[0] == 3
if PY3: # pragma: no cover
string_types = str,
text_type = str
else:
else: # pragma: no cover
string_types = basestring,
text_type = unicode
@@ -21,7 +21,7 @@ if PY3: # pragma: no cover
if isinstance(v, str):
return False
return hasattr(v, '__iter__')
else:
else: # pragma: no cover
def is_nonstr_iter(v):
return hasattr(v, '__iter__')

View File

@@ -2033,10 +2033,11 @@ class TestSchemaNode(unittest.TestCase):
self.assertTrue(hasattr(node, '_order'))
def test_ctor_no_title(self):
node = self._makeOne(None, 0, validator=1, default=2, name='name_a',
child = DummySchemaNode(None, name='fred')
node = self._makeOne(None, child, validator=1, default=2, name='name_a',
missing='missing')
self.assertEqual(node.typ, None)
self.assertEqual(node.children, [0])
self.assertEqual(node.children, [child])
self.assertEqual(node.validator, 1)
self.assertEqual(node.default, 2)
self.assertEqual(node.missing, 'missing')
@@ -2044,36 +2045,40 @@ class TestSchemaNode(unittest.TestCase):
self.assertEqual(node.title, 'Name A')
def test_ctor_with_title(self):
node = self._makeOne(None, 0, validator=1, default=2, name='name',
child = DummySchemaNode(None, name='fred')
node = self._makeOne(None, child, validator=1, default=2, name='name',
title='title')
self.assertEqual(node.typ, None)
self.assertEqual(node.children, [0])
self.assertEqual(node.children, [child])
self.assertEqual(node.validator, 1)
self.assertEqual(node.default, 2)
self.assertEqual(node.name, 'name')
self.assertEqual(node.title, 'title')
def test_ctor_with_description(self):
node = self._makeOne(None, 0, validator=1, default=2, name='name',
node = self._makeOne(None, validator=1, default=2, name='name',
title='title', description='desc')
self.assertEqual(node.description, 'desc')
def test_ctor_with_widget(self):
node = self._makeOne(None, 0, widget='abc')
node = self._makeOne(None, widget='abc')
self.assertEqual(node.widget, 'abc')
def test_ctor_with_preparer(self):
node = self._makeOne(None, 0, preparer='abc')
node = self._makeOne(None, preparer='abc')
self.assertEqual(node.preparer, 'abc')
def test_ctor_without_preparer(self):
node = self._makeOne(None, 0)
node = self._makeOne(None)
self.assertEqual(node.preparer, None)
def test_ctor_with_unknown_kwarg(self):
node = self._makeOne(None, 0, foo=1)
node = self._makeOne(None, foo=1)
self.assertEqual(node.foo, 1)
def test_ctor_without_type(self):
self.assertRaises(NotImplementedError, self._makeOne)
def test_required_true(self):
node = self._makeOne(None)
self.assertEqual(node.required, True)
@@ -2367,7 +2372,119 @@ class TestSchemaNode(unittest.TestCase):
node = self._makeOne(typ)
self.assertEqual(node.cstruct_children(None), [])
self.assertEqual(len(w), 1)
def test_raise_invalid(self):
import colander
typ = DummyType()
node = self._makeOne(typ)
self.assertRaises(colander.Invalid, node.raise_invalid, 'Wrong')
class TestSchemaNodeSubcassing(unittest.TestCase):
def test_subclass_uses_validator_method(self):
import colander
class MyNode(colander.SchemaNode):
__schema_type__ = colander.Int
name = 'my'
def validator(self, node, cstruct):
if cstruct > 10:
self.raise_invalid('Wrong')
node = MyNode()
self.assertRaises(colander.Invalid, node.deserialize, 20)
def test_subclass_uses_missing(self):
import colander
class MyNode(colander.SchemaNode):
__schema_type__ = colander.Int
name = 'my'
missing = 10
node = MyNode()
result = node.deserialize(colander.null)
self.assertEqual(result, 10)
def test_subclass_value_overridden_by_constructor(self):
import colander
class MyNode(colander.SchemaNode):
__schema_type__ = colander.Int
name = 'my'
missing = 10
node = MyNode(missing=5)
result = node.deserialize(colander.null)
self.assertEqual(result, 5)
def test_method_values_can_rely_on_binding(self):
import colander
class MyNode(colander.SchemaNode):
__schema_type__ = colander.Int
def amethod(self):
return self.bindings['request']
node = MyNode()
newnode = node.bind(request=True)
self.assertEqual(newnode.amethod(), True)
def test_nonmethod_values_can_rely_on_after_bind(self):
import colander
class MyNode(colander.SchemaNode):
__schema_type__ = colander.Int
def after_bind(self, node, kw):
self.missing = kw['missing']
node = MyNode()
newnode = node.bind(missing=10)
self.assertEqual(newnode.deserialize(colander.null), 10)
def test_deferred_methods_dont_quite_work_yet(self):
import colander
class MyNode(colander.SchemaNode):
__schema_type__ = colander.Int
@colander.deferred
def avalidator(self, node, kw):
def _avalidator(node, cstruct):
self.raise_invalid('Foo')
return _avalidator
node = MyNode()
self.assertRaises(TypeError, node.bind)
def test_nonmethod_values_can_be_deferred_though(self):
import colander
def _missing(node, kw):
return 10
class MyNode(colander.SchemaNode):
__schema_type__ = colander.Int
missing = colander.deferred(_missing)
node = MyNode()
bound_node = node.bind()
self.assertEqual(bound_node.deserialize(colander.null), 10)
def test_schema_child_names_conflict_with_value_names_notused(self):
import colander
def _missing(node, kw):
return 10
class MyNode(colander.SchemaNode):
__schema_type__ = colander.Mapping
title = colander.SchemaNode(
colander.String(),
)
node = MyNode()
self.assertEqual(node.title, '')
def test_schema_child_names_conflict_with_value_names_used(self):
import colander
def _missing(node, kw):
return 10
doesntmatter = colander.SchemaNode(
colander.String(),
name='name',
)
class MyNode(colander.SchemaNode):
__schema_type__ = colander.Mapping
name = 'fred'
wontmatter = doesntmatter
node = MyNode()
self.assertEqual(node.name, 'fred')
self.assertEqual(node['name'], doesntmatter)
class TestMappingSchemaInheritance(unittest.TestCase):
def test_single_inheritance(self):
@@ -2508,7 +2625,7 @@ class TestSchema(unittest.TestCase):
node = MySchema(default='abc')
self.assertTrue(hasattr(node, '_order'))
self.assertEqual(node.default, 'abc')
self.assertEqual(node.__class__, colander.SchemaNode)
self.assertTrue(isinstance(node, colander.SchemaNode))
self.assertEqual(node.typ.__class__, colander.Mapping)
self.assertEqual(node.children[0].typ.__class__, colander.String)
self.assertEqual(node.children[0].title, 'Thing A')
@@ -2535,7 +2652,7 @@ class TestSequenceSchema(unittest.TestCase):
inner = _inner
node = MySchema()
self.assertTrue(hasattr(node, '_order'))
self.assertEqual(node.__class__, colander.SchemaNode)
self.assertTrue(isinstance(node, colander.SchemaNode))
self.assertEqual(node.typ.__class__, colander.Sequence)
self.assertEqual(node.children[0], _inner)
@@ -2567,7 +2684,7 @@ class TestTupleSchema(unittest.TestCase):
thing = colander.SchemaNode(colander.String())
node = MySchema()
self.assertTrue(hasattr(node, '_order'))
self.assertEqual(node.__class__, colander.SchemaNode)
self.assertTrue(isinstance(node, colander.SchemaNode))
self.assertEqual(node.typ.__class__, colander.Tuple)
self.assertEqual(node.children[0].typ.__class__, colander.String)
@@ -2895,6 +3012,51 @@ class TestDeclarative(unittest.TestCase, TestFunctional):
schema = MainSchema(name=name)
return schema
class TestUltraDeclarative(unittest.TestCase, TestFunctional):
def _makeSchema(self, name='schema'):
import colander
class IntSchema(colander.SchemaNode):
__schema_type__ = colander.Int
class StringSchema(colander.SchemaNode):
__schema_type__ = colander.String
class TupleSchema(colander.TupleSchema):
tupint = IntSchema()
tupstring = StringSchema()
class MappingSchema(colander.MappingSchema):
key = IntSchema()
key2 = IntSchema()
class SequenceOne(colander.SequenceSchema):
tup = TupleSchema()
class SequenceTwo(colander.SequenceSchema):
mapping = MappingSchema()
class IntSchemaRanged(IntSchema):
validator = colander.Range(0, 10)
class GlobalObjectSchema(colander.SchemaNode):
def __schema_type__(self):
return colander.GlobalObject(package=colander)
class MainSchema(colander.MappingSchema):
int = IntSchemaRanged()
ob = GlobalObjectSchema()
seq = SequenceOne()
tup = TupleSchema()
seq2 = SequenceTwo()
MainSchema.name = name
schema = MainSchema()
return schema
class Test_null(unittest.TestCase):
def test___nonzero__(self):
from colander import null