from lollipop.errors import ValidationError, ValidationErrorBuilder, \
ErrorMessagesMixin, merge_errors
from lollipop.utils import is_list, is_dict, call_with_context
from lollipop.compat import string_types, int_types, iteritems
import datetime
__all__ = [
'MISSING',
'Type',
'Any',
'String',
'Integer',
'Float',
'Boolean',
'List',
'Tuple',
'Dict',
'OneOf',
'type_name_hint',
'dict_value_hint',
'Field',
'ConstantField',
'AttributeField',
'MethodField',
'FunctionField',
'Object',
'Optional',
'LoadOnly',
'DumpOnly',
]
class MissingType(object):
def __repr__(self):
return '<MISSING>'
#: Special singleton value (like None) to represent case when value is missing.
MISSING = MissingType()
[docs]class Type(ErrorMessagesMixin, object):
"""Base class for defining data types.
:param list validate: A validator or list of validators for this data type.
Validator is a callable that takes serialized data and raises
:exc:`~lollipop.errors.ValidationError` if data is invalid.
Validator return value is ignored.
"""
default_error_messages = {
'invalid': 'Invalid value type',
'required': 'Value is required',
}
def __init__(self, validate=None, *args, **kwargs):
super(Type, self).__init__(*args, **kwargs)
if validate is None:
validate = []
elif callable(validate):
validate = [validate]
self._validators = validate
[docs] def validate(self, data, context=None):
"""Takes serialized data and returns validation errors or None.
:param data: Data to validate.
:param context: Context data.
"""
try:
self.load(data, context)
return {}
except ValidationError as ve:
return ve.messages
[docs] def load(self, data, context=None):
"""Deserialize data from primitive types. Raises
:exc:`~lollipop.errors.ValidationError` if data is invalid.
:param data: Data to deserialize.
:param context: Context data.
"""
errors_builder = ValidationErrorBuilder()
for validator in self._validators:
try:
call_with_context(validator, context, data)
except ValidationError as ve:
errors_builder.add_errors(ve.messages)
errors_builder.raise_errors()
return data
[docs] def dump(self, value, context=None):
"""Serialize data to primitive types. Raises
:exc:`~lollipop.errors.ValidationError` if data is invalid.
:param value: Value to serialize.
:param context: Context data.
"""
return value
def __repr__(self):
return '<{klass}>'.format(klass=self.__class__.__name__)
[docs]class Any(Type):
"""Any type. Does not transform/validate given data."""
pass
class Number(Type):
num_type = float
default_error_messages = {
'invalid': 'Value should be number',
}
def _normalize(self, value):
try:
return self.num_type(value)
except (TypeError, ValueError):
self._fail('invalid')
def load(self, data, *args, **kwargs):
if data is MISSING or data is None:
self._fail('required')
return super(Number, self).load(self._normalize(data), *args, **kwargs)
def dump(self, value, *args, **kwargs):
if value is MISSING or value is None:
self._fail('required')
return super(Number, self).dump(self._normalize(value), *args, **kwargs)
[docs]class Integer(Number):
"""An integer type."""
num_type = int
default_error_messages = {
'invalid': 'Value should be integer'
}
[docs]class Float(Number):
"""A float type."""
num_type = float
default_error_messages = {
'invalid': 'Value should be float'
}
[docs]class String(Type):
"""A string type."""
default_error_messages = {
'invalid': 'Value should be string',
}
def load(self, data, *args, **kwargs):
if data is MISSING or data is None:
self._fail('required')
if not isinstance(data, string_types):
self._fail('invalid')
return super(String, self).load(data, *args, **kwargs)
def dump(self, value, *args, **kwargs):
if value is MISSING or value is None:
self._fail('required')
if not isinstance(value, string_types):
self._fail('invalid')
return super(String, self).dump(str(value), *args, **kwargs)
[docs]class Boolean(Type):
"""A boolean type."""
default_error_messages = {
'invalid': 'Value should be boolean',
}
def load(self, data, *args, **kwargs):
if data is MISSING or data is None:
self._fail('required')
if not isinstance(data, bool):
self._fail('invalid')
return super(Boolean, self).load(data, *args, **kwargs)
def dump(self, value, *args, **kwargs):
if value is MISSING or value is None:
self._fail('required')
if not isinstance(value, bool):
self._fail('invalid')
return super(Boolean, self).dump(bool(value), *args, **kwargs)
class DateTime(Type):
"""A date and time type which serializes into string.
:param str format: Format string (see :func:`datetime.datetime.strptime`) or
one of predefined format names (e.g. 'iso8601', 'rfc3339', etc.
See :const:`~DateTime.FORMATS`)
:param kwargs: Same keyword arguments as for :class:`Type`.
"""
FORMATS = {
'iso': '%Y-%m-%dT%H:%M:%S%Z', # shortcut for iso8601
'iso8601': '%Y-%m-%dT%H:%M:%S%Z',
'rfc': '%Y-%m-%d', # shortcut for rfc3339
'rfc3339': '%Y-%m-%dT%H:%M:%S%Z',
'rfc822': '%d %b %y %H:%M:%S %Z',
}
DEFAULT_FORMAT = 'iso'
default_error_messages = {
'invalid': 'Invalid datetime value',
'invalid_type': 'Value should be string',
'invalid_format': 'Value should match datetime format',
}
def __init__(self, format=None, *args, **kwargs):
super(DateTime, self).__init__(*args, **kwargs)
self.format = format or self.DEFAULT_FORMAT
def _convert_value(self, value):
return value
def load(self, data, *args, **kwargs):
if data is MISSING or data is None:
self._fail('required')
if not isinstance(data, string_types):
self._fail('invalid_type', data=data)
format_str = self.FORMATS.get(self.format, self.format)
try:
date = self._convert_value(datetime.datetime.strptime(data, format_str))
return super(DateTime, self).load(date, *args, **kwargs)
except ValueError:
self._fail('invalid_format', data=data, format=format_str)
def dump(self, value, *args, **kwargs):
if value is MISSING or value is None:
self._fail('required')
format_str = self.FORMATS.get(self.format, self.format)
try:
return super(DateTime, self)\
.dump(value.strftime(format_str), *args, **kwargs)
except (AttributeError, ValueError):
self._fail('invalid', data=value)
class Date(DateTime):
"""A date type which serializes into string.
:param str format: Format string (see :func:`datetime.datetime.strptime`) or
one of predefined format names (e.g. 'iso8601', 'rfc3339', etc.
See :const:`~Date.FORMATS`)
:param kwargs: Same keyword arguments as for :class:`Type`.
"""
FORMATS = {
'iso': '%Y-%m-%d', # shortcut for iso8601
'iso8601': '%Y-%m-%d',
'rfc': '%Y-%m-%d', # shortcut for rfc3339
'rfc3339': '%Y-%m-%d',
'rfc822': '%d %b %y',
}
DEFAULT_FORMAT = 'iso'
default_error_messages = {
'invalid': 'Invalid date value',
'invalid_type': 'Value should be string',
'invalid_format': 'Value should match date format',
}
def _convert_value(self, value):
return value.date()
class Time(DateTime):
"""A date type which serializes into string.
:param str format: Format string (see :func:`datetime.datetime.strptime`) or
one of predefined format names (e.g. 'iso8601', 'rfc3339', etc.)
:param kwargs: Same keyword arguments as for :class:`Type`.
"""
FORMATS = {
'iso': '%H:%M:%S', # shortcut for iso8601
'iso8601': '%H:%M:%S',
}
DEFAULT_FORMAT = 'iso'
default_error_messages = {
'invalid': 'Invalid time value',
'invalid_type': 'Value should be string',
'invalid_format': 'Value should match time format',
}
def _convert_value(self, value):
return value.time()
[docs]class List(Type):
"""A homogenous list type.
Example: ::
List(String()).load(['foo', 'bar', 'baz'])
:param Type item_type: Type of list elements.
:param kwargs: Same keyword arguments as for :class:`Type`.
"""
default_error_messages = {
'invalid': 'Value should be list',
}
def __init__(self, item_type, **kwargs):
super(List, self).__init__(**kwargs)
self.item_type = item_type
def load(self, data, *args, **kwargs):
if data is MISSING or data is None:
self._fail('required')
# TODO: Make more intelligent check for collections
if not is_list(data):
self._fail('invalid')
errors_builder = ValidationErrorBuilder()
items = []
for idx, item in enumerate(data):
try:
items.append(self.item_type.load(item, *args, **kwargs))
except ValidationError as ve:
errors_builder.add_errors({idx: ve.messages})
errors_builder.raise_errors()
return super(List, self).load(items, *args, **kwargs)
def dump(self, value, *args, **kwargs):
if value is MISSING or value is None:
self._fail('required')
if not is_list(value):
self._fail('invalid')
errors_builder = ValidationErrorBuilder()
items = []
for idx, item in enumerate(value):
try:
items.append(self.item_type.dump(item, *args, **kwargs))
except ValidationError as ve:
errors_builder.add_errors({idx: ve.messages})
errors_builder.raise_errors()
return super(List, self).dump(items, *args, **kwargs)
def __repr__(self):
return '<{klass} of {item_type}>'.format(
klass=self.__class__.__name__,
item_type=repr(self.item_type),
)
[docs]class Tuple(Type):
"""A heterogenous list type.
Example: ::
Tuple([String(), Integer(), Boolean()]).load(['foo', 123, False])
:param list item_types: List of item types.
:param kwargs: Same keyword arguments as for :class:`Type`.
"""
default_error_messages = {
'invalid': 'Value should be list',
'invalid_length': 'Value length should be {expected_length}',
}
def __init__(self, item_types, **kwargs):
super(Tuple, self).__init__(**kwargs)
self.item_types = item_types
def load(self, data, *args, **kwargs):
if data is MISSING or data is None:
self._fail('required')
if not is_list(data):
self._fail('invalid')
if len(data) != len(self.item_types):
self._fail('invalid_length', expected_length=len(self.item_types))
errors_builder = ValidationErrorBuilder()
result = []
for idx, (item_type, item) in enumerate(zip(self.item_types, data)):
try:
result.add(item_type.load(item, *args, **kwargs))
except ValidationError as ve:
errors_builder.add_errors({idx: ve.messages})
errors_builder.raise_errors()
return super(Tuple, self).load(result, *args, **kwargs)
def dump(self, value, *args, **kwargs):
if value is MISSING or value is None:
self._fail('required')
if not is_list(data):
self._fail('invalid')
if len(value) != len(self.item_types):
self._fail('invalid_length', expected_length=len(self.item_types))
errors_builder = ValidationErrorBuilder()
result = []
for idx, (item_type, item) in enumerate(zip(self.item_types, value)):
try:
result.add(item_type.dump(item, *args, **kwargs))
except ValidationError as ve:
errors_builder.add_errors({idx: ve.messages})
errors_builder.raise_errors()
return super(Tuple, self).dump(result, *args, **kwargs)
def __repr__(self):
return '<{klass} of {item_types}>'.format(
klass=self.__class__.__name__,
item_type=repr(self.item_types),
)
[docs]def type_name_hint(data):
"""Returns type name of given value.
To be used as a type hint in :class:`OneOf`.
"""
return data.__class__.__name__
[docs]def dict_value_hint(key, mapper=None):
"""Returns a function that takes a dictionary and returns value of
particular key. The returned value can be optionally processed by `mapper`
function.
To be used as a type hint in :class:`OneOf`.
"""
if mapper is None:
mapper = lambda x: x
def hinter(data):
return mapper(data.get(key))
return hinter
[docs]class OneOf(Type):
"""
Example: ::
class Foo(object):
def __init__(self, foo):
self.foo = foo
class Bar(object):
def __init__(self, bar):
self.bar = bar
FooType = Object({'foo': String()}, constructor=Foo)
BarType = Object({'bar': Integer()}, constructor=Bar)
def object_with_type(name, subject_type):
return Object(subject_type, {'type': name},
constructor=subject_type.constructor)
FooBarType = OneOf({
'Foo': object_with_type('Foo', FooType),
'Bar': object_with_type('Bar', BarType),
}, dump_hint=type_name_hint, load_hint=dict_value_hint('type'))
List(FooBarType).dump([Foo(foo='hello'), Bar(bar=123)])
# => [{'type': 'Foo', 'foo': 'hello'}, {'type': 'Bar', 'bar': 123}]
List(FooBarType).load([{'type': 'Foo', 'foo': 'hello'},
{'type': 'Bar', 'bar': 123}])
# => [Foo(foo='hello'), Bar(bar=123)]
"""
default_error_messages = {
'invalid': 'Invalid data',
}
def __init__(self, types,
load_hint=type_name_hint,
dump_hint=type_name_hint,
*args, **kwargs):
super(OneOf, self).__init__(*args, **kwargs)
self.types = types
self.load_hint = load_hint
self.dump_hint = dump_hint
def load(self, data, *args, **kwargs):
if data is MISSING or data is None:
self._fail('required')
if is_dict(self.types) and self.load_hint:
type_id = self.load_hint(data)
if type_id not in self.types:
self._fail('invalid')
item_type = self.types[type_id]
result = item_type.load(data, *args, **kwargs)
return super(OneOf, self).load(result, *args, **kwargs)
else:
for item_type in (self.types.values() if is_dict(self.types) else self.types):
try:
result = item_type.load(data, *args, **kwargs)
return super(OneOf, self).load(result, *args, **kwargs)
except ValidationError as ve:
pass
self._fail('invalid')
def dump(self, data, *args, **kwargs):
if data is MISSING or data is None:
self._fail('required')
if is_dict(self.types) and self.dump_hint:
type_id = self.dump_hint(data)
if type_id not in self.types:
self._fail('invalid')
item_type = self.types[type_id]
result = item_type.dump(data, *args, **kwargs)
return super(OneOf, self).dump(result, *args, **kwargs)
else:
for item_type in (self.types.values() if is_dict(self.types) else self.types):
try:
result = item_type.dump(data, *args, **kwargs)
return super(OneOf, self).dump(result, *args, **kwargs)
except ValidationError as ve:
pass
self._fail('invalid')
class DictWithDefault(object):
def __init__(self, values={}, default=None):
super(DictWithDefault, self).__init__()
self.values = values
self.default = default
def __len__(self):
return len(self.values)
def __getitem__(self, key):
if key in self.values:
return self.values[key]
return self.default
def __setitem__(self, key, value):
self.values[key] = value
def __delitem__(self, key):
del self.values[key]
def get(self, key, default=None):
return self[key]
[docs]class Dict(Type):
"""A dict type. You can specify either a single type for all dict values
or provide a dict-like mapping object that will return proper Type instance
for each given dict key.
Example: ::
Dict(Integer()).load({'key0': 1, 'key1': 5, 'key2': 15})
Dict({'foo': String(), 'bar': Integer()}).load({
'foo': 'hello', 'bar': 123,
})
:param dict value_type: A single :class:`Type` for all dict values or mapping
of allowed keys to :class:`Type` instances.
:param kwargs: Same keyword arguments as for :class:`Type`.
"""
default_error_messages = {
'invalid': 'Value should be dict',
}
def __init__(self, value_types=Any(), **kwargs):
super(Dict, self).__init__(**kwargs)
if isinstance(value_types, Type):
value_types = DictWithDefault(default=value_types)
self.value_types = value_types
def load(self, data, *args, **kwargs):
if data is MISSING or data is None:
self._fail('required')
if not is_dict(data):
self._fail('invalid')
errors_builder = ValidationErrorBuilder()
result = {}
for k, v in iteritems(data):
value_type = self.value_types.get(k)
if value_type is None:
continue
try:
result[k] = value_type.load(v, *args, **kwargs)
except ValidationError as ve:
errors_builder.add_error(k, ve.messages)
errors_builder.raise_errors()
return super(Dict, self).load(result, *args, **kwargs)
def dump(self, value, *args, **kwargs):
if value is MISSING or value is None:
self._fail('required')
if not is_dict(value):
self._fail('invalid')
errors_builder = ValidationErrorBuilder()
result = {}
for k, v in iteritems(value):
value_type = self.value_types.get(k)
if value_type is None:
continue
try:
result[k] = value_type.dump(v, *args, **kwargs)
except ValidationError as ve:
errors_builder.add_error(k, ve.messages)
errors_builder.raise_errors()
return super(Dict, self).dump(result, *args, **kwargs)
def __repr__(self):
return '<{klass}>'.format(klass=self.__class__.__name__)
[docs]class Field(ErrorMessagesMixin):
"""Base class for describing :class:`Object` fields. Defines a way to access
object fields during serialization/deserialization. Usually it extracts data to
serialize/deserialize and call `self.field_type.load()` to do data
transformation.
:param Type field_type: Field type.
"""
def __init__(self, field_type, *args, **kwargs):
super(Field, self).__init__(*args, **kwargs)
self.field_type = field_type
def _get_value(self, name, obj, context=None):
raise NotImplemented()
[docs] def load(self, name, data, *args, **kwargs):
"""Deserialize data from primitive types. Raises
:exc:`~lollipop.errors.ValidationError` if data is invalid.
:param str name: Name of attribute to deserialize.
:param data: Raw data to get value to deserialize from.
"""
return MISSING
[docs] def dump(self, name, obj, *args, **kwargs):
"""Serialize data to primitive types. Raises
:exc:`~lollipop.errors.ValidationError` if data is invalid.
:param str name: Name of attribute to serialize.
:param obj: Application object to extract serialized value from.
"""
value = self._get_value(name, obj)
return self.field_type.dump(value, *args, **kwargs)
[docs]class ConstantField(Field):
"""Field that always serializes to given value and does not deserialize.
:param Type field_type: Field type.
:param value: Value constant for this field.
"""
default_error_messages = {
'required': 'Value is required',
'value': 'Value is incorrect',
}
def __init__(self, field_type, value, *args, **kwargs):
super(ConstantField, self).__init__(field_type, *args, **kwargs)
self.value = value
def _get_value(self, name, obj, *args, **kwargs):
return self.value
def load(self, name, data, *args, **kwargs):
value = data.get(name, MISSING)
if value is MISSING or value is None:
self._fail('required')
result = self.field_type.load(value)
if result != MISSING and result != self.value:
self._fail('value')
return MISSING
[docs]class AttributeField(Field):
"""Field that corresponds to object attribute.
:param Type field_type: Field type.
:param str attribute: Use given attribute name instead of field name
defined in object type.
"""
def __init__(self, field_type, attribute=None, *args, **kwargs):
super(AttributeField, self).__init__(field_type, *args, **kwargs)
self.attribute = attribute
def _get_value(self, name, obj, *args, **kwargs):
return getattr(obj, self.attribute or name, MISSING)
def load(self, name, data, *args, **kwargs):
value = data.get(name, MISSING)
return self.field_type.load(value, *args, **kwargs)
[docs]class MethodField(Field):
"""Field that is result of method invocation.
Example: ::
class Person(object):
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
def get_name(self):
return self.first_name + ' ' + self.last_name
PersonType = Object({
'name': MethodField(String(), 'get_name'),
}, constructor=Person)
:param Type field_type: Field type.
:param str method: Method name. Method should not take any arguments.
"""
def __init__(self, field_type, method, *args, **kwargs):
super(MethodField, self).__init__(field_type, *args, **kwargs)
self.method = method
def _get_value(self, name, obj, context=None):
if self.method:
name = self.method
if not hasattr(obj, name):
raise ValueError('Object does not have method %s' % name)
if not callable(getattr(obj, name)):
raise ValueError('Value %s is not callable' % name)
return call_with_context(getattr(obj, name), context)
[docs]class FunctionField(Field):
"""Field that is result of function invocation.
Example: ::
class Person(object):
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
def get_name(person):
return person.first_name + ' ' + person.last_name
PersonType = Object({
'name': FunctionField(String(), get_name),
}, constructor=Person)
:param Type field_type: Field type.
:param callable function: Function that takes source object and returns
field value.
"""
def __init__(self, field_type, function, *args, **kwargs):
super(FunctionField, self).__init__(field_type, *args, **kwargs)
self.function = function
def _get_value(self, name, obj, context=None):
return call_with_context(self.function, context, name, obj)
[docs]class Object(Type):
"""An object type. Serializes to a dict of field names to serialized field
values. Parametrized with field names to types mapping.
The way values are obtained during serialization is determined by type of
field object in :attr:`~Object.fields` mapping (see :class:`ConstantField`,
:class:`AttributeField`, :class:`MethodField` for details). You can specify
either :class:`Field` object, a :class:`Type` object or any other value.
In case of :class:`Type`, it will be automatically wrapped with a default
field type, which is controlled by :attr:`~Object.default_field_type`
constructor argument.
In case of any other value it will be transformed into :class:`ConstantField`.
Example: ::
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
PersonType = Object({
'name': String(),
'age': Integer(),
}, constructor=Person)
PersonType.load({'name': 'John', 'age': 42})
# => Person(name='John', age=42)
:param base_or_fields: Either :class:`Object` instance or fields (See
`fields` argument). In case of fields, the actual fields argument should
not be specified.
:param fields: List of name-to-value tuples or mapping of object field names to
:class:`Type`, :class:`Field` objects or constant values.
:param callable contructor: Deserialized value constructor. Constructor
should take all fields values as keyword arguments.
:param Field default_field_type: Default field type to use for fields defined
by their type.
:param bool allow_extra_fields: If False, it will raise
:exc:`~lollipop.errors.ValidationError` for all extra dict keys during
deserialization. If True, will ignore all extra fields.
:param list only: List of field names to include in this object. All other
fields (own or inherited) won't be used.
:param list exclude: List of field names to exclude from this object.
All other fields (own or inherited) will be included.
:param kwargs: Same keyword arguments as for :class:`Type`.
"""
default_error_messages = {
'invalid': 'Value should be dict',
'unknown': 'Unknown field',
}
def __init__(self, bases_or_fields=None, fields=None, constructor=dict,
default_field_type=AttributeField,
allow_extra_fields=True, only=None, exclude=None,
**kwargs):
super(Object, self).__init__(**kwargs)
if bases_or_fields is None and fields is None:
raise ValueError('No base and/or fields are specified')
if isinstance(bases_or_fields, Object):
self.bases = [bases_or_fields]
if is_list(bases_or_fields) and \
all([isinstance(base, Object) for base in bases_or_fields]):
self.bases = bases_or_fields
elif is_list(bases_or_fields) or is_dict(bases_or_fields):
if fields is None:
self.bases = None
fields = bases_or_fields
else:
raise ValueError('Unknown base object type: %r' % bases_or_fields)
self._fields = [
(name, self._normalize_field(field, default_field_type))
for name, field in (iteritems(fields) if is_dict(fields) else fields)
]
self.__fields = None
self.constructor = constructor
self.allow_extra_fields = allow_extra_fields
self.only = only
self.exclude = exclude
def _normalize_field(self, value, default_field_type):
if isinstance(value, Field):
return value
if isinstance(value, Type):
return default_field_type(value)
return ConstantField(Any(), value)
# Resolved at time of usage
@property
def fields(self):
if self.__fields is None:
fields = []
if self.bases is not None:
for base in self.bases:
fields += base.fields
fields += self._fields
if self.only is not None:
fields = [(name, field) for name, field in fields
if name in self.only]
if self.exclude is not None:
fields = [(name, field) for name, field in fields
if name not in self.exclude]
self.__fields = fields
return self.__fields
def load(self, data, *args, **kwargs):
if data is MISSING or data is None:
self._fail('required')
if not is_dict(data):
self._fail('invalid')
errors_builder = ValidationErrorBuilder()
result = {}
for name, field in self.fields:
try:
loaded = field.load(name, data, *args, **kwargs)
if loaded != MISSING:
result[name] = loaded
except ValidationError as ve:
errors_builder.add_error(name, ve.messages)
if not self.allow_extra_fields:
field_names = [name for name, _ in self.fields]
for name in data:
if name not in field_names:
errors_builder.add_error(name, self._error_messages['unknown'])
errors_builder.raise_errors()
return self.constructor(**super(Object, self).load(result, *args, **kwargs))
def dump(self, obj, *args, **kwargs):
if obj is MISSING or obj is None:
self._fail('required')
errors_builder = ValidationErrorBuilder()
result = {}
for name, field in self.fields:
try:
dumped = field.dump(name, obj, *args, **kwargs)
if dumped != MISSING:
result[name] = dumped
except ValidationError as ve:
errors_builder.add_error(name, ve.messages)
errors_builder.raise_errors()
return super(Object, self).dump(result, *args, **kwargs)
[docs]class Optional(Type):
"""A wrapper type which makes values optional: if value is missing or None,
it will not transform it with an inner type but instead will return None
(or any other configured value).
Example: ::
UserType = Object({
'email': String(), # by default types require valid values
'name': Optional(String()), # value can be omitted or None
'role': Optional( # when value is omitted or None, use given value
String(validate=AnyOf(['admin', 'customer'])),
load_default='customer',
),
})
:param Type inner_type: Actual type that should be optional.
:param load_default: Value to use when value is missing on deserialization.
:param dump_default: Value to use when value is missing on serialization.
:param kwargs: Same keyword arguments as for :class:`Type`.
"""
def __init__(self, inner_type,
load_default=None, dump_default=None,
**kwargs):
super(Optional, self).__init__(**kwargs)
self.inner_type = inner_type
self.load_default = load_default
self.dump_default = dump_default
def load(self, data, *args, **kwargs):
if data is MISSING or data is None:
return self.load_default
return super(Optional, self).load(
self.inner_type.load(data, *args, **kwargs),
*args, **kwargs
)
def dump(self, data, *args, **kwargs):
if data is MISSING or data is None:
return self.dump_default
return super(Optional, self).dump(
self.inner_type.dump(data, *args, **kwargs),
*args, **kwargs
)
def __repr__(self):
return '<{klass} {inner_type}>'.format(
klass=self.__class__.__name__,
inner_type=repr(self.inner_type),
)
[docs]class LoadOnly(Type):
"""A wrapper type which proxies loading to inner type but always returns
:obj:`MISSING` on dump.
Example: ::
UserType = Object({
'name': String(),
'password': LoadOnly(String()),
})
:param Type inner_type: Data type.
"""
def __init__(self, inner_type):
super(LoadOnly, self).__init__()
self.inner_type = inner_type
def load(self, data, *args, **kwargs):
return self.inner_type.load(data, *args, **kwargs)
def dump(self, data, context=None):
return MISSING
def __repr__(self):
return '<{klass} {inner_type}>'.format(
klass=self.__class__.__name__,
inner_type=repr(self.inner_type),
)
[docs]class DumpOnly(Type):
"""A wrapper type which proxies dumping to inner type but always returns
:obj:`MISSING` on load.
Example: ::
UserType = Object({
'name': String(),
'created_at': DumpOnly(DateTime()),
})
:param Type inner_type: Data type.
"""
def __init__(self, inner_type):
super(DumpOnly, self).__init__()
self.inner_type = inner_type
def load(self, data, *args, **kwargs):
return MISSING
def dump(self, data, *args, **kwargs):
return self.inner_type.dump(data, *args, **kwargs)
def __repr__(self):
return '<{klass} {inner_type}>'.format(
klass=self.__class__.__name__,
inner_type=repr(self.inner_type),
)