So far I am using colander to validate the data in my aiohttp application.
The problem I face is that I don't know how to do "deep" validation.
Given the following schema:
import colander
class User(colander.MappingSchema):
username = colander.SchemaNode(colander.String())
password = colander.SchemaNode(colander.String())
confirmation = colander.SchemaNode(colander.String())
I do both validate that the input datastructure has all the required fields are there (constraints are minimal for the sake of clarity) but I also need to check that:
username
is not already taken by another userpassword
and confirmation
are the sameSo in my controllers, the code looks like the following pseudo code:
def create_user(request):
user = await request.json()
schema = User()
# do schema validation
try:
user = schema.deserialize(user)
except colander.Invalid, exc:
response = dict(
status='error',
errors=errors.asdict()
)
return json_response(response)
else:
# check password and confirmation are the same
if user['password'] != user['confirmation']:
response = dict(
status='error'
errors=dict(confirmation="doesn't match password")
)
return json_response(response)
# check the user is not already used by another user
# we want usernames to be unique
if user_exists(user['user']):
response = dict(
status='error',
errors=dict(username='Choose another username')
)
return json_response(response)
return json_response(dict(status='ok'))
Basically there is two kinds of validation. Is it possible to have both logic in single colander schema? Is it a good pattern?
Obviously it's a matter of taste but IMHO it's better to keep data validation separate from application logic.
You'll also run into a few problems trying to confirm that the username is unique:
async
method which checks the user exists.create_user
with the same username cannot possibly create two users with the same username.Checking passwords match is another story, that doesn't require any knowledge about the rest of the world and should fairly trivial with colander. I'm not expert on colander, but it looks like you can use a deferred validator to check the two passwords match.
A few other notes on your code:
create_user
should be an async
methoduser_exists
should be async tooon conflict
or equivalent to catch a duplicate user as you create them rather than checking they exist first