init
This commit is contained in:
158
spider/validator/validator.py
Normal file
158
spider/validator/validator.py
Normal file
@ -0,0 +1,158 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
import six
|
||||
from . import exceptions
|
||||
from .fields import BaseField, EMPTY_VALUE, create_field, DictField
|
||||
from .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():
|
||||
self.errors[name] = exceptions.FieldRequiredError()
|
||||
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 exceptions.FieldValidationError as e:
|
||||
self.errors[name] = e
|
||||
|
||||
if self.errors:
|
||||
return
|
||||
try:
|
||||
data = self.validate(data)
|
||||
except exceptions.ValidationError as e:
|
||||
self.errors['__data_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 exceptions.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)
|
||||
Reference in New Issue
Block a user