Features
~~~~~~~~ - 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:
186
CHANGES.txt
186
CHANGES.txt
@@ -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)
|
||||
------------------
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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__')
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user