# -*- coding: utf-8 -*- from __future__ import unicode_literals import six from library.validator.exceptions import ValidationError, FieldRequiredError, FieldValidationError, BaseValidationError from library.validator.fields import BaseField, EMPTY_VALUE, create_field, DictField from library.validator.utils import force_str class ValidatorMetaClass(type): def __new__(cls, cls_name, bases, attrs): fields_map = dict() parent_fields_map = dict() for parent in bases: if hasattr(parent, '_FIELDS_MAP'): parent_fields_map.update(parent._FIELDS_MAP) for name, value in six.iteritems(attrs): if isinstance(value, BaseField): fields_map[name] = value for name in fields_map: attrs.pop(name, None) parent_fields_map.update(fields_map) attrs['_FIELDS_MAP'] = parent_fields_map return super(ValidatorMetaClass, cls).__new__(cls, cls_name, bases, attrs) @six.add_metaclass(ValidatorMetaClass) class Validator(object): """ a data validator like Django ORM """ def __init__(self, raw_data): """ :param raw_data: unvalidate data """ assert isinstance(raw_data, dict), '"raw_data" must be a dict, not "{}"'.format(type(raw_data).__name__) self.raw_data = raw_data self.validated_data = None self.errors = {} def _validate(self): data = {} for name, field in six.iteritems(self._FIELDS_MAP): value = self.raw_data.get(name, field.get_default()) if value is EMPTY_VALUE: if field.is_required(): # 对其进行改造 如果这个地方存在自定义的FieldRequiredError的情况下 上报自定义的 self.errors[field.unicodeName() or name] = field.requiredError() continue # dont need to validate None if value is None: data[name] = None continue try: validated_value = field.validate(value) internal_value = field.to_internal(validated_value) field_validator = getattr( self, 'validate_{}'.format(name), None) if field_validator and callable(field_validator): field_validator(internal_value) data[name] = internal_value except FieldValidationError as e: self.errors[field.unicodeName() or name] = e if self.errors: return try: data = self.validate(data) except BaseValidationError as e: self.errors['error'] = e if not self.errors: self.validated_data = data def is_valid(self, raise_error=False): self._validate() if raise_error and self.errors: raise ValidationError(self.errors) return False if self.errors else True def validate(self, data): """ model-level validate. sub-class can override this method to validate data, return modified data """ return data @property def str_errors(self): errors = dict() for name, error in six.iteritems(self.errors): errors[name] = error.get_detail() return errors @classmethod def to_dict(cls): """ format Validator to dict """ d = dict() for name, field in six.iteritems(cls._FIELDS_MAP): field_info = field.to_dict() d[name] = field_info return d @classmethod def mock_data(cls): """ return random mocking data. mocking data will be valid in most case, but it maybe can't pass from your own `validate` method or `validator` """ mocking_data = {} for name, field in six.iteritems(cls._FIELDS_MAP): mocking_data[name] = field.mock_data() return mocking_data def _format(self): fields = [] for name, field in six.iteritems(self._FIELDS_MAP): fields.append('{0}:{1}'.format(name, field.FIELD_TYPE_NAME)) fields = ','.join(fields) if len(fields) > 103: fields = fields[:100] return '<{0}: {1}>'.format(self.__class__.__name__, fields) def __str__(self): return self._format() def __repr__(self): return self._format() def create_validator(data_struct_dict, name=None): """ create a Validator instance from data_struct_dict :param data_struct_dict: a dict describe validator's fields, like the dict `to_dict()` method returned. :param name: name of Validator class :return: Validator instance """ if name is None: name = 'FromDictValidator' attrs = {} for field_name, field_info in six.iteritems(data_struct_dict): field_type = field_info['type'] if field_type == DictField.FIELD_TYPE_NAME and isinstance(field_info.get('validator'), dict): field_info['validator'] = create_validator(field_info['validator']) attrs[field_name] = create_field(field_info) name = force_str(name) return type(name, (Validator, ), attrs)