Find Cheaper University Textbooks
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

256 lines
7.3 KiB

from datetime import date
from dateutil import parser
from six import itervalues
from .utils import DslBase, _make_dsl_class, ObjectBase, AttrDict, AttrList
from .exceptions import ValidationException
__all__ = [
'construct_field', 'Field', 'Object', 'Nested', 'Date', 'String', 'Float', 'Double',
'Byte', 'Short', 'Integer', 'Long', 'Boolean', 'Ip', 'Attachment',
'GeoPoint', 'GeoShape', 'InnerObjectWrapper'
]
def construct_field(name_or_field, **params):
# {"type": "string", "index": "not_analyzed"}
if isinstance(name_or_field, dict):
if params:
raise ValueError('construct_field() cannot accept parameters when passing in a dict.')
params = name_or_field.copy()
if 'type' not in params:
# inner object can be implicitly defined
if 'properties' in params:
name = 'object'
else:
raise ValueError('construct_field() needs to have a "type" key.')
else:
name = params.pop('type')
return Field.get_dsl_class(name)(**params)
# String()
if isinstance(name_or_field, Field):
if params:
raise ValueError('construct_field() cannot accept parameters when passing in a construct_field object.')
return name_or_field
# "string", index="not_analyzed"
return Field.get_dsl_class(name_or_field)(**params)
class Field(DslBase):
_type_name = 'field'
_type_shortcut = staticmethod(construct_field)
# all fields can be multifields
_param_defs = {'fields': {'type': 'field', 'hash': True}}
name = None
_coerce = False
def __init__(self, *args, **kwargs):
self._multi = kwargs.pop('multi', False)
self._required = kwargs.pop('required', False)
super(Field, self).__init__(*args, **kwargs)
def _serialize(self, data):
return data
def _deserialize(self, data):
return data
def _empty(self):
return None
def empty(self):
if self._multi:
return AttrList([])
return self._empty()
def serialize(self, data):
if isinstance(data, (list, AttrList)):
data[:] = map(self._serialize, data)
return data
return self._serialize(data)
def deserialize(self, data):
if isinstance(data, (list, AttrList)):
data[:] = map(self._deserialize, data)
return data
return self._deserialize(data)
def clean(self, data):
if data is not None:
data = self.deserialize(data)
# FIXME: numeric 0
if not data and self._required:
raise ValidationException("Value required for this field.")
return data
def to_dict(self):
d = super(Field, self).to_dict()
name, value = d.popitem()
value['type'] = name
return value
class CustomField(Field):
name = 'custom'
_coerce = True
def to_dict(self):
if isinstance(self.builtin_type, Field):
return self.builtin_type.to_dict()
d = super(CustomField, self).to_dict()
d['type'] = self.builtin_type
return d
class InnerObjectWrapper(ObjectBase):
def __init__(self, mapping, **kwargs):
# mimic DocType behavior with _doc_type.mapping
super(AttrDict, self).__setattr__('_doc_type', type('Meta', (), {'mapping': mapping}))
super(InnerObjectWrapper, self).__init__(**kwargs)
class InnerObject(object):
" Common functionality for nested and object fields. "
_param_defs = {'properties': {'type': 'field', 'hash': True}}
_coerce = True
def __init__(self, *args, **kwargs):
self._doc_class = kwargs.pop('doc_class', InnerObjectWrapper)
super(InnerObject, self).__init__(*args, **kwargs)
def field(self, name, *args, **kwargs):
self.properties[name] = construct_field(*args, **kwargs)
return self
# XXX: backwards compatible, will be removed
property = field
def _empty(self):
return self._doc_class(self.properties)
def empty(self):
if self._multi:
return AttrList([], lambda d: self._doc_class(self.properties, **d))
return self._empty()
def __getitem__(self, name):
return self.properties[name]
def __contains__(self, name):
return name in self.properties
def _collect_fields(self):
" Iterate over all Field objects within, including multi fields. "
for f in itervalues(self.properties.to_dict()):
yield f
# multi fields
if hasattr(f, 'fields'):
for inner_f in itervalues(f.fields.to_dict()):
yield inner_f
# nested and inner objects
if hasattr(f, '_collect_fields'):
for inner_f in f._collect_fields():
yield inner_f
def update(self, other_object):
if not hasattr(other_object, 'properties'):
# not an inner/nested object, no merge possible
return
our, other = self.properties, other_object.properties
for name in other:
if name in our:
if hasattr(our[name], 'update'):
our[name].update(other[name])
continue
our[name] = other[name]
def _deserialize(self, data):
if data is None:
return None
# don't wrap already wrapped data
if isinstance(data, self._doc_class):
return data
if isinstance(data, (list, AttrList)):
data[:] = list(map(self._deserialize, data))
return data
if isinstance(data, AttrDict):
data = data._d_
return self._doc_class(self.properties, **data)
def _serialize(self, data):
return data.to_dict()
def clean(self, data):
data = super(InnerObject, self).clean(data)
if data is None:
return None
if isinstance(data, (list, AttrList)):
for d in data:
d.full_clean()
else:
data.full_clean()
return data
class Object(InnerObject, Field):
name = 'object'
class Nested(InnerObject, Field):
name = 'nested'
def __init__(self, *args, **kwargs):
# change the default for Nested fields
kwargs.setdefault('multi', True)
super(Nested, self).__init__(*args, **kwargs)
class Date(Field):
name = 'date'
_coerce = True
def _deserialize(self, data):
if not data:
return None
if isinstance(data, date):
return data
try:
# TODO: add format awareness
return parser.parse(data)
except Exception as e:
raise ValidationException('Could not parse date from the value (%r)' % data, e)
class String(Field):
_param_defs = {
'fields': {'type': 'field', 'hash': True},
'analyzer': {'type': 'analyzer'},
'index_analyzer': {'type': 'analyzer'},
'search_analyzer': {'type': 'analyzer'},
}
name = 'string'
def _empty(self):
return ''
FIELDS = (
'float',
'double',
'byte',
'short',
'integer',
'long',
'boolean',
'ip',
'attachment',
'geo_point',
'geo_shape',
'completion',
)
# generate the query classes dynamicaly
for f in FIELDS:
fclass = _make_dsl_class(Field, f)
globals()[fclass.__name__] = fclass
__all__.append(fclass.__name__)