Source code for nanomongo.field

import copy
import datetime

import six

from bson import DBRef, ObjectId

from .errors import ValidationError
from .util import check_keys


[docs]class Field(object): """Instances of this class is used to define field types and automatically create validators:: field_name = Field(str, default='cheeseburger') foo = Field(datetime, auto_update=True) bar = Field(list, required=False) """ allowed_types = (bool, int, float, six.binary_type, six.text_type, list, dict, datetime.datetime, DBRef, ObjectId) # kwarg_name : kwarg_input_validator dictionaries allowed_kwargs = { 'default': lambda v: True, 'required': lambda v: isinstance(v, bool), } extra_kwargs = { datetime.datetime: {'auto_update': lambda v: isinstance(v, bool)}, DBRef: {'document_class': lambda v: isinstance(v, six.string_types)}, }
[docs] def __init__(self, *args, **kwargs): """Field kwargs are checked for correctness and field validator is set, along with other attributes such as ``required`` and ``auto_update`` :Keyword Arguments: - `default`: default field value, must pass type check, can be a ``callable`` - `required`: if ``True`` field must exist and not be ``None`` (default: ``True``) - `auto_update`: set value to ``datetime.utcnow()`` before inserts/saves; only valid for datetime fields (default: ``False``) """ if not args: raise TypeError('Field definition incorrect, please provide type') elif not isinstance(args[0], type): raise TypeError('Field input not a type') self.data_type = args[0] if ((self.data_type not in self.allowed_types and not issubclass(self.data_type, self.allowed_types))): raise TypeError('Field input type %s is not allowed' % self.data_type) self.check_kwargs(kwargs, self.data_type) # attributes if 'auto_update' in kwargs and kwargs['auto_update']: self.auto_update = self.data_type.utcnow # datetime.datetime if 'document_class' in kwargs and kwargs['document_class']: self.document_class = kwargs['document_class'] self.validator = self.generate_validator(self.data_type, **kwargs) self.required = kwargs['required'] if 'required' in kwargs else True if 'default' in kwargs: self.default_value = kwargs['default'] if not callable(self.default_value): validation_failed = False try: self.validator(self.default_value) except ValidationError as e: new_err = ('default value "%s"' % kwargs['default']) + ''.join(e.args) validation_failed = True if validation_failed: raise TypeError(new_err) # check if dict/list type and wrap copy in callable if isinstance(self.default_value, (dict, list)): def default_value_wrapper(): return copy.deepcopy(kwargs['default']) self.default_value = default_value_wrapper
[docs] @classmethod def check_kwargs(cls, kwargs, data_type): """Check keyword arguments & their values given to ``Field`` constructor such as ``default``, ``required`` ... """ err_str = data_type.__name__ + ': %s keyword argument not allowed or "%s" value invalid' for k, v in kwargs.items(): # kwargs allowed for all data_types if k in cls.allowed_kwargs: if not cls.allowed_kwargs[k](v): # run kwarg validator raise TypeError(err_str % (k, v)) # type specific kwargs elif data_type in cls.extra_kwargs and k in cls.extra_kwargs[data_type]: if not cls.extra_kwargs[data_type][k](v): # run kwarg validator raise TypeError(err_str % (k, v)) else: raise TypeError(err_str % (k, v))
[docs] def generate_validator(self, t, **kwargs): """Generates and returns validator function ``(value_to_check, field_name='')``. ``field_name`` kwarg is optional, used for better error reporting. """ def validator(val, field_name=''): if val is None and 'required' in kwargs and not kwargs['required']: return True elif val is None: raise ValidationError('%s: None is not allowed (field required)' % field_name) if not isinstance(val, t): raise ValidationError('%s: "%s" not an instance of %s but an instance of %s' % (field_name, val, t, type(val))) if isinstance(val, dict): check_keys(val) # check against . & $ in keys return True return validator