Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/colander/__init__.py : 30%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# coding=utf-8
2import copy
3import datetime
4import decimal
5import functools
6import itertools
7import pprint
8import re
9import translationstring
10import warnings
11import types
13from iso8601 import iso8601
15from .compat import text_, text_type, string_types, xrange, is_nonstr_iter
18_ = translationstring.TranslationStringFactory('colander')
21class _required(object):
22 """ Represents a required value in colander-related operations. """
24 def __repr__(self):
25 return '<colander.required>'
27 def __reduce__(self):
28 # when unpickled, refers to "required" below (singleton)
29 return 'required'
32required = _required()
33_marker = required # bw compat
36class _null(object):
37 """ Represents a null value in colander-related operations. """
39 def __nonzero__(self):
40 return False
42 # py3 compat
43 __bool__ = __nonzero__
45 def __repr__(self):
46 return '<colander.null>'
48 def __reduce__(self):
49 return 'null' # when unpickled, refers to "null" below (singleton)
52null = _null()
55class _drop(object):
56 """ Represents a value that will be dropped from the schema if it
57 is missing during *serialization* or *deserialization*. Passed as
58 a value to the `missing` or `default` keyword argument
59 of :class:`SchemaNode`.
60 """
62 def __repr__(self):
63 return '<colander.drop>'
65 def __reduce__(self):
66 return 'drop' # when unpickled, refers to "drop" below (singleton)
69drop = _drop()
72def interpolate(msgs):
73 for s in msgs:
74 if hasattr(s, 'interpolate'):
75 yield s.interpolate()
76 else:
77 yield s
80class UnboundDeferredError(Exception):
81 """
82 An exception raised by :meth:`SchemaNode.deserialize` when an attempt
83 is made to deserialize a node which has an unbound :class:`deferred`
84 validator.
85 """
88class Invalid(Exception):
89 """
90 An exception raised by data types and validators indicating that
91 the value for a particular node was not valid.
93 The constructor receives a mandatory ``node`` argument. This must
94 be an instance of the :class:`colander.SchemaNode` class, or at
95 least something with the same interface.
97 The constructor also receives an optional ``msg`` keyword
98 argument, defaulting to ``None``. The ``msg`` argument is a
99 freeform field indicating the error circumstance.
101 The constructor additionally may receive an optional ``value``
102 keyword, indicating the value related to the error.
103 """
105 pos = None
106 positional = False
108 def __init__(self, node, msg=None, value=None):
109 Exception.__init__(self, node, msg)
110 self.node = node
111 self.msg = msg
112 self.value = value
113 self.children = []
115 def messages(self):
116 """ Return an iterable of error messages for this exception using the
117 ``msg`` attribute of this error node. If the ``msg`` attribute is
118 iterable, it is returned. If it is not iterable, and is
119 non-``None``, a single-element list containing the ``msg`` value is
120 returned. If the value is ``None``, an empty list is returned."""
121 if is_nonstr_iter(self.msg):
122 return self.msg
123 if self.msg is None:
124 return []
125 return [self.msg]
127 def add(self, exc, pos=None):
128 """ Add a child exception; ``exc`` must be an instance of
129 :class:`colander.Invalid` or a subclass.
131 ``pos`` is a value important for accurate error reporting. If
132 it is provided, it must be an integer representing the
133 position of ``exc`` relative to all other subexceptions of
134 this exception node. For example, if the exception being
135 added is about the third child of the exception which is
136 ``self``, ``pos`` might be passed as ``3``.
138 If ``pos`` is provided, it will be assigned to the ``pos``
139 attribute of the provided ``exc`` object.
140 """
141 if self.node and isinstance(self.node.typ, Positional):
142 exc.positional = True
143 if pos is not None:
144 exc.pos = pos
145 self.children.append(exc)
147 def __setitem__(self, name, msg):
148 """ Add a subexception related to a child node with the
149 message ``msg``. ``name`` must be present in the names of the
150 set of child nodes of this exception's node; if this is not
151 so, a :exc:`KeyError` is raised.
153 For example, if the exception upon which ``__setitem__`` is
154 called has a node attribute, and that node attribute has
155 children that have the names ``name`` and ``title``, you may
156 successfully call ``__setitem__('name', 'Bad name')`` or
157 ``__setitem__('title', 'Bad title')``. But calling
158 ``__setitem__('wrong', 'whoops')`` will result in a
159 :exc:`KeyError`.
161 This method is typically only useful if the ``node`` attribute
162 of the exception upon which it is called is a schema node
163 representing a mapping.
164 """
165 for num, child in enumerate(self.node.children):
166 if child.name == name:
167 exc = Invalid(child, msg)
168 self.add(exc, num)
169 return
170 raise KeyError(name)
172 def paths(self):
173 """ A generator which returns each path through the exception
174 graph. Each path is represented as a tuple of exception
175 nodes. Within each tuple, the leftmost item will represent
176 the root schema node, the rightmost item will represent the
177 leaf schema node."""
179 def traverse(node, stack):
180 stack.append(node)
182 if not node.children:
183 yield tuple(stack)
185 for child in node.children:
186 for path in traverse(child, stack):
187 yield path
189 stack.pop()
191 return traverse(self, [])
193 def _keyname(self):
194 if self.positional:
195 return str(self.pos)
196 return str(self.node.name)
198 def asdict(self, translate=None, separator='; '):
199 """ Return a dictionary containing a basic
200 (non-language-translated) error report for this exception.
202 If ``translate`` is supplied, it must be a callable taking a
203 translation string as its sole argument and returning a localized,
204 interpolated string.
206 If ``separator`` is supplied, error messages are joined with that.
207 """
208 paths = self.paths()
209 errors = {}
210 for path in paths:
211 keyparts = []
212 msgs = []
213 for exc in path:
214 exc.msg and msgs.extend(exc.messages())
215 keyname = exc._keyname()
216 keyname and keyparts.append(keyname)
217 if translate:
218 msgs = [translate(msg) for msg in msgs]
219 msgs = interpolate(msgs)
220 if separator:
221 msgs = separator.join(msgs)
222 else:
223 msgs = list(msgs)
224 errors['.'.join(keyparts)] = msgs
225 return errors
227 def __str__(self):
228 """ Return a pretty-formatted string representation of the
229 result of an execution of this exception's ``asdict`` method"""
230 return pprint.pformat(self.asdict())
233class UnsupportedFields(Invalid):
234 """
235 Exception used when schema object detect unknown fields in the
236 cstruct during deserialize.
237 """
239 def __init__(self, node, fields, msg=None):
240 super(UnsupportedFields, self).__init__(node, msg)
241 self.fields = fields
244class All(object):
245 """ Composite validator which succeeds if none of its
246 subvalidators raises an :class:`colander.Invalid` exception"""
248 def __init__(self, *validators):
249 self.validators = validators
251 def __call__(self, node, value):
252 excs = []
253 for validator in self.validators:
254 try:
255 validator(node, value)
256 except Invalid as e:
257 excs.append(e)
259 if excs:
260 exc = Invalid(node, [exc.msg for exc in excs])
261 for e in excs:
262 exc.children.extend(e.children)
263 raise exc
266class Any(All):
267 """ Composite validator which succeeds if at least one of its
268 subvalidators does not raise an :class:`colander.Invalid` exception."""
270 def __call__(self, node, value):
271 try:
272 return super(Any, self).__call__(node, value)
273 except Invalid as e:
274 if len(e.msg) < len(self.validators):
275 # At least one validator did not fail:
276 return
277 raise
280class Function(object):
281 """ Validator which accepts a function and an optional message;
282 the function is called with the ``value`` during validation.
284 If the function returns anything falsy (``None``, ``False``, the
285 empty string, ``0``, an object with a ``__nonzero__`` that returns
286 ``False``, etc) when called during validation, an
287 :exc:`colander.Invalid` exception is raised (validation fails);
288 its msg will be the value of the ``msg`` argument passed to this
289 class' constructor.
291 If the function returns a stringlike object (a ``str`` or
292 ``unicode`` object) that is *not* the empty string , a
293 :exc:`colander.Invalid` exception is raised using the stringlike
294 value returned from the function as the exeption message
295 (validation fails).
297 If the function returns anything *except* a stringlike object
298 object which is truthy (e.g. ``True``, the integer ``1``, an
299 object with a ``__nonzero__`` that returns ``True``, etc), an
300 :exc:`colander.Invalid` exception is *not* raised (validation
301 succeeds).
303 The default value for the ``msg`` when not provided via the
304 constructor is ``Invalid value``.
306 The ``message`` parameter has been deprecated, use ``msg`` instead.
307 """
309 def __init__(self, function, msg=None, message=None):
310 self.function = function
311 if msg is not None and message is not None:
312 raise ValueError('Only one of msg and message can be passed')
313 # Handle bw compat
314 if msg is None and message is None:
315 msg = _('Invalid value')
316 elif message is not None:
317 warnings.warn(
318 'The "message" argument has been deprecated, use "msg" '
319 'instead.',
320 DeprecationWarning,
321 )
322 msg = message
323 self.msg = msg
325 def __call__(self, node, value):
326 result = self.function(value)
327 if not result:
328 raise Invalid(
329 node,
330 translationstring.TranslationString(
331 self.msg, mapping={'val': value}
332 ),
333 )
334 if isinstance(result, string_types):
335 raise Invalid(
336 node,
337 translationstring.TranslationString(
338 result, mapping={'val': value}
339 ),
340 )
343class Regex(object):
344 """ Regular expression validator.
346 Initialize it with the string regular expression ``regex`` that will
347 be compiled and matched against ``value`` when validator is called. It
348 uses Python's :py:func:`re.match`, which only matches at the beginning
349 of the string and not at the beginning of each line. To match the
350 entire string, enclose the regular expression with ``^`` and ``$``.
351 If ``msg`` is supplied, it will be the error message to be used;
352 otherwise, defaults to 'String does not match expected pattern'.
354 The ``regex`` expression behaviour can be modified by specifying
355 any ``flags`` value taken by ``re.compile``.
357 The ``regex`` argument may also be a pattern object (the
358 result of ``re.compile``) instead of a string.
360 When calling, if ``value`` matches the regular expression,
361 validation succeeds; otherwise, :exc:`colander.Invalid` is
362 raised with the ``msg`` error message.
363 """
365 def __init__(self, regex, msg=None, flags=0):
366 if isinstance(regex, string_types):
367 self.match_object = re.compile(regex, flags)
368 else:
369 self.match_object = regex
370 if msg is None:
371 self.msg = _("String does not match expected pattern")
372 else:
373 self.msg = msg
375 def __call__(self, node, value):
376 if self.match_object.match(value) is None:
377 raise Invalid(node, self.msg)
380# Regex for email addresses.
381#
382# Stolen from the WhatWG HTML spec:
383# https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type=email)
384#
385# If it is good enough for browsers, it is good enough for us!
386EMAIL_RE = (
387 r"^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9]"
388 r"(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9]"
389 r"(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
390)
393class Email(Regex):
394 """ Email address validator. If ``msg`` is supplied, it will be
395 the error message to be used when raising :exc:`colander.Invalid`;
396 otherwise, defaults to 'Invalid email address'.
397 """
399 def __init__(self, msg=None):
400 email_regex = text_(EMAIL_RE)
401 if msg is None:
402 msg = _("Invalid email address")
403 super(Email, self).__init__(email_regex, msg=msg)
406class Range(object):
407 """ Validator which succeeds if the value it is passed is greater
408 or equal to ``min`` and less than or equal to ``max``. If ``min``
409 is not specified, or is specified as ``None``, no lower bound
410 exists. If ``max`` is not specified, or is specified as ``None``,
411 no upper bound exists.
413 ``min_err`` is used to form the ``msg`` of the
414 :exc:`colander.Invalid` error when reporting a validation failure
415 caused by a value not meeting the minimum. If ``min_err`` is
416 specified, it must be a string. The string may contain the
417 replacement targets ``${min}`` and ``${val}``, representing the
418 minimum value and the provided value respectively. If it is not
419 provided, it defaults to ``'${val} is less than minimum value
420 ${min}'``.
422 ``max_err`` is used to form the ``msg`` of the
423 :exc:`colander.Invalid` error when reporting a validation failure
424 caused by a value exceeding the maximum. If ``max_err`` is
425 specified, it must be a string. The string may contain the
426 replacement targets ``${max}`` and ``${val}``, representing the
427 maximum value and the provided value respectively. If it is not
428 provided, it defaults to ``'${val} is greater than maximum value
429 ${max}'``.
430 """
432 _MIN_ERR = _('${val} is less than minimum value ${min}')
433 _MAX_ERR = _('${val} is greater than maximum value ${max}')
435 def __init__(self, min=None, max=None, min_err=_MIN_ERR, max_err=_MAX_ERR):
436 self.min = min
437 self.max = max
438 self.min_err = min_err
439 self.max_err = max_err
441 def __call__(self, node, value):
442 if self.min is not None:
443 if value < self.min:
444 min_err = _(
445 self.min_err, mapping={'val': value, 'min': self.min}
446 )
447 raise Invalid(node, min_err)
449 if self.max is not None:
450 if value > self.max:
451 max_err = _(
452 self.max_err, mapping={'val': value, 'max': self.max}
453 )
454 raise Invalid(node, max_err)
457class Length(object):
458 """Validator which succeeds if the value passed to it has a
459 length between a minimum and maximum, expressed in the
460 optional ``min`` and ``max`` arguments.
461 The value can be any sequence, most often a string.
463 If ``min`` is not specified, or is specified as ``None``,
464 no lower bound exists. If ``max`` is not specified, or
465 is specified as ``None``, no upper bound exists.
467 The default error messages are "Shorter than minimum length ${min}"
468 and "Longer than maximum length ${max}". These can be customized:
470 ``min_err`` is used to form the ``msg`` of the
471 :exc:`colander.Invalid` error when reporting a validation failure
472 caused by a value not meeting the minimum length. If ``min_err`` is
473 specified, it must be a string. The string may contain the
474 replacement target ``${min}``.
476 ``max_err`` is used to form the ``msg`` of the
477 :exc:`colander.Invalid` error when reporting a validation failure
478 caused by a value exceeding the maximum length. If ``max_err`` is
479 specified, it must be a string. The string may contain the
480 replacement target ``${max}``.
481 """
483 _MIN_ERR = _('Shorter than minimum length ${min}')
484 _MAX_ERR = _('Longer than maximum length ${max}')
486 def __init__(self, min=None, max=None, min_err=_MIN_ERR, max_err=_MAX_ERR):
487 self.min = min
488 self.max = max
489 self.min_err = min_err
490 self.max_err = max_err
492 def __call__(self, node, value):
493 if self.min is not None:
494 if len(value) < self.min:
495 min_err = _(self.min_err, mapping={'min': self.min})
496 raise Invalid(node, min_err)
497 if self.max is not None:
498 if len(value) > self.max:
499 max_err = _(self.max_err, mapping={'max': self.max})
500 raise Invalid(node, max_err)
503class OneOf(object):
504 """ Validator which succeeds if the value passed to it is one of
505 a fixed set of values """
507 def __init__(self, choices):
508 self.choices = choices
510 def __call__(self, node, value):
511 if value not in self.choices:
512 choices = ', '.join(['%s' % x for x in self.choices])
513 err = _(
514 '"${val}" is not one of ${choices}',
515 mapping={'val': value, 'choices': choices},
516 )
517 raise Invalid(node, err)
520class NoneOf(object):
521 """ Validator which succeeds if the value passed to it is none of a
522 fixed set of values.
524 ``msg_err`` is used to form the ``msg`` of the :exc:`colander.Invalid`
525 error when reporting a validation failure. If ``msg_err`` is specified,
526 it must be a string. The string may contain the replacement targets
527 ``${choices}`` and ``${val}``, representing the set of forbidden values
528 and the provided value respectively.
529 """
531 _MSG_ERR = _('"${val}" must not be one of ${choices}')
533 def __init__(self, choices, msg_err=_MSG_ERR):
534 self.forbidden = choices
535 self.msg_err = msg_err
537 def __call__(self, node, value):
538 if value not in self.forbidden:
539 return
541 choices = ', '.join(['%s' % x for x in self.forbidden])
542 err = _(self.msg_err, mapping={'val': value, 'choices': choices})
544 raise Invalid(node, err)
547class ContainsOnly(object):
548 """ Validator which succeeds if the value passed to is a sequence and each
549 element in the sequence is also in the sequence passed as ``choices``.
550 This validator is useful when attached to a schemanode with, e.g. a
551 :class:`colander.Set` or another sequencetype.
552 """
554 err_template = _('One or more of the choices you made was not acceptable')
556 def __init__(self, choices):
557 self.choices = choices
559 def __call__(self, node, value):
560 if not set(value).issubset(self.choices):
561 err = _(
562 self.err_template,
563 mapping={'val': value, 'choices': self.choices},
564 )
565 raise Invalid(node, err)
568def luhnok(node, value):
569 """ Validator which checks to make sure that the value passes a luhn
570 mod-10 checksum (credit cards). ``value`` must be a string, not an
571 integer."""
572 try:
573 checksum = _luhnok(value)
574 except ValueError:
575 raise Invalid(
576 node,
577 _(
578 '"${val}" is not a valid credit card number',
579 mapping={'val': value},
580 ),
581 )
583 if (checksum % 10) != 0:
584 raise Invalid(
585 node,
586 _(
587 '"${val}" is not a valid credit card number',
588 mapping={'val': value},
589 ),
590 )
593def _luhnok(value):
594 checksum = 0
595 num_digits = len(value)
596 oddeven = num_digits & 1
598 for count in range(0, num_digits):
599 digit = int(value[count])
601 if not ((count & 1) ^ oddeven):
602 digit *= 2
603 if digit > 9:
604 digit -= 9
606 checksum += digit
607 return checksum
610# Gingerly lifted from Django 1.3.x:
611# https://github.com/django/django/blob/stable/1.3.x/django/core/validators.py#L45
612# <3 y'all!
613URL_REGEX = (
614 # {http,ftp}s:// (not required)
615 r'^((?:http|ftp)s?://)?'
616 # Domain
617 r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+'
618 r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'
619 # Localhost
620 r'localhost|'
621 # IPv6 address
622 r'\[[a-f0-9:]+\]|'
623 # IPv4 address
624 r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
625 # Optional port
626 r'(?::\d+)?'
627 # Path
628 r'(?:/?|[/?]\S+)$'
629)
631url = Regex(URL_REGEX, msg=_('Must be a URL'), flags=re.IGNORECASE)
634URI_REGEX = (
635 # file:// (required)
636 r'^file://'
637 # Path
638 r'(?:/|[/?]\S+)$'
639)
641file_uri = Regex(
642 URI_REGEX, msg=_('Must be a file:// URI scheme'), flags=re.IGNORECASE
643)
645UUID_REGEX = (
646 r'^(?:urn:uuid:)?\{?[a-f0-9]{8}(?:-?[a-f0-9]{4}){3}-?[a-f0-9]{12}\}?$'
647)
648uuid = Regex(UUID_REGEX, _('Invalid UUID string'), re.IGNORECASE)
651class SchemaType(object):
652 """ Base class for all schema types """
654 def flatten(self, node, appstruct, prefix='', listitem=False):
655 result = {}
656 if listitem:
657 selfname = prefix
658 else:
659 selfname = '%s%s' % (prefix, node.name)
660 result[selfname.rstrip('.')] = appstruct
661 return result
663 def unflatten(self, node, paths, fstruct):
664 name = node.name
665 assert paths == [name], "paths should be [name] for leaf nodes."
666 return fstruct[name]
668 def set_value(self, node, appstruct, path, value):
669 raise AssertionError("Can't call 'set_value' on a leaf node.")
671 def get_value(self, node, appstruct, path):
672 raise AssertionError("Can't call 'get_value' on a leaf node.")
674 def cstruct_children(self, node, cstruct):
675 return []
678class Mapping(SchemaType):
679 """ A type which represents a mapping of names to nodes.
681 The subnodes of the :class:`colander.SchemaNode` that wraps
682 this type imply the named keys and values in the mapping.
684 The constructor of this type accepts one extra optional keyword
685 argument that other types do not: ``unknown``. An attribute of
686 the same name can be set on a type instance to control the
687 behavior after construction.
689 unknown
690 ``unknown`` controls the behavior of this type when an unknown
691 key is encountered in the cstruct passed to the
692 ``deserialize`` method of this instance. All the potential
693 values of ``unknown`` are strings. They are:
695 - ``ignore`` means that keys that are not present in the schema
696 associated with this type will be ignored during
697 deserialization.
699 - ``raise`` will cause a :exc:`colander.Invalid` exception to
700 be raised when unknown keys are present in the cstruct
701 during deserialization.
703 - ``preserve`` will preserve the 'raw' unknown keys and values
704 in the appstruct returned by deserialization.
706 Default: ``ignore``.
708 Special behavior is exhibited when a subvalue of a mapping is
709 present in the schema but is missing from the mapping passed to
710 either the ``serialize`` or ``deserialize`` method of this class.
711 In this case, the :attr:`colander.null` value will be passed to
712 the ``serialize`` or ``deserialize`` method of the schema node
713 representing the subvalue of the mapping respectively. During
714 serialization, this will result in the behavior described in
715 :ref:`serializing_null` for the subnode. During deserialization,
716 this will result in the behavior described in
717 :ref:`deserializing_null` for the subnode.
719 If the :attr:`colander.null` value is passed to the serialize
720 method of this class, a dictionary will be returned, where each of
721 the values in the returned dictionary is the serialized
722 representation of the null value for its type.
723 """
725 def __init__(self, unknown='ignore'):
726 self.unknown = unknown
728 def _set_unknown(self, value):
729 if value not in ('ignore', 'raise', 'preserve'):
730 raise ValueError(
731 'unknown attribute must be one of "ignore", "raise", '
732 'or "preserve"'
733 )
734 self._unknown = value
736 def _get_unknown(self):
737 return self._unknown
739 unknown = property(_get_unknown, _set_unknown)
741 def _validate(self, node, value):
742 try:
743 if hasattr(value, 'items'):
744 return dict(value)
745 else:
746 raise TypeError('Does not implement dict-like functionality.')
747 except Exception as e:
748 raise Invalid(
749 node,
750 _(
751 '"${val}" is not a mapping type: ${err}',
752 mapping={'val': value, 'err': e},
753 ),
754 )
756 def cstruct_children(self, node, cstruct):
757 if cstruct is null:
758 value = {}
759 else:
760 value = self._validate(node, cstruct)
761 children = []
762 for subnode in node.children:
763 name = subnode.name
764 subval = value.get(name, _marker)
765 if subval is _marker:
766 subval = subnode.serialize(null)
767 children.append(subval)
768 return children
770 def _impl(self, node, value, callback):
771 value = self._validate(node, value)
773 error = None
774 result = {}
776 for num, subnode in enumerate(node.children):
777 name = subnode.name
778 subval = value.pop(name, null)
779 if subval is drop or (subval is null and subnode.default is drop):
780 continue
781 try:
782 sub_result = callback(subnode, subval)
783 except Invalid as e:
784 if error is None:
785 error = Invalid(node)
786 error.add(e, num)
787 else:
788 if sub_result is drop:
789 continue
790 result[name] = sub_result
792 if self.unknown == 'raise':
793 if value:
794 raise UnsupportedFields(
795 node,
796 value,
797 msg=_(
798 'Unrecognized keys in mapping: "${val}"',
799 mapping={'val': value},
800 ),
801 )
803 elif self.unknown == 'preserve':
804 result.update(copy.deepcopy(value))
806 if error is not None:
807 raise error
809 return result
811 def serialize(self, node, appstruct):
812 if appstruct is null:
813 appstruct = {}
815 def callback(subnode, subappstruct):
816 return subnode.serialize(subappstruct)
818 return self._impl(node, appstruct, callback)
820 def deserialize(self, node, cstruct):
821 if cstruct is null:
822 return null
824 def callback(subnode, subcstruct):
825 return subnode.deserialize(subcstruct)
827 return self._impl(node, cstruct, callback)
829 def flatten(self, node, appstruct, prefix='', listitem=False):
830 result = {}
831 if listitem:
832 selfprefix = prefix
833 else:
834 if node.name:
835 selfprefix = '%s%s.' % (prefix, node.name)
836 else:
837 selfprefix = prefix
839 for subnode in node.children:
840 name = subnode.name
841 substruct = appstruct.get(name, null)
842 result.update(
843 subnode.typ.flatten(subnode, substruct, prefix=selfprefix)
844 )
845 return result
847 def unflatten(self, node, paths, fstruct):
848 return _unflatten_mapping(node, paths, fstruct)
850 def set_value(self, node, appstruct, path, value):
851 if '.' in path:
852 next_name, rest = path.split('.', 1)
853 next_node = node[next_name]
854 next_appstruct = appstruct[next_name]
855 appstruct[next_name] = next_node.typ.set_value(
856 next_node, next_appstruct, rest, value
857 )
858 else:
859 appstruct[path] = value
860 return appstruct
862 def get_value(self, node, appstruct, path):
863 if '.' in path:
864 name, rest = path.split('.', 1)
865 next_node = node[name]
866 return next_node.typ.get_value(next_node, appstruct[name], rest)
867 return appstruct[path]
870class Positional(object):
871 """
872 Marker abstract base class meaning 'this type has children which
873 should be addressed by position instead of name' (e.g. via seq[0],
874 but never seq['name']). This is consulted by Invalid.asdict when
875 creating a dictionary representation of an error tree.
876 """
879class Tuple(Positional, SchemaType):
880 """ A type which represents a fixed-length sequence of nodes.
882 The subnodes of the :class:`colander.SchemaNode` that wraps
883 this type imply the positional elements of the tuple in the order
884 they are added.
886 This type is willing to serialize and deserialized iterables that,
887 when converted to a tuple, have the same number of elements as the
888 number of the associated node's subnodes.
890 If the :attr:`colander.null` value is passed to the serialize
891 method of this class, the :attr:`colander.null` value will be
892 returned.
893 """
895 def _validate(self, node, value):
896 if not hasattr(value, '__iter__'):
897 raise Invalid(
898 node, _('"${val}" is not iterable', mapping={'val': value})
899 )
901 valuelen, nodelen = len(value), len(node.children)
903 if valuelen != nodelen:
904 raise Invalid(
905 node,
906 _(
907 '"${val}" has an incorrect number of elements '
908 '(expected ${exp}, was ${was})',
909 mapping={'val': value, 'exp': nodelen, 'was': valuelen},
910 ),
911 )
913 return list(value)
915 def cstruct_children(self, node, cstruct):
916 childlen = len(node.children)
917 if cstruct is null:
918 cstruct = []
919 structlen = len(cstruct)
920 if structlen < childlen:
921 missing_children = node.children[structlen:]
922 cstruct = list(cstruct)
923 for child in missing_children:
924 cstruct.append(child.serialize(null))
925 elif structlen > childlen:
926 cstruct = cstruct[:childlen]
927 else:
928 cstruct = list(cstruct)
929 return cstruct
931 def _impl(self, node, value, callback):
932 value = self._validate(node, value)
933 error = None
934 result = []
936 for num, subnode in enumerate(node.children):
937 subval = value[num]
938 try:
939 result.append(callback(subnode, subval))
940 except Invalid as e:
941 if error is None:
942 error = Invalid(node)
943 error.add(e, num)
945 if error is not None:
946 raise error
948 return tuple(result)
950 def serialize(self, node, appstruct):
951 if appstruct is null:
952 return null
954 def callback(subnode, subappstruct):
955 return subnode.serialize(subappstruct)
957 return self._impl(node, appstruct, callback)
959 def deserialize(self, node, cstruct):
960 if cstruct is null:
961 return null
963 def callback(subnode, subval):
964 return subnode.deserialize(subval)
966 return self._impl(node, cstruct, callback)
968 def flatten(self, node, appstruct, prefix='', listitem=False):
969 result = {}
970 if listitem:
971 selfprefix = prefix
972 else:
973 selfprefix = '%s%s.' % (prefix, node.name)
975 for num, subnode in enumerate(node.children):
976 substruct = appstruct[num]
977 result.update(
978 subnode.typ.flatten(subnode, substruct, prefix=selfprefix)
979 )
980 return result
982 def unflatten(self, node, paths, fstruct):
983 mapstruct = _unflatten_mapping(node, paths, fstruct)
984 appstruct = []
985 for subnode in node.children:
986 appstruct.append(mapstruct[subnode.name])
987 return tuple(appstruct)
989 def set_value(self, node, appstruct, path, value):
990 appstruct = list(appstruct)
991 if '.' in path:
992 next_name, rest = path.split('.', 1)
993 else:
994 next_name, rest = path, None
995 for index, next_node in enumerate(node.children):
996 if next_node.name == next_name:
997 break
998 else:
999 raise KeyError(next_name)
1000 if rest is not None:
1001 next_appstruct = appstruct[index]
1002 appstruct[index] = next_node.typ.set_value(
1003 next_node, next_appstruct, rest, value
1004 )
1005 else:
1006 appstruct[index] = value
1007 return tuple(appstruct)
1009 def get_value(self, node, appstruct, path):
1010 if '.' in path:
1011 name, rest = path.split('.', 1)
1012 else:
1013 name, rest = path, None
1014 for index, next_node in enumerate(node.children):
1015 if next_node.name == name:
1016 break
1017 else:
1018 raise KeyError(name)
1019 if rest is not None:
1020 return next_node.typ.get_value(next_node, appstruct[index], rest)
1021 return appstruct[index]
1024class Set(SchemaType):
1025 """ A type representing a non-overlapping set of items.
1026 Deserializes an iterable to a ``set`` object.
1028 If the :attr:`colander.null` value is passed to the serialize
1029 method of this class, the :attr:`colander.null` value will be
1030 returned.
1032 .. versionadded: 1.0a1
1034 """
1036 def serialize(self, node, appstruct):
1037 if appstruct is null:
1038 return null
1040 return appstruct
1042 def deserialize(self, node, cstruct):
1043 if cstruct is null:
1044 return null
1046 if not is_nonstr_iter(cstruct):
1047 raise Invalid(
1048 node,
1049 _('${cstruct} is not iterable', mapping={'cstruct': cstruct}),
1050 )
1052 return set(cstruct)
1055class List(SchemaType):
1056 """ Type representing an ordered sequence of items.
1058 Desrializes an iterable to a ``list`` object.
1060 If the :attr:`colander.null` value is passed to the serialize
1061 method of this class, the :attr:`colander.null` value will be
1062 returned.
1064 .. versionadded: 1.0a6
1065 """
1067 def serialize(self, node, appstruct):
1068 if appstruct is null:
1069 return null
1071 return appstruct
1073 def deserialize(self, node, cstruct):
1074 if cstruct is null:
1075 return null
1077 if not is_nonstr_iter(cstruct):
1078 raise Invalid(
1079 node,
1080 _('${cstruct} is not iterable', mapping={'cstruct': cstruct}),
1081 )
1083 return list(cstruct)
1086class SequenceItems(list):
1087 """
1088 List marker subclass for use by Sequence.cstruct_children, which indicates
1089 to a caller of that method that the result is from a sequence type.
1090 Usually these values need to be treated specially, because all of the
1091 children of a Sequence are not present in a schema.
1092 """
1095class Sequence(Positional, SchemaType):
1096 """
1097 A type which represents a variable-length sequence of nodes,
1098 all of which must be of the same type.
1100 The type of the first subnode of the
1101 :class:`colander.SchemaNode` that wraps this type is considered the
1102 sequence type.
1104 The optional ``accept_scalar`` argument to this type's constructor
1105 indicates what should happen if the value found during serialization or
1106 deserialization does not have an ``__iter__`` method or is a
1107 mapping type.
1109 If ``accept_scalar`` is ``True`` and the value does not have an
1110 ``__iter__`` method or is a mapping type, the value will be turned
1111 into a single element list.
1113 If ``accept_scalar`` is ``False`` and the value does not have an
1114 ``__iter__`` method or is a mapping type, an
1115 :exc:`colander.Invalid` error will be raised during serialization
1116 and deserialization.
1118 The default value of ``accept_scalar`` is ``False``.
1120 If the :attr:`colander.null` value is passed to the serialize
1121 method of this class, the :attr:`colander.null` value is returned.
1122 """
1124 def __init__(self, accept_scalar=False):
1125 self.accept_scalar = accept_scalar
1127 def _validate(self, node, value, accept_scalar):
1128 if (
1129 hasattr(value, '__iter__')
1130 and not hasattr(value, 'get')
1131 and not isinstance(value, string_types)
1132 ):
1133 return list(value)
1134 if accept_scalar:
1135 return [value]
1136 else:
1137 raise Invalid(
1138 node, _('"${val}" is not iterable', mapping={'val': value})
1139 )
1141 def cstruct_children(self, node, cstruct):
1142 if cstruct is null:
1143 return SequenceItems([])
1144 return SequenceItems(cstruct)
1146 def _impl(self, node, value, callback, accept_scalar):
1147 if accept_scalar is None:
1148 accept_scalar = self.accept_scalar
1150 value = self._validate(node, value, accept_scalar)
1152 error = None
1153 result = []
1155 subnode = node.children[0]
1156 for num, subval in enumerate(value):
1157 if subval is drop or (subval is null and subnode.default is drop):
1158 continue
1159 try:
1160 sub_result = callback(subnode, subval)
1161 except Invalid as e:
1162 if error is None:
1163 error = Invalid(node)
1164 error.add(e, num)
1165 else:
1166 if sub_result is drop:
1167 continue
1168 result.append(sub_result)
1170 if error is not None:
1171 raise error
1173 return result
1175 def serialize(self, node, appstruct, accept_scalar=None):
1176 """
1177 Along with the normal ``node`` and ``appstruct`` arguments,
1178 this method accepts an additional optional keyword argument:
1179 ``accept_scalar``. This keyword argument can be used to
1180 override the constructor value of the same name.
1182 If ``accept_scalar`` is ``True`` and the ``appstruct`` does
1183 not have an ``__iter__`` method or is a mapping type, the
1184 value will be turned into a single element list.
1186 If ``accept_scalar`` is ``False`` and the ``appstruct`` does
1187 not have an ``__iter__`` method or is a mapping type, an
1188 :exc:`colander.Invalid` error will be raised during
1189 serialization and deserialization.
1191 The default of ``accept_scalar`` is ``None``, which means
1192 respect the default ``accept_scalar`` value attached to this
1193 instance via its constructor.
1194 """
1195 if appstruct is null:
1196 return null
1198 def callback(subnode, subappstruct):
1199 return subnode.serialize(subappstruct)
1201 return self._impl(node, appstruct, callback, accept_scalar)
1203 def deserialize(self, node, cstruct, accept_scalar=None):
1204 """
1205 Along with the normal ``node`` and ``cstruct`` arguments, this
1206 method accepts an additional optional keyword argument:
1207 ``accept_scalar``. This keyword argument can be used to
1208 override the constructor value of the same name.
1210 If ``accept_scalar`` is ``True`` and the ``cstruct`` does not
1211 have an ``__iter__`` method or is a mapping type, the value
1212 will be turned into a single element list.
1214 If ``accept_scalar`` is ``False`` and the ``cstruct`` does not have an
1215 ``__iter__`` method or is a mapping type, an
1216 :exc:`colander.Invalid` error will be raised during serialization
1217 and deserialization.
1219 The default of ``accept_scalar`` is ``None``, which means
1220 respect the default ``accept_scalar`` value attached to this
1221 instance via its constructor.
1222 """
1223 if cstruct is null:
1224 return null
1226 def callback(subnode, subcstruct):
1227 return subnode.deserialize(subcstruct)
1229 return self._impl(node, cstruct, callback, accept_scalar)
1231 def flatten(self, node, appstruct, prefix='', listitem=False):
1232 result = {}
1233 if listitem:
1234 selfprefix = prefix
1235 else:
1236 selfprefix = '%s%s.' % (prefix, node.name)
1238 childnode = node.children[0]
1240 for num, subval in enumerate(appstruct):
1241 subname = '%s%s' % (selfprefix, num)
1242 subprefix = subname + '.'
1243 result.update(
1244 childnode.typ.flatten(
1245 childnode, subval, prefix=subprefix, listitem=True
1246 )
1247 )
1249 return result
1251 def unflatten(self, node, paths, fstruct):
1252 only_child = node.children[0]
1253 child_name = only_child.name
1255 def get_child(name):
1256 return only_child
1258 def rewrite_subpath(subpath):
1259 if '.' in subpath:
1260 suffix = subpath.split('.', 1)[1]
1261 return '%s.%s' % (child_name, suffix)
1262 return child_name
1264 mapstruct = _unflatten_mapping(
1265 node, paths, fstruct, get_child, rewrite_subpath
1266 )
1267 return [mapstruct[str(index)] for index in xrange(len(mapstruct))]
1269 def set_value(self, node, appstruct, path, value):
1270 if '.' in path:
1271 next_name, rest = path.split('.', 1)
1272 index = int(next_name)
1273 next_node = node.children[0]
1274 next_appstruct = appstruct[index]
1275 appstruct[index] = next_node.typ.set_value(
1276 next_node, next_appstruct, rest, value
1277 )
1278 else:
1279 index = int(path)
1280 appstruct[index] = value
1281 return appstruct
1283 def get_value(self, node, appstruct, path):
1284 if '.' in path:
1285 name, rest = path.split('.', 1)
1286 index = int(name)
1287 next_node = node.children[0]
1288 return next_node.typ.get_value(next_node, appstruct[index], rest)
1289 return appstruct[int(path)]
1292Seq = Sequence
1295class String(SchemaType):
1296 """ A type representing a Unicode string.
1298 This type constructor accepts two arguments:
1300 ``encoding``
1301 Represents the encoding which should be applied to value
1302 serialization and deserialization, for example ``utf-8``. If
1303 ``encoding`` is passed as ``None``, the ``serialize`` method of
1304 this type will not do any special encoding of the appstruct it is
1305 provided, nor will the ``deserialize`` method of this type do
1306 any special decoding of the cstruct it is provided; inputs and
1307 outputs will be assumed to be Unicode. ``encoding`` defaults
1308 to ``None``.
1310 If ``encoding`` is ``None``:
1312 - A Unicode input value to ``serialize`` is returned untouched.
1314 - A non-Unicode input value to ``serialize`` is run through the
1315 ``unicode()`` function without an ``encoding`` parameter
1316 (``unicode(value)``) and the result is returned.
1318 - A Unicode input value to ``deserialize`` is returned untouched.
1320 - A non-Unicode input value to ``deserialize`` is run through the
1321 ``unicode()`` function without an ``encoding`` parameter
1322 (``unicode(value)``) and the result is returned.
1324 If ``encoding`` is not ``None``:
1326 - A Unicode input value to ``serialize`` is run through the
1327 ``unicode`` function with the encoding parameter
1328 (``unicode(value, encoding)``) and the result (a ``str``
1329 object) is returned.
1331 - A non-Unicode input value to ``serialize`` is converted to a
1332 Unicode using the encoding (``unicode(value, encoding)``);
1333 subsequently the Unicode object is re-encoded to a ``str``
1334 object using the encoding and returned.
1336 - A Unicode input value to ``deserialize`` is returned
1337 untouched.
1339 - A non-Unicode input value to ``deserialize`` is converted to
1340 a ``str`` object using ``str(value``). The resulting str
1341 value is converted to Unicode using the encoding
1342 (``unicode(value, encoding)``) and the result is returned.
1344 A corollary: If a string (as opposed to a unicode object) is
1345 provided as a value to either the serialize or deserialize
1346 method of this type, and the type also has an non-None
1347 ``encoding``, the string must be encoded with the type's
1348 encoding. If this is not true, an :exc:`colander.Invalid`
1349 error will result.
1351 ``allow_empty``
1352 Boolean, if True allows deserialization of an empty string. If
1353 False (default), empty strings will deserialize to
1354 :attr:`colander.null`
1356 The subnodes of the :class:`colander.SchemaNode` that wraps
1357 this type are ignored.
1358 """
1360 def __init__(self, encoding=None, allow_empty=False):
1361 self.encoding = encoding
1362 self.allow_empty = allow_empty
1364 def serialize(self, node, appstruct):
1365 if appstruct is null:
1366 return null
1368 try:
1369 if isinstance(appstruct, (text_type, bytes)):
1370 encoding = self.encoding
1371 if encoding:
1372 result = text_(appstruct, encoding).encode(encoding)
1373 else:
1374 result = text_type(appstruct)
1375 else:
1376 result = text_type(appstruct)
1377 if self.encoding:
1378 result = result.encode(self.encoding)
1379 return result
1380 except Exception as e:
1381 raise Invalid(
1382 node,
1383 _(
1384 '${val} cannot be serialized: ${err}',
1385 mapping={'val': appstruct, 'err': e},
1386 ),
1387 )
1389 def deserialize(self, node, cstruct):
1390 if cstruct == '' and self.allow_empty:
1391 return text_type('')
1393 if not cstruct:
1394 return null
1396 try:
1397 result = cstruct
1398 if isinstance(result, (text_type, bytes)):
1399 if self.encoding:
1400 result = text_(cstruct, self.encoding)
1401 else:
1402 result = text_type(cstruct)
1403 else:
1404 raise Invalid(node)
1405 except Exception as e:
1406 raise Invalid(
1407 node,
1408 _(
1409 '${val} is not a string: ${err}',
1410 mapping={'val': cstruct, 'err': e},
1411 ),
1412 )
1414 return result
1417Str = String
1420class Number(SchemaType):
1421 """ Abstract base class for float, int, decimal """
1423 num = None
1425 def serialize(self, node, appstruct):
1426 if appstruct in (null, None):
1427 return null
1429 try:
1430 return str(self.num(appstruct))
1431 except Exception:
1432 raise Invalid(
1433 node, _('"${val}" is not a number', mapping={'val': appstruct})
1434 )
1436 def deserialize(self, node, cstruct):
1437 if cstruct != 0 and not cstruct:
1438 return null
1440 try:
1441 return self.num(cstruct)
1442 except Exception:
1443 raise Invalid(
1444 node, _('"${val}" is not a number', mapping={'val': cstruct})
1445 )
1448class Integer(Number):
1449 """ A type representing an integer.
1451 If the :attr:`colander.null` value is passed to the serialize
1452 method of this class, the :attr:`colander.null` value will be
1453 returned.
1455 The Integer constructor takes an optional argument ``strict``, which if
1456 enabled will verify that the number passed to serialize/deserialize is an
1457 integer, and not a float that would get truncated.
1459 The subnodes of the :class:`colander.SchemaNode` that wraps
1460 this type are ignored.
1461 """
1463 num = int
1465 def __init__(self, strict=False):
1466 if strict:
1468 def _strict_int(val):
1469 if not float(val).is_integer():
1470 raise ValueError("Value is not an Integer")
1471 return int(val)
1473 self.num = _strict_int
1475 super(Integer, self).__init__()
1478Int = Integer
1481class Float(Number):
1482 """ A type representing a float.
1484 If the :attr:`colander.null` value is passed to the serialize
1485 method of this class, the :attr:`colander.null` value will be
1486 returned.
1488 The subnodes of the :class:`colander.SchemaNode` that wraps
1489 this type are ignored.
1490 """
1492 num = float
1495class Decimal(Number):
1496 """
1497 A type representing a decimal floating point. Deserialization returns an
1498 instance of the Python ``decimal.Decimal`` type.
1500 If the :attr:`colander.null` value is passed to the serialize
1501 method of this class, the :attr:`colander.null` value will be
1502 returned.
1504 The Decimal constructor takes three optional arguments, ``quant``,
1505 ``rounding`` and ``normalize``. If supplied, ``quant`` should be a string,
1506 (e.g. ``1.00``). If supplied, ``rounding`` should be one of the Python
1507 ``decimal`` module rounding options (e.g. ``decimal.ROUND_UP``,
1508 ``decimal.ROUND_DOWN``, etc). The serialized and deserialized result
1509 will be quantized and rounded via
1510 ``result.quantize(decimal.Decimal(quant), rounding)``. ``rounding`` is
1511 ignored if ``quant`` is not supplied. If ``normalize`` is ``True``,
1512 the serialized and deserialized result will be normalized by stripping
1513 the rightmost trailing zeros.
1515 The subnodes of the :class:`colander.SchemaNode` that wraps
1516 this type are ignored.
1517 """
1519 def __init__(self, quant=None, rounding=None, normalize=False):
1520 if quant is None:
1521 self.quant = None
1522 else:
1523 self.quant = decimal.Decimal(quant)
1524 self.rounding = rounding
1525 self.normalize = normalize
1527 def num(self, val):
1528 result = decimal.Decimal(str(val))
1529 if self.quant is not None:
1530 if self.rounding is None:
1531 result = result.quantize(self.quant)
1532 else:
1533 result = result.quantize(self.quant, self.rounding)
1534 if self.normalize:
1535 result = result.normalize()
1536 return result
1539class Money(Decimal):
1540 """ A type representing a money value with two digit precision.
1541 Deserialization returns an instance of the Python ``decimal.Decimal``
1542 type (quantized to two decimal places, rounded up).
1544 If the :attr:`colander.null` value is passed to the serialize
1545 method of this class, the :attr:`colander.null` value will be
1546 returned.
1548 The subnodes of the :class:`colander.SchemaNode` that wraps
1549 this type are ignored.
1550 """
1552 def __init__(self):
1553 super(Money, self).__init__(decimal.Decimal('.01'), decimal.ROUND_UP)
1556class Boolean(SchemaType):
1557 """ A type representing a boolean object.
1559 The constructor accepts these keyword arguments:
1561 - ``false_choices``: The set of strings representing a ``False``
1562 value on deserialization.
1564 - ``true_choices``: The set of strings representing a ``True``
1565 value on deserialization.
1567 - ``false_val``: The value returned on serialization of a False
1568 value.
1570 - ``true_val``: The value returned on serialization of a True
1571 value.
1573 During deserialization, a value contained in :attr:`false_choices`,
1574 will be considered ``False``.
1576 The behaviour for values not contained in :attr:`false_choices`
1577 depends on :attr:`true_choices`: if it's empty, any value is considered
1578 ``True``; otherwise, only values contained in :attr:`true_choices`
1579 are considered ``True``, and an Invalid exception would be raised
1580 for values outside of both :attr:`false_choices` and :attr:`true_choices`.
1582 Serialization will produce :attr:`true_val` or :attr:`false_val`
1583 based on the value.
1585 If the :attr:`colander.null` value is passed to the serialize
1586 method of this class, the :attr:`colander.null` value will be
1587 returned.
1589 The subnodes of the :class:`colander.SchemaNode` that wraps
1590 this type are ignored.
1591 """
1593 def __init__(
1594 self,
1595 false_choices=('false', '0'),
1596 true_choices=(),
1597 false_val='false',
1598 true_val='true',
1599 ):
1601 self.false_choices = false_choices
1602 self.true_choices = true_choices
1603 self.false_val = false_val
1604 self.true_val = true_val
1606 self.true_reprs = ', '.join([repr(c) for c in self.true_choices])
1607 self.false_reprs = ', '.join([repr(c) for c in self.false_choices])
1609 def serialize(self, node, appstruct):
1610 if appstruct is null:
1611 return null
1613 return appstruct and self.true_val or self.false_val
1615 def deserialize(self, node, cstruct):
1616 if cstruct is null:
1617 return null
1619 try:
1620 result = str(cstruct)
1621 except Exception:
1622 raise Invalid(
1623 node, _('${val} is not a string', mapping={'val': cstruct})
1624 )
1625 result = result.lower()
1627 if result in self.false_choices:
1628 return False
1629 elif self.true_choices:
1630 if result in self.true_choices:
1631 return True
1632 else:
1633 raise Invalid(
1634 node,
1635 _(
1636 '"${val}" is neither in (${false_choices}) '
1637 'nor in (${true_choices})',
1638 mapping={
1639 'val': cstruct,
1640 'false_choices': self.false_reprs,
1641 'true_choices': self.true_reprs,
1642 },
1643 ),
1644 )
1646 return True
1649Bool = Boolean
1652class GlobalObject(SchemaType):
1653 """ A type representing an importable Python object. This type
1654 serializes 'global' Python objects (objects which can be imported)
1655 to dotted Python names.
1657 Two dotted name styles are supported during deserialization:
1659 - ``pkg_resources``-style dotted names where non-module attributes
1660 of a module are separated from the rest of the path using a ':'
1661 e.g. ``package.module:attr``.
1663 - ``zope.dottedname``-style dotted names where non-module
1664 attributes of a module are separated from the rest of the path
1665 using a '.' e.g. ``package.module.attr``.
1667 These styles can be used interchangeably. If the serialization
1668 contains a ``:`` (colon), the ``pkg_resources`` resolution
1669 mechanism will be chosen, otherwise the ``zope.dottedname``
1670 resolution mechanism will be chosen.
1672 The constructor accepts a single argument named ``package`` which
1673 should be a Python module or package object; it is used when
1674 *relative* dotted names are supplied to the ``deserialize``
1675 method. A serialization which has a ``.`` (dot) or ``:`` (colon)
1676 as its first character is treated as relative. E.g. if
1677 ``.minidom`` is supplied to ``deserialize``, and the ``package``
1678 argument to this type was passed the ``xml`` module object, the
1679 resulting import would be for ``xml.minidom``. If a relative
1680 package name is supplied to ``deserialize``, and no ``package``
1681 was supplied to the constructor, an :exc:`colander.Invalid` error
1682 will be raised.
1684 If the :attr:`colander.null` value is passed to the serialize
1685 method of this class, the :attr:`colander.null` value will be
1686 returned.
1688 The subnodes of the :class:`colander.SchemaNode` that wraps
1689 this type are ignored.
1690 """
1692 def __init__(self, package):
1693 self.package = package
1695 def _pkg_resources_style(self, node, value):
1696 """ package.module:attr style """
1697 import pkg_resources
1699 if value.startswith('.') or value.startswith(':'):
1700 if not self.package:
1701 raise Invalid(
1702 node,
1703 _(
1704 'relative name "${val}" irresolveable without package',
1705 mapping={'val': value},
1706 ),
1707 )
1708 if value in ['.', ':']:
1709 value = self.package.__name__
1710 else:
1711 value = self.package.__name__ + value
1712 return pkg_resources.EntryPoint.parse('x=%s' % value).load(False)
1714 def _zope_dottedname_style(self, node, value):
1715 """ package.module.attr style """
1716 module = self.package and self.package.__name__ or None
1717 if value == '.':
1718 if self.package is None:
1719 raise Invalid(
1720 node,
1721 _(
1722 'relative name "${val}" irresolveable without package',
1723 mapping={'val': value},
1724 ),
1725 )
1726 name = module.split('.')
1727 else:
1728 name = value.split('.')
1729 if not name[0]:
1730 if module is None:
1731 raise Invalid(
1732 node,
1733 _(
1734 'relative name "${val}" irresolveable without '
1735 'package',
1736 mapping={'val': value},
1737 ),
1738 )
1739 module = module.split('.')
1740 name.pop(0)
1741 while not name[0]:
1742 module.pop()
1743 name.pop(0)
1744 name = module + name
1746 used = name.pop(0)
1747 found = __import__(used)
1748 for n in name:
1749 used += '.' + n
1750 try:
1751 found = getattr(found, n)
1752 except AttributeError: # pragma: no cover
1753 __import__(used)
1754 found = getattr(found, n)
1756 return found
1758 def serialize(self, node, appstruct):
1759 if appstruct is null:
1760 return null
1762 try:
1763 if isinstance(appstruct, types.ModuleType):
1764 return appstruct.__name__
1765 else:
1766 return '{0.__module__}.{0.__name__}'.format(appstruct)
1768 except AttributeError:
1769 raise Invalid(
1770 node, _('"${val}" has no __name__', mapping={'val': appstruct})
1771 )
1773 def deserialize(self, node, cstruct):
1774 if not cstruct:
1775 return null
1777 if not isinstance(cstruct, string_types):
1778 raise Invalid(
1779 node, _('"${val}" is not a string', mapping={'val': cstruct})
1780 )
1781 try:
1782 if ':' in cstruct:
1783 return self._pkg_resources_style(node, cstruct)
1784 else:
1785 return self._zope_dottedname_style(node, cstruct)
1786 except ImportError:
1787 raise Invalid(
1788 node,
1789 _(
1790 'The dotted name "${name}" cannot be imported',
1791 mapping={'name': cstruct},
1792 ),
1793 )
1796class DateTime(SchemaType):
1797 """ A type representing a Python ``datetime.datetime`` object.
1799 This type serializes python ``datetime.datetime`` objects to a
1800 `ISO8601 <https://en.wikipedia.org/wiki/ISO_8601>`_ string format.
1801 The format includes the date, the time, and the timezone of the
1802 datetime.
1804 The constructor accepts an argument named ``default_tzinfo`` which
1805 should be a Python ``tzinfo`` object. If ``default_tzinfo`` is not
1806 specified the default tzinfo will be equivalent to UTC (Zulu time).
1807 The ``default_tzinfo`` tzinfo object is used to convert 'naive'
1808 datetimes to a timezone-aware representation during serialization.
1809 If ``default_tzinfo`` is explicitly set to ``None`` then no default
1810 tzinfo will be applied to naive datetimes.
1812 You can adjust the error message reported by this class by
1813 changing its ``err_template`` attribute in a subclass on an
1814 instance of this class. By default, the ``err_template``
1815 attribute is the string ``Invalid date``. This string is used as
1816 the interpolation subject of a dictionary composed of ``val`` and
1817 ``err``. ``val`` and ``err`` are the unvalidatable value and the
1818 exception caused trying to convert the value, respectively. These
1819 may be used in an overridden err_template as ``${val}`` and
1820 ``${err}`` respectively as necessary, e.g. ``_('${val} cannot be
1821 parsed as an iso8601 date: ${err}')``.
1823 For convenience, this type is also willing to coerce
1824 ``datetime.date`` objects to a DateTime ISO string representation
1825 during serialization. It does so by using midnight of the day as
1826 the time, and uses the ``default_tzinfo`` to give the
1827 serialization a timezone.
1829 Likewise, for convenience, during deserialization, this type will
1830 convert ``YYYY-MM-DD`` ISO8601 values to a datetime object. It
1831 does so by using midnight of the day as the time, and uses the
1832 ``default_tzinfo`` to give the serialization a timezone.
1834 If the :attr:`colander.null` value is passed to the serialize
1835 method of this class, the :attr:`colander.null` value will be
1836 returned.
1838 The subnodes of the :class:`colander.SchemaNode` that wraps
1839 this type are ignored.
1840 """
1842 err_template = _('Invalid date')
1844 def __init__(self, default_tzinfo=iso8601.UTC, format=None):
1845 self.default_tzinfo = default_tzinfo
1846 self.format = format
1848 def serialize(self, node, appstruct):
1849 if not appstruct:
1850 return null
1852 # cant use isinstance; dt subs date
1853 if type(appstruct) is datetime.date:
1854 appstruct = datetime.datetime.combine(appstruct, datetime.time())
1856 if not isinstance(appstruct, datetime.datetime):
1857 raise Invalid(
1858 node,
1859 _(
1860 '"${val}" is not a datetime object',
1861 mapping={'val': appstruct},
1862 ),
1863 )
1865 if appstruct.tzinfo is None:
1866 appstruct = appstruct.replace(tzinfo=self.default_tzinfo)
1867 if not self.format:
1868 return appstruct.isoformat()
1869 else:
1870 return appstruct.strftime(self.format)
1872 def deserialize(self, node, cstruct):
1873 if not cstruct:
1874 return null
1876 try:
1877 if self.format:
1878 result = datetime.datetime.strptime(cstruct, self.format)
1879 if not result.tzinfo and self.default_tzinfo:
1880 result = result.replace(tzinfo=self.default_tzinfo)
1881 else:
1882 result = iso8601.parse_date(
1883 cstruct, default_timezone=self.default_tzinfo
1884 )
1885 except (ValueError, iso8601.ParseError) as e:
1886 raise Invalid(
1887 node, _(self.err_template, mapping={'val': cstruct, 'err': e})
1888 )
1889 return result
1892class Date(SchemaType):
1893 """ A type representing a Python ``datetime.date`` object.
1895 This type serializes python ``datetime.date`` objects to a
1896 `ISO8601 <https://en.wikipedia.org/wiki/ISO_8601>`_ string format.
1897 The format includes the date only.
1899 The constructor accepts no arguments.
1901 You can adjust the error message reported by this class by
1902 changing its ``err_template`` attribute in a subclass on an
1903 instance of this class. By default, the ``err_template``
1904 attribute is the string ``Invalid date``. This string is used as
1905 the interpolation subject of a dictionary composed of ``val`` and
1906 ``err``. ``val`` and ``err`` are the unvalidatable value and the
1907 exception caused trying to convert the value, respectively. These
1908 may be used in an overridden err_template as ``${val}`` and
1909 ``${err}`` respectively as necessary, e.g. ``_('${val} cannot be
1910 parsed as an iso8601 date: ${err}')``.
1912 For convenience, this type is also willing to coerce
1913 ``datetime.datetime`` objects to a date-only ISO string
1914 representation during serialization. It does so by stripping off
1915 any time information, converting the ``datetime.datetime`` into a
1916 date before serializing.
1918 Likewise, for convenience, this type is also willing to coerce ISO
1919 representations that contain time info into a ``datetime.date``
1920 object during deserialization. It does so by throwing away any
1921 time information related to the serialized value during
1922 deserialization.
1924 If the :attr:`colander.null` value is passed to the serialize
1925 method of this class, the :attr:`colander.null` value will be
1926 returned.
1928 The subnodes of the :class:`colander.SchemaNode` that wraps
1929 this type are ignored.
1930 """
1932 err_template = _('Invalid date')
1934 def __init__(self, format=None):
1935 self.format = format
1937 def serialize(self, node, appstruct):
1938 if not appstruct:
1939 return null
1941 if isinstance(appstruct, datetime.datetime):
1942 appstruct = appstruct.date()
1944 if not isinstance(appstruct, datetime.date):
1945 raise Invalid(
1946 node,
1947 _('"${val}" is not a date object', mapping={'val': appstruct}),
1948 )
1950 if self.format:
1951 return appstruct.strftime(self.format)
1952 return appstruct.isoformat()
1954 def deserialize(self, node, cstruct):
1955 if not cstruct:
1956 return null
1957 try:
1958 if self.format:
1959 result = datetime.datetime.strptime(cstruct, self.format)
1960 else:
1961 result = iso8601.parse_date(cstruct)
1962 result = result.date()
1963 except iso8601.ParseError as e:
1964 raise Invalid(
1965 node, _(self.err_template, mapping={'val': cstruct, 'err': e})
1966 )
1967 return result
1970class Time(SchemaType):
1971 """ A type representing a Python ``datetime.time`` object.
1973 .. note:: This type is new as of Colander 0.9.3.
1975 This type serializes python ``datetime.time`` objects to a
1976 `ISO8601 <https://en.wikipedia.org/wiki/ISO_8601>`_ string format.
1977 The format includes the time only.
1979 The constructor accepts no arguments.
1981 You can adjust the error message reported by this class by
1982 changing its ``err_template`` attribute in a subclass on an
1983 instance of this class. By default, the ``err_template``
1984 attribute is the string ``Invalid date``. This string is used as
1985 the interpolation subject of a dictionary composed of ``val`` and
1986 ``err``. ``val`` and ``err`` are the unvalidatable value and the
1987 exception caused trying to convert the value, respectively. These
1988 may be used in an overridden err_template as ``${val}`` and
1989 ``${err}`` respectively as necessary, e.g. ``_('${val} cannot be
1990 parsed as an iso8601 date: ${err}')``.
1992 For convenience, this type is also willing to coerce
1993 ``datetime.datetime`` objects to a time-only ISO string
1994 representation during serialization. It does so by stripping off
1995 any date information, converting the ``datetime.datetime`` into a
1996 time before serializing.
1998 Likewise, for convenience, this type is also willing to coerce ISO
1999 representations that contain time info into a ``datetime.time``
2000 object during deserialization. It does so by throwing away any
2001 date information related to the serialized value during
2002 deserialization.
2004 If the :attr:`colander.null` value is passed to the serialize
2005 method of this class, the :attr:`colander.null` value will be
2006 returned.
2008 The subnodes of the :class:`colander.SchemaNode` that wraps
2009 this type are ignored.
2010 """
2012 err_template = _('Invalid time')
2014 def serialize(self, node, appstruct):
2015 if isinstance(appstruct, datetime.datetime):
2016 appstruct = appstruct.time()
2018 if not isinstance(appstruct, datetime.time):
2019 if not appstruct:
2020 return null
2021 raise Invalid(
2022 node,
2023 _('"${val}" is not a time object', mapping={'val': appstruct}),
2024 )
2026 return appstruct.isoformat()
2028 def deserialize(self, node, cstruct):
2029 if not cstruct:
2030 return null
2031 try:
2032 result = iso8601.parse_date(cstruct)
2033 return result.time()
2034 except (iso8601.ParseError, TypeError) as e:
2035 err = e
2036 fmts = ['%H:%M:%S.%f', '%H:%M:%S', '%H:%M']
2037 for fmt in fmts:
2038 try:
2039 return datetime.datetime.strptime(cstruct, fmt).time()
2040 except (ValueError, TypeError):
2041 continue
2042 raise Invalid(
2043 node, _(self.err_template, mapping={'val': cstruct, 'err': err})
2044 )
2047class Enum(SchemaType):
2048 """A type representing a Python ``enum.Enum`` object.
2050 The constructor accepts three arguments named ``enum_cls``, ``attr``,
2051 and ``typ``.
2053 ``enum_cls`` is a mandatory argument and it should be a subclass of
2054 ``enum.Enum``. This argument represents the appstruct's type.
2056 ``attr`` is an optional argument. Its default is ``name``.
2057 It is used to pick a serialized value from an enum instance.
2058 A serialized value must be unique.
2060 ``typ`` is an optional argument, and it should be an instance of
2061 ``colander.SchemaType``. This argument represents the cstruct's type.
2062 If ``typ`` is not specified, a plain ``colander.String`` is used.
2063 """
2065 def __init__(self, enum_cls, attr=None, typ=None):
2066 self.enum_cls = enum_cls
2067 self.attr = 'name' if attr is None else attr
2068 self.typ = String() if typ is None else typ
2069 if self.attr == 'name':
2070 self.values = enum_cls.__members__.copy()
2071 else:
2072 self.values = {}
2073 for e in self.enum_cls.__members__.values():
2074 v = getattr(e, self.attr)
2075 if v in self.values:
2076 raise ValueError(
2077 '%r is not unique in %r', v, self.enum_cls
2078 )
2079 self.values[v] = e
2081 def serialize(self, node, appstruct):
2082 if appstruct is null:
2083 return null
2085 if not isinstance(appstruct, self.enum_cls):
2086 raise Invalid(
2087 node,
2088 _(
2089 '"${val}" is not a valid "${cls}"',
2090 mapping={'val': appstruct, 'cls': self.enum_cls.__name__},
2091 ),
2092 )
2094 return self.typ.serialize(node, getattr(appstruct, self.attr))
2096 def deserialize(self, node, cstruct):
2097 result = self.typ.deserialize(node, cstruct)
2098 if result is null:
2099 return null
2101 if result not in self.values:
2102 raise Invalid(
2103 node,
2104 _(
2105 '"${val}" is not a valid "${cls}"',
2106 mapping={'val': cstruct, 'cls': self.enum_cls.__name__},
2107 ),
2108 )
2109 return self.values[result]
2112def _add_node_children(node, children):
2113 for n in children:
2114 insert_before = getattr(n, 'insert_before', None)
2115 exists = node.get(n.name, _marker) is not _marker
2116 # use exists for microspeed; we could just call __setitem__
2117 # exclusively, but it does an enumeration that's unnecessary in the
2118 # common (nonexisting) case (.add is faster)
2119 if insert_before is None:
2120 if exists:
2121 node[n.name] = n
2122 else:
2123 node.add(n)
2124 else:
2125 if exists:
2126 del node[n.name]
2127 node.add_before(insert_before, n)
2130class _SchemaNode(object):
2131 """
2132 Fundamental building block of schemas.
2134 The constructor accepts these positional arguments:
2136 - ``typ``: The 'type' for this node. It should be an
2137 instance of a class that implements the
2138 :class:`colander.interfaces.Type` interface. If ``typ`` is not passed,
2139 a call to the ``schema_type()`` method on this class is made to
2140 get a default type. (When subclassing, ``schema_type()`` should
2141 be overridden to provide a reasonable default type).
2143 - ``*children``: a sequence of subnodes. If the subnodes of this
2144 node are not known at construction time, they can later be added
2145 via the ``add`` method.
2147 The constructor accepts these keyword arguments:
2149 - ``name``: The name of this node.
2151 - ``typ``: The 'type' for this node can optionally be passed in as a
2152 keyword argument. See the documentation for the positional arg above.
2154 - ``default``: The default serialization value for this node when
2155 not set. If ``default`` is :attr:`colander.drop`, the node
2156 will be dropped from schema serialization. If not provided,
2157 the node will be serialized to :attr:`colander.null`.
2159 - ``missing``: The default deserialization value for this node. If it is
2160 not provided, the missing value of this node will be the special marker
2161 value :attr:`colander.required`, indicating that it is considered
2162 'required'. When ``missing`` is :attr:`colander.required`, the
2163 ``required`` computed attribute will be ``True``. When ``missing`` is
2164 :attr:`colander.drop`, the node is dropped from the schema if it isn't
2165 set during deserialization.
2167 - ``missing_msg``: Optional error message to be used if the value is
2168 required and missing.
2170 - ``preparer``: Optional preparer for this node. It should be
2171 an object that implements the
2172 :class:`colander.interfaces.Preparer` interface.
2174 - ``validator``: Optional validator for this node. It should be
2175 an object that implements the
2176 :class:`colander.interfaces.Validator` interface.
2178 - ``after_bind``: A callback which is called after a clone of this
2179 node has 'bound' all of its values successfully. This callback
2180 is useful for performing arbitrary actions to the cloned node,
2181 or direct children of the cloned node (such as removing or
2182 adding children) at bind time. A 'binding' is the result of an
2183 execution of the ``bind`` method of the clone's prototype node,
2184 or one of the parents of the clone's prototype nodes. The
2185 deepest nodes in the node tree are bound first, so the
2186 ``after_bind`` methods of the deepest nodes are called before
2187 the shallowest. The ``after_bind`` callback should
2188 accept two values: ``node`` and ``kw``. ``node`` will be a
2189 clone of the bound node object, ``kw`` will be the set of
2190 keywords passed to the ``bind`` method.
2192 - ``title``: The title of this node. Defaults to a titleization
2193 of the ``name`` (underscores replaced with empty strings and the
2194 first letter of every resulting word capitalized). The title is
2195 used by higher-level systems (not by Colander itself).
2197 - ``description``: The description for this node. Defaults to
2198 ``''`` (the empty string). The description is used by
2199 higher-level systems (not by Colander itself).
2201 - ``widget``: The 'widget' for this node. Defaults to ``None``.
2202 The widget attribute is not interpreted by Colander itself, it
2203 is only meaningful to higher-level systems such as Deform.
2205 - ``insert_before``: if supplied, it names a sibling defined by a
2206 superclass for its parent node; the current node will be inserted
2207 before the named node. It is not useful unless a mapping schema is
2208 inherited from another mapping schema, and you need to control
2209 the ordering of the resulting nodes.
2211 Arbitrary keyword arguments remaining will be attached to the node
2212 object unmolested.
2213 """
2215 _counter = itertools.count()
2216 preparer = None
2217 validator = None
2218 default = null
2219 missing = required
2220 missing_msg = _('Required')
2221 name = ''
2222 raw_title = _marker # only changes if title is explicitly set
2223 title = _marker
2224 description = ''
2225 widget = None
2226 after_bind = None
2227 bindings = None
2229 def __new__(cls, *args, **kw):
2230 node = object.__new__(cls)
2231 node._order = next(cls._counter)
2232 node.children = []
2233 _add_node_children(node, cls.__all_schema_nodes__)
2234 return node
2236 def __init__(self, *arg, **kw):
2237 # bw compat forces us to treat first arg as type if not a _SchemaNode
2238 if 'typ' in kw:
2239 self.typ = kw.pop('typ')
2240 elif arg and not isinstance(arg[0], _SchemaNode):
2241 self.typ, arg = arg[0], arg[1:]
2242 else:
2243 self.typ = self.schema_type()
2244 _add_node_children(self, arg)
2246 # bw compat forces us to manufacture a title if one is not supplied
2247 title = kw.get('title', self.title)
2248 if title is _marker:
2249 name = kw.get('name', self.name)
2250 kw['title'] = name.replace('_', ' ').title()
2251 else:
2252 kw['raw_title'] = title
2254 self.__dict__.update(kw)
2256 @staticmethod
2257 def schema_type():
2258 raise NotImplementedError(
2259 'Schema node construction without a `typ` argument or '
2260 'a schema_type() callable present on the node class '
2261 )
2263 @property
2264 def required(self):
2265 """ A property which returns ``True`` if the ``missing`` value
2266 related to this node was not specified.
2268 A return value of ``True`` implies that a ``missing`` value wasn't
2269 specified for this node or that the ``missing`` value of this node is
2270 :attr:`colander.required`. A return value of ``False`` implies that
2271 a 'real' ``missing`` value was specified for this node."""
2272 if isinstance(self.missing, deferred): # unbound schema with deferreds
2273 return True
2274 return self.missing is required
2276 def serialize(self, appstruct=null):
2277 """ Serialize the :term:`appstruct` to a :term:`cstruct` based
2278 on the schema represented by this node and return the
2279 cstruct.
2281 If ``appstruct`` is :attr:`colander.null`, return the
2282 serialized value of this node's ``default`` attribute (by
2283 default, the serialization of :attr:`colander.null`).
2285 If an ``appstruct`` argument is not explicitly provided, it
2286 defaults to :attr:`colander.null`.
2287 """
2288 if appstruct is null:
2289 appstruct = self.default
2290 if isinstance(appstruct, deferred): # unbound schema with deferreds
2291 appstruct = null
2292 cstruct = self.typ.serialize(self, appstruct)
2293 return cstruct
2295 def flatten(self, appstruct):
2296 """ Create and return a data structure which is a flattened
2297 representation of the passed in struct based on the schema represented
2298 by this node. The return data structure is a dictionary; its keys are
2299 dotted names. Each dotted name represents a path to a location in the
2300 schema. The values of of the flattened dictionary are subvalues of
2301 the passed in struct."""
2302 flat = self.typ.flatten(self, appstruct)
2303 return flat
2305 def unflatten(self, fstruct):
2306 """ Create and return a data structure with nested substructures based
2307 on the schema represented by this node using the flattened
2308 representation passed in. This is the inverse operation to
2309 :meth:`colander.SchemaNode.flatten`."""
2310 paths = sorted(fstruct.keys())
2311 return self.typ.unflatten(self, paths, fstruct)
2313 def set_value(self, appstruct, dotted_name, value):
2314 """ Uses the schema to set a value in a nested datastructure from a
2315 dotted name path. """
2316 self.typ.set_value(self, appstruct, dotted_name, value)
2318 def get_value(self, appstruct, dotted_name):
2319 """ Traverses the nested data structure using the schema and retrieves
2320 the value specified by the dotted name path."""
2321 return self.typ.get_value(self, appstruct, dotted_name)
2323 def deserialize(self, cstruct=null):
2324 """ Deserialize the :term:`cstruct` into an :term:`appstruct` based
2325 on the schema, run this :term:`appstruct` through the
2326 preparer, if one is present, then validate the
2327 prepared appstruct. The ``cstruct`` value is deserialized into an
2328 ``appstruct`` unconditionally.
2330 If ``appstruct`` returned by type deserialization and
2331 preparation is the value :attr:`colander.null`, do something
2332 special before attempting validation:
2334 - If the ``missing`` attribute of this node has been set explicitly,
2335 return its value. No validation of this value is performed; it is
2336 simply returned.
2338 - If the ``missing`` attribute of this node has not been set
2339 explicitly, raise a :exc:`colander.Invalid` exception error.
2341 If the appstruct is not ``colander.null`` and cannot be validated , a
2342 :exc:`colander.Invalid` exception will be raised.
2344 If a ``cstruct`` argument is not explicitly provided, it
2345 defaults to :attr:`colander.null`.
2346 """
2347 appstruct = self.typ.deserialize(self, cstruct)
2349 if self.preparer is not None:
2350 # if the preparer is a function, call a single preparer
2351 if hasattr(self.preparer, '__call__'):
2352 appstruct = self.preparer(appstruct)
2353 # if the preparer is a list, call each separate preparer
2354 elif is_nonstr_iter(self.preparer):
2355 for preparer in self.preparer:
2356 appstruct = preparer(appstruct)
2358 if appstruct is null:
2359 appstruct = self.missing
2360 if appstruct is required:
2361 raise Invalid(
2362 self,
2363 _(
2364 self.missing_msg,
2365 mapping={'title': self.title, 'name': self.name},
2366 ),
2367 )
2369 if isinstance(appstruct, deferred):
2370 # unbound schema with deferreds
2371 raise Invalid(self, self.missing_msg)
2372 # We never deserialize or validate the missing value
2373 return appstruct
2375 if self.validator is not None:
2376 if isinstance(self.validator, deferred): # unbound
2377 raise UnboundDeferredError(
2378 "Schema node {node} has an unbound "
2379 "deferred validator".format(node=self)
2380 )
2381 self.validator(self, appstruct)
2382 return appstruct
2384 def add(self, node):
2385 """ Append a subnode to this node. ``node`` must be a SchemaNode."""
2386 self.children.append(node)
2388 def insert(self, index, node):
2389 """ Insert a subnode into the position ``index``. ``node`` must be
2390 a SchemaNode."""
2391 self.children.insert(index, node)
2393 def add_before(self, name, node):
2394 """ Insert a subnode into the position before the node named ``name``
2395 """
2396 for pos, sub in enumerate(self.children[:]):
2397 if sub.name == name:
2398 self.insert(pos, node)
2399 return
2400 raise KeyError('No such node named %s' % name)
2402 def get(self, name, default=None):
2403 """ Return the subnode associated with ``name`` or ``default`` if no
2404 such node exists."""
2405 for node in self.children:
2406 if node.name == name:
2407 return node
2408 return default
2410 def clone(self):
2411 """ Clone the schema node and return the clone. All subnodes
2412 are also cloned recursively. Attributes present in node
2413 dictionaries are preserved."""
2414 cloned = self.__class__(self.typ)
2415 cloned.__dict__.update(self.__dict__)
2416 cloned.children = [node.clone() for node in self.children]
2417 return cloned
2419 def bind(self, **kw):
2420 """ Resolve any deferred values attached to this schema node
2421 and its children (recursively), using the keywords passed as
2422 ``kw`` as input to each deferred value. This function
2423 *clones* the schema it is called upon and returns the cloned
2424 value. The original schema node (the source of the clone)
2425 is not modified."""
2426 cloned = self.clone()
2427 cloned._bind(kw)
2428 return cloned
2430 def _bind(self, kw):
2431 self.bindings = kw
2432 for child in self.children:
2433 child._bind(kw)
2434 names = dir(self)
2435 for k in names:
2436 v = getattr(self, k)
2437 if isinstance(v, deferred):
2438 v = v(self, kw)
2439 if isinstance(v, SchemaNode):
2440 self[k] = v
2441 else:
2442 setattr(self, k, v)
2443 if getattr(self, 'after_bind', None):
2444 self.after_bind(self, kw)
2446 def cstruct_children(self, cstruct):
2447 """ Will call the node's type's ``cstruct_children`` method with this
2448 node as a first argument, and ``cstruct`` as a second argument."""
2449 cstruct_children = getattr(self.typ, 'cstruct_children', None)
2450 if cstruct_children is None:
2451 warnings.warn(
2452 'The node type %s has no cstruct_children method. '
2453 'This method is required to be implemented by schema types '
2454 'for compatibility with Colander 0.9.9+. In a future Colander '
2455 'version, the absence of this method will cause an '
2456 'exception. Returning [] for compatibility although it '
2457 'may not be the right value.' % self.typ.__class__,
2458 DeprecationWarning,
2459 stacklevel=2,
2460 )
2461 return []
2462 return cstruct_children(self, cstruct)
2464 def __delitem__(self, name):
2465 """ Remove a subnode by name """
2466 for idx, node in enumerate(self.children[:]):
2467 if node.name == name:
2468 return self.children.pop(idx)
2469 raise KeyError(name)
2471 def __getitem__(self, name):
2472 """ Get a subnode by name. """
2473 val = self.get(name, _marker)
2474 if val is _marker:
2475 raise KeyError(name)
2476 return val
2478 def __setitem__(self, name, newnode):
2479 """ Replace a subnode by name. ``newnode`` must be a SchemaNode. If
2480 a subnode named ``name`` doesn't already exist, calling this method
2481 is the same as setting the node's name to ``name`` and calling the
2482 ``add`` method with the node (it will be appended to the children
2483 list)."""
2484 newnode.name = name
2485 for idx, node in enumerate(self.children[:]):
2486 if node.name == name:
2487 self.children[idx] = newnode
2488 return node
2489 self.add(newnode)
2491 def __iter__(self):
2492 """ Iterate over the children nodes of this schema node """
2493 return iter(self.children)
2495 def __contains__(self, name):
2496 """ Return True if subnode named ``name`` exists in this node """
2497 return self.get(name, _marker) is not _marker
2499 def __repr__(self):
2500 return '<%s.%s object at %d (named %s)>' % (
2501 self.__module__,
2502 self.__class__.__name__,
2503 id(self),
2504 self.name,
2505 )
2507 def raise_invalid(self, msg, node=None):
2508 """ Raise a :exc:`colander.Invalid` exception with the message
2509 ``msg``. ``node``, if supplied, should be an instance of a
2510 :class:`colander.SchemaNode`. If it is not supplied, ``node`` will
2511 be this node. Example usage::
2513 class CustomSchemaNode(SchemaNode):
2514 def validator(self, node, cstruct):
2515 if cstruct != 'the_right_thing':
2516 self.raise_invalid('Not the right thing')
2518 """
2519 if node is None:
2520 node = self
2521 raise Invalid(node, msg)
2524class _SchemaMeta(type):
2525 def __init__(cls, name, bases, clsattrs):
2526 nodes = []
2528 for name, value in clsattrs.items():
2529 if isinstance(value, _SchemaNode):
2530 delattr(cls, name)
2531 if not value.name:
2532 value.name = name
2533 if value.raw_title is _marker:
2534 value.title = name.replace('_', ' ').title()
2535 nodes.append((value._order, value))
2537 nodes.sort(key=lambda n: n[0])
2538 cls.__class_schema_nodes__ = [n[1] for n in nodes]
2540 # Combine all attrs from this class and its _SchemaNode superclasses.
2541 cls.__all_schema_nodes__ = []
2542 for c in reversed(cls.__mro__):
2543 csn = getattr(c, '__class_schema_nodes__', [])
2544 cls.__all_schema_nodes__.extend(csn)
2547# metaclass spelling compatibility across Python 2 and Python 3
2548SchemaNode = _SchemaMeta(
2549 'SchemaNode', (_SchemaNode,), {'__doc__': _SchemaNode.__doc__}
2550)
2553class Schema(SchemaNode):
2554 schema_type = Mapping
2557MappingSchema = Schema
2560class TupleSchema(SchemaNode):
2561 schema_type = Tuple
2564class SequenceSchema(SchemaNode):
2565 schema_type = Sequence
2567 def __init__(self, *args, **kw):
2568 SchemaNode.__init__(self, *args, **kw)
2569 if len(self.children) != 1:
2570 raise Invalid(
2571 self, 'Sequence schemas must have exactly one child node'
2572 )
2574 def clone(self):
2575 """ Clone the schema node and return the clone. All subnodes
2576 are also cloned recursively. Attributes present in node
2577 dictionaries are preserved."""
2579 # Cloning a ``SequenceSchema`` doesn't work with ``_SchemaNode.clone``.
2581 children = [node.clone() for node in self.children]
2582 cloned = self.__class__(self.typ, *children)
2584 attributes = self.__dict__.copy()
2585 attributes.pop('children', None)
2586 cloned.__dict__.update(attributes)
2587 return cloned
2590class deferred(object):
2591 """ A decorator which can be used to define deferred schema values
2592 (missing values, widgets, validators, etc.)"""
2594 def __init__(self, wrapped):
2595 try:
2596 functools.update_wrapper(self, wrapped)
2597 except AttributeError:
2598 # non-function (raises in Python 2)
2599 self.__doc__ = getattr(wrapped, '__doc__', None)
2600 self.wrapped = wrapped
2602 def __call__(self, node, kw):
2603 return self.wrapped(node, kw)
2606def _unflatten_mapping(
2607 node, paths, fstruct, get_child=None, rewrite_subpath=None
2608):
2609 if get_child is None:
2610 get_child = node.__getitem__
2611 if rewrite_subpath is None:
2613 def rewrite_subpath(subpath):
2614 return subpath
2616 node_name = node.name
2617 if node_name:
2618 prefix = node_name + '.'
2619 else:
2620 prefix = ''
2621 prefix_len = len(prefix)
2622 appstruct = {}
2623 subfstruct = {}
2624 subpaths = []
2625 curname = None
2626 for path in paths:
2627 if path == node_name:
2628 # flattened structs contain non-leaf nodes which are ignored
2629 # during unflattening.
2630 continue
2631 assert path.startswith(prefix), "Bad node: %s" % path
2632 subpath = path[prefix_len:]
2633 if '.' in subpath:
2634 name = subpath[: subpath.index('.')]
2635 else:
2636 name = subpath
2637 if curname is None:
2638 curname = name
2639 elif name != curname:
2640 subnode = get_child(curname)
2641 appstruct[curname] = subnode.typ.unflatten(
2642 subnode, subpaths, subfstruct
2643 )
2644 subfstruct = {}
2645 subpaths = []
2646 curname = name
2647 subpath = rewrite_subpath(subpath)
2648 subfstruct[subpath] = fstruct[path]
2649 subpaths.append(subpath)
2650 if curname is not None:
2651 subnode = get_child(curname)
2652 appstruct[curname] = subnode.typ.unflatten(
2653 subnode, subpaths, subfstruct
2654 )
2655 return appstruct
2658class instantiate(object):
2659 """
2660 A decorator which can be used to instantiate :class:`SchemaNode`
2661 elements inline within a class definition.
2663 All parameters passed to the decorator and passed along to the
2664 :class:`SchemaNode` during instantiation.
2665 """
2667 def __init__(self, *args, **kw):
2668 self.args, self.kw = args, kw
2670 def __call__(self, class_):
2671 return class_(*self.args, **self.kw)