"""
Serializers
"""
import datetime
import collections
from dateutil import parser
from google.appengine.ext.ndb import ModelAttribute
from exceptions import ValidationError, SerializerError
from helpers import get_key_from_urlsafe, KEY_CLASS
def get_attribute(obj, name):
if issubclass(obj.__class__, collections.Mapping):
if name in obj:
return obj[name]
attr = getattr(obj, name, None)
if attr is None:
raise SerializerError('Missing attribute {} on {}'.format(
name, obj
))
return attr
def has_attribute(obj, name):
if issubclass(obj.__class__, collections.Mapping):
if name in obj:
return True
attr = getattr(obj, name, None)
if attr is None:
return False
return True
[docs]class Field(object):
base_type = None
def __init__(self, *args, **kwargs):
self.is_many = kwargs.get('many', False)
self.object = None if len(args) == 0 else args[0]
self._data = kwargs.get('data', None)
self.args = args
self.kwargs = kwargs
self.required = kwargs.get('required', False)
self.source = kwargs.get('source', None)
def input(self, value):
if self.base_type is not None:
try:
return self.base_type(value)
except Exception:
raise ValidationError('Field: Unknown {} value: {}'.format(
self.__class__.__name__, value))
return value
def input_many(self, values):
if self.is_many:
return [
self.input(value)
for value in values
]
else:
return self.input(values)
def output(self, value):
return value
def output_many(self, values):
if self.is_many:
return [
self.output(value)
for value in values
]
else:
return self.output(values)
def validate_base_type(self, value):
if self.base_type is not None:
return isinstance(value, self.base_type)
return True
[docs] def serialize(self):
"""
---> si y'a un data={} on veux verifier que les fields sont bien types, pas de manquant etc..
---> si pas de data, on a un objet et on veux le mettre en dict
"""
if self._data is None and self.object is not None:
return self.output_many(self.object)
elif self.object is None and self._data is not None:
return self.input_many(self._data)
else:
raise SerializerError('If no positional argument are passed, you must use data= kwarg')
@property
def data(self):
if self.is_many:
return [
self.__class__(obj, data=self._data).serialize()
for obj in self.object
]
return self.serialize()
[docs]class BooleanField(Field):
base_type = bool
TRUE_VALUES = [
't', 'T',
'y', 'Y', 'yes', 'YES',
'true', 'True', 'TRUE',
'on', 'On', 'ON',
'1', 1,
True
]
FALSE_VALUES = [
'f', 'F',
'n', 'N', 'no', 'NO',
'false', 'False', 'FALSE',
'off', 'Off', 'OFF',
'0', 0, 0.0,
False
]
def input(self, value):
if value in self.TRUE_VALUES:
return True
elif value in self.FALSE_VALUES:
return False
raise ValidationError('Field: Unknown Boolean value: {}'.format(value))
[docs]class IntegerField(Field):
base_type = int
[docs]class StringField(Field):
base_type = str
[docs]class FloatField(Field):
base_type = float
[docs]class KeyField(Field):
base_type = KEY_CLASS
def input(self, value):
try:
return get_key_from_urlsafe(value)
except Exception:
raise ValidationError('Field: Unknown Key value: {}'.format(value))
def output(self, value):
if not self.validate_base_type(value) and value is not None:
raise ValidationError('Field: Needs a Key class, {} given'.format(value.__class__.__name__))
return value.urlsafe() if value else None
[docs]class DatetimeField(Field):
base_type = datetime.datetime
def input(self, value):
try:
if 'format' in self.kwargs:
return datetime.datetime.strptime(value, self.kwargs['format'])
return parser.parse(value)
except Exception:
raise ValidationError('Field: Unknown {} value: {}'.format(
self.__class__.__name__, value))
def output(self, value):
if 'format' in self.kwargs:
return value.strftime(value, self.kwargs['format'])
return value.isoformat()
class MethodField(Field):
pass
TYPE_MAPPING = {
'IntegerProperty': IntegerField,
'FloatProperty': FloatField,
'BooleanProperty': BooleanField,
'StringProperty': StringField,
'TextProperty': StringField,
'DateTimeProperty': DatetimeField,
'KeyProperty': KeyField
}
READ_ONLY_FIELDS = [
MethodField
]
[docs]class Serializer(Field):
def get_class_fields(self, read_only=False):
_attrs = {}
for key, value in self.__class__.__dict__.iteritems():
if issubclass(value.__class__, Field):
if read_only is False:
if value.__class__ not in READ_ONLY_FIELDS:
_attrs[key] = value
else:
_attrs[key] = value
return _attrs
def input(self, data):
payload = {}
for attr, field in self.get_class_fields().iteritems():
_source = field.source or attr
if not has_attribute(data, _source) and field.required:
raise SerializerError('{} requires field {} but is missing'.format(
self.__class__.__name__,
attr
))
if has_attribute(data, _source):
payload.update({
attr: field.input_many(get_attribute(data, _source))
})
return payload
def output(self, obj):
payload = {}
for attr, field in self.get_class_fields(read_only=True).iteritems():
_source = field.source or attr
if field.__class__ == MethodField:
_method = getattr(self, 'get_{}'.format(attr), None)
if _method is None:
raise SerializerError('missing method get_{} for field MethodField'.format(attr))
payload.update({
attr: _method(obj)
})
else:
payload.update({
attr: field.output_many(getattr(obj, _source))
})
payload['key'] = obj.key.urlsafe()
return payload
[docs]class ModelSerializer(Serializer):
def get_class_fields(self, read_only=False):
_attrs = {}
_meta = getattr(self, 'Meta', None)
if _meta.fields == '__all__':
_fields = []
for key, val in dict(_meta.model.__dict__).iteritems():
if issubclass(val.__class__, ModelAttribute):
_fields.append(key)
else:
_fields = _meta.fields
for field in _fields:
if field == 'key':
_attrs[field] = KeyField()
else:
try:
if isinstance(_meta.model, tuple):
_meta.model = _meta.model[0]
if field.capitalize() in _meta.model._properties:
_attrs[field] = TYPE_MAPPING[_meta.model._properties[field.capitalize()].__class__.__name__]()
else:
_attrs[field] = TYPE_MAPPING[_meta.model._properties[field].__class__.__name__]()
except KeyError:
if getattr(self, 'get_{}'.format(field), None) is not None:
_attrs[field] = MethodField()
else:
raise SerializerError('Field {} doest not exists on {} or its type is not supported yet'.format(
field, _meta.model.__name__
))
return _attrs