-
Notifications
You must be signed in to change notification settings - Fork 132
Descriptors
In the MongoKit philosophy, the structure must be simple, clear and readable. So all descriptors (validation, requirement, default values, etc.) are described outside of the structure. Descriptors can be combined and can apply the same field.
This descriptor describes the required fields:
class MyDoc(Document):
structure = {
'bar': basestring,
'foo':{
'spam': basestring,
'eggs': int,
}
}
required = ['bar', 'foo.spam']
If you want to reach nested fields, just use the dot notation.
This descriptors allow to specify a default value at the creation of the document:
class MyDoc(Document):
structure = {
'bar': basestring,
'foo':{
'spam': basestring,
'eggs': int,
}
}
default_values = {'bar': 'hello', 'foo.eggs': 4}
Note that the default value must be a valid type. Again, to reach nested fields, use dot notation.
This descriptor bring a validation layer to a field. It take a function which
returns a False
if the validation fails, True
otherwise:
import re
def email_validator(value):
email = re.compile(r'(?:^|\s)[-a-z0-9_.]+@(?:[-a-z0-9]+\.)+[a-z]{2,6}(?:\s|$)',re.IGNORECASE)
return bool(email.match(value))
class MyDoc(Document):
structure = {
'email': basestring,
'foo': {
'eggs': int,
}
}
validators = {
'email': email_validator,
'foo.eggs': lambda x: x > 10
}
You can add custom message in your validators by throwing a ValidationError
instead of returning false.
def email_validator(value):
email = re.compile(r'(?:^|\s)[-a-z0-9_.]+@(?:[-a-z0-9]+\.)+[a-z]{2,6}(?:\s|$)',re.IGNORECASE)
if not bool(email.match(value))
raise ValidationError('%s is not a valid email')
Make sure to include one '%s' in the message. This will be used to describes the failing field name.
You can also pass params to your validator by wrapping it in a class:
class MinLengthValidator(object):
def __init__(self, min_length):
self.min_length = min_length
def __call__(self, value):
if len(value) >= self.min_length:
return True
else:
raise Exception('%s must be at least ' + str(self.min_length) + ' characters long.')
class Client(Document):
structure = {
'first_name': basestring
}
validators = { 'first_name': MinLengthValidator(2) }
In this example, first_name
must contain at least 2 characters.
If the use of a validator is not enough, you can overload the validation method to fit your needs.
Example the following document:
class MyDoc(Document):
structure = {
'foo': int,
'bar': int,
'baz': basestring
}
We want to be sure that before saving our object, foo is greater than bar. To do that, we just overload the validation method:
def validate(self, *args, **kwargs):
assert self['foo'] > self['bar']
super(MyDoc, self).validate(*args, **kwargs)
Once your application is ready for production and you are sure that the data is
consistent, you might want to skip the validation layer. This will make
MongoKit significantly faster (as fast as pymongo). In order to do that, just
set the skip_validation
attribute to True
.
TIP: It is a good idea to create a RootDocument
and to inherit all your
document classes from it. This will allow you to control the default behavior
of all your objects by setting attributes on the RootDocument:
class RootDocument(Document):
structure = {}
skip_validation = True
use_autorefs = True
class MyDoc(RootDocument):
structure = {
'foo': int
}
Note that you can always force the validation at any moment on saving even if
skip_validation
is True
:
>>> con.register([MyDoc]) # No need to register RootDocument as we do not instantiate it
>>> mydoc = tutorial.MyDoc()
>>> mydoc['foo'] = 'bar'
>>> mydoc.save(validate=True)
Traceback (most recent call last):
...
SchemaTypeError: foo must be an instance of int not basestring
By default, when validation is on, each error raises an Exception. Sometimes,
you just want to collect any errors in one place. This is possible by setting
the raise_validation_errors
to False. This causes any errors to be stored
in the validation_errors
attribute:
class MyDoc(Document):
raise_validation_errors = False
structure = {
'foo': set,
}
>>> con.register([MyDoc])
>>> doc = tutorial.MyDoc()
>>> doc.validate()
>>> doc.validation_errors
{'foo': [StructureError("<type 'set'> is not an authorized type",), RequireFieldError('foo is required',)]}
validation_errors
is a dictionary which take the field name as key and the
python exception as value. Here foo has two issues: a structure one (set
is not an authorized type) and field requirement error (foo
is required
field but is not specified).
>>> doc.validation_errors['foo'][0].message
"<type 'set'> is not an authorized type"
If the value of key is not known but we want to validate some deeper structure, we use the "$" descriptor:
class MyDoc(Document):
structure = {
'key': {
unicode: {
'first': int,
'secondpart: {
unicode: int
}
}
}
}
required_fields = ["key1.$unicode.bla"]
Note that if you use a python type as a key in structure, generate_skeleton won't be able to build the entire underlying structure:
>>> con.register([MyDoc])
>>> tutorial.MyDoc() == {'key1': {}, 'bla': None}
True
So, neither default_values nor validators will work.