Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested validators: colander.All and colander.Any #194

Closed
adundovi opened this issue Sep 19, 2014 · 1 comment
Closed

Nested validators: colander.All and colander.Any #194

adundovi opened this issue Sep 19, 2014 · 1 comment

Comments

@adundovi
Copy link

Hi,

it would be great if meta-validators (All, Any and so) could be nested in each other.
For example:

   validator=colander.All(
                           colander.Any( 
                                            first_validator,
                                            second_validator 
                                       ),
                           third_validator
                     )

The latest version doesn't support this because an error occurs during the "propagation" of result message in asdict:

    errors['.'.join(keyparts)] = '; '.join(interpolate(msgs))

(a similar problem is fixed here a6b85dd )

Is there any other way to do it? Tnx.

@novikovav
Copy link

Let me elaborate this issue on colander.All example (colander.Any inherits it and has the same problem):
Currently colander.All collects all colander.Invalid raised by it's internal validators and composes them into single colander.Invalid with this code:

exc = Invalid(node, [exc.msg for exc in excs])
for e in excs:
     exc.children.extend(e.children)
raise exc

The problem here is that colander.Invalid msg property can be either string OR iterable and this code doesn't care about it. As a result, when we have two nested colander.All validators and inner one raised colander.Invalid error, outer validator catches it and adds (not extends!) msg property of inner colander.Invalid to the newly created instance of colander.Invalid, which means that list of messages of outer exception contains list of messages from inner exception. As a result, when colander.Invalid.asdict() called for the outer exception, message won't be interpolated.

Here's how you can reproduce it:

import colander
import translationstring


class DummySchemaNode(object):
    def __init__(self, typ, name='', exc=None, default=None):
        self.typ = typ
        self.name = name
        self.exc = exc
        self.required = default is None
        self.default = default
        self.children = []

    def deserialize(self, val):
        from colander import Invalid

        if self.exc:
            raise Invalid(self, self.exc)
        return val

    def serialize(self, val):
        from colander import Invalid

        if self.exc:
            raise Invalid(self, self.exc)
        return val

    def __getitem__(self, name):
        for child in self.children:
            if child.name == name:
                return child


_ = translationstring.TranslationStringFactory('fnord')


def dummy_validator(node, cstruct):
    message = _("${val} <- should be interpolated", mapping={'val': "interpolated"})
    raise colander.Invalid(node, message)


try:
    validator = colander.All(colander.All(dummy_validator, dummy_validator, dummy_validator))
    validator(DummySchemaNode(None, name='Node name'), "doesn't matter")
except colander.Invalid as e:
    error_dict = e.asdict(separator=None)
    print(error_dict)

Following output will be produced:
{'Node name': [['${val} <- should be interpolated', '${val} <- should be interpolated', '${val} <- should be interpolated']]}

IMO, proper fix is to force colander.All to test if raised exception's msg property is string or iterable. It can be done this way, for example:


class All(object):
    """
    Colander validator which succeeds if ALL inner validators succeed.
    """
    def __init__(self, *validators):
        """Constructor."""
        self._validators = validators

    def __call__(self, node, value):
        """
        Call all inner validators.
        """
        exceptions = []
        try:
            for validator in self._validators:
                validator(node, value)
        except colander.Invalid as exc:
            exceptions.append(exc)

        if exceptions:
            children = []
            messages = []
            for exception in exceptions:
                if colander.is_nonstr_iter(exception.msg):
                    messages.extend(exception.msg)
                else:
                    messages.append(exception.msg)
                children.extend(exception.children)

            exc = colander.Invalid(node, messages)
            exc.children.extend(children)
            raise exc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants