validationpyramidcolandercornice

pyramid/cornice validators and colander schema


I have a cornice API with a view that has validators and a colander schema. I can't get access to colander validated data (request.validated) in my validator.

I pass my data through colander. My colander schema looks something like this:

from colander import (
    MappingSchema,
    SchemaNode,
    String
)

class UserSchemaRecord(MappingSchema):
    username = SchemaNode(String())
    password = SchemaNode(String())

class UserSchema(MappingSchema):
    user = UserSchemaRecord()

It adds a sanitized version of the request data into request.validated['user'] that I can then access in my view like this.

@view(renderer='json', validators=(valid_token, valid_new_username), schema=UserSchema)
def collection_post(self):
    """Adds a new user"""
    user_data = self.request.validated['user']
    user = UserModel(**user_data)
    DBSession.add(user)
    DBSession.flush()
    return {'user': user}

However, I also need to check that the request provides a unique username and return an error if the username is already taken. I'd like to do this with a validator (valid_new_username) but when I try to access request.validated['user'] in my validator the data aren't there.

def valid_new_username(request):
    user = request.validated['user'] # this line fails with a KeyError
    username = user['username']
    if UserModel.get_by_username(username):
        request.errors.add('body', 'username', "User '%s' already exists!" % username)
        request.errors.status = 409 # conflict

It looks like the validator is called before the data have been extracted. I don't really want to access the request json_body data directly before passing them through colander. Is there a way I can change the ordering of the schema/validator?

The alternative is to do the checking directly in my view callable. Is that a good option? Are validators not supposed to work with colander validated data?


Solution

  • Not sure if it's your problem, but if the data is not valid in the first place (Colander found errors), then it will not be available as request.validated['key'] in the validators.

    You can use a decorator like this if you want to apply the validator only if the data has passed Colander validation.

    def when_valid(validator):
        """Decorator for validation functions.
        Only try validation if no errors already.
    
        """
        def inner(request):
            if len(request.errors) > 0:
                return
            validator(request)
        return inner
    
    @when_valid
    def valid_new_username(request):
        pass # your validation here