Commit 859fd3e9 by Roman Krejcik

lazy loaded objects

parent b3316278
......@@ -20,10 +20,19 @@ from parse_rest.connection import API_ROOT, ParseBase
from parse_rest.query import QueryManager
def complex_type(name=None):
'''Decorator for registering complex types'''
def wrapped(cls):
ParseType.type_mapping[name or cls.__name__] = cls
return cls
return wrapped
class ParseType(object):
type_mapping = {}
@staticmethod
def convert_from_parse(parse_data, class_name):
def convert_from_parse(parse_data):
is_parse_type = isinstance(parse_data, dict) and '__type' in parse_data
......@@ -31,28 +40,8 @@ class ParseType(object):
if not is_parse_type:
return parse_data
# determine just which kind of parse type this element is - ie: a built in parse type such as File, Pointer, User etc
parse_type = parse_data['__type']
# if its a pointer, we need to handle to ensure that we don't mishandle a circular reference
if parse_type == "Pointer":
# grab the pointer object here
return Pointer.from_native(class_name, **parse_data)
# embedded object by select_related
if parse_type == "Object":
return EmbeddedObject.from_native(class_name, **parse_data)
# now handle the other parse types accordingly
native = {
'Date': Date,
'Bytes': Binary,
'GeoPoint': GeoPoint,
'File': File,
'Relation': Relation
}.get(parse_type)
return native and native.from_native(**parse_data) or parse_data
native = ParseType.type_mapping.get(parse_data['__type'])
return native.from_native(**parse_data) if native else parse_data
@staticmethod
def convert_to_parse(python_object, as_pointer=False):
......@@ -85,36 +74,18 @@ class ParseType(object):
return cls(**kw)
def _to_native(self):
return self._value
raise NotImplementedError("_to_native must be overridden")
@complex_type('Pointer')
class Pointer(ParseType):
@classmethod
def _prevent_circular(cls, parent_class_name, objectData):
# TODO this should be replaced with more clever checking, instead of simple class mathching original id should be compared
# also circular refs through more object are now ignored, in fact lazy loaded references will be best solution
objectData = dict(objectData)
# now lets see if we have any references to the parent class here
for key, value in six.iteritems(objectData):
if isinstance(value, dict) and "className" in value and value["className"] == parent_class_name:
# simply put the reference here as a string -- not sure what the drawbacks are for this but it works for me
objectData[key] = value["objectId"]
return objectData
@classmethod
def from_native(cls, parent_class_name=None, **kw):
# grab the object data manually here so we can manipulate it before passing back an actual object
def from_native(cls, **kw):
# create object with only objectId and unloaded flag. it is automatically loaded when any other field is accessed
klass = Object.factory(kw.get('className'))
objectData = klass.GET("/" + kw.get('objectId'))
# now lets check if we have circular references here
if parent_class_name:
objectData = cls._prevent_circular(parent_class_name, objectData)
return klass(objectId=kw.get('objectId'), _is_loaded=False)
# set a temporary flag that will remove the recursive pointer types etc
klass = Object.factory(kw.get('className'))
return klass(**objectData)
def __init__(self, obj):
self._object = obj
......@@ -127,21 +98,22 @@ class Pointer(ParseType):
}
@complex_type('Object')
class EmbeddedObject(ParseType):
@classmethod
def from_native(cls, parent_class_name=None, **kw):
if parent_class_name:
kw = Pointer._prevent_circular(parent_class_name, kw)
def from_native(cls, **kw):
klass = Object.factory(kw.get('className'))
return klass(**kw)
@complex_type()
class Relation(ParseType):
@classmethod
def from_native(cls, **kw):
pass
@complex_type()
class Date(ParseType):
FORMAT = '%Y-%m-%dT%H:%M:%S.%f%Z'
......@@ -167,6 +139,7 @@ class Date(ParseType):
}
@complex_type('Bytes')
class Binary(ParseType):
@classmethod
......@@ -181,6 +154,7 @@ class Binary(ParseType):
return {'__type': 'Bytes', 'base64': self._encoded}
@complex_type()
class GeoPoint(ParseType):
@classmethod
......@@ -199,6 +173,7 @@ class GeoPoint(ParseType):
}
@complex_type()
class File(ParseType):
@classmethod
......@@ -233,14 +208,10 @@ class Function(ParseBase):
return self.POST('/' + self.name, **kwargs)
class ParseResource(ParseBase, Pointer):
class ParseResource(ParseBase):
PROTECTED_ATTRIBUTES = ['objectId', 'createdAt', 'updatedAt']
@classmethod
def retrieve(cls, resource_id):
return cls(**cls.GET('/' + resource_id))
@property
def _editable_attrs(self):
protected_attrs = self.__class__.PROTECTED_ATTRIBUTES
......@@ -248,19 +219,23 @@ class ParseResource(ParseBase, Pointer):
return dict([(k, v) for k, v in self.__dict__.items() if allowed(k)])
def __init__(self, **kw):
for key, value in kw.items():
setattr(self, key, ParseType.convert_from_parse(value, self.__class__.__name__))
self.objectId = None
self._init_attrs(kw)
def __getattr__(self, attr):
# if object is not loaded and attribute is missing, try to load it
if not self.__dict__.get('_is_loaded', True):
del self._is_loaded
self._init_attrs(self.GET(self._absolute_url))
return object.__getattribute__(self, attr) #preserve default if attr not exists
def _init_attrs(self, args):
for key, value in six.iteritems(args):
setattr(self, key, ParseType.convert_from_parse(value))
def _to_native(self):
return ParseType.convert_to_parse(self)
def _get_object_id(self):
return self.__dict__.get('_object_id')
def _set_object_id(self, value):
if '_object_id' in self.__dict__:
raise ValueError('Can not re-set object id')
self._object_id = value
def _get_updated_datetime(self):
return self.__dict__.get('_updated_at') and self._updated_at._date
......@@ -294,8 +269,7 @@ class ParseResource(ParseBase, Pointer):
call_back(response)
def _update(self, batch=False):
response = self.__class__.PUT(self._absolute_url, batch=batch,
**self._to_native())
response = self.__class__.PUT(self._absolute_url, batch=batch, **self._to_native())
def call_back(response_dict):
self.updatedAt = response_dict['updatedAt']
......@@ -307,19 +281,12 @@ class ParseResource(ParseBase, Pointer):
def delete(self, batch=False):
response = self.__class__.DELETE(self._absolute_url, batch=batch)
def call_back(response_dict):
self.__dict__ = {}
if batch:
return response, call_back
else:
call_back(response)
return response, lambda response_dict: None
_absolute_url = property(
lambda self: '/'.join([self.__class__.ENDPOINT_ROOT, self.objectId])
)
_absolute_url = property(lambda self: '/'.join([self.__class__.ENDPOINT_ROOT, self.objectId]))
objectId = property(_get_object_id, _set_object_id)
createdAt = property(_get_created_datetime, _set_created_datetime)
updatedAt = property(_get_updated_datetime, _set_updated_datetime)
......@@ -365,10 +332,7 @@ class Object(six.with_metaclass(ObjectMetaclass, ParseResource)):
@property
def as_pointer(self):
return Pointer(**{
'className': self.__class__.__name__,
'objectId': self.objectId
})
return Pointer(self)
def increment(self, key, amount=1):
"""
......
......@@ -46,7 +46,7 @@ class User(ParseResource):
if password is not None:
self = User.login(self.username, password)
user = User.retrieve(self.objectId)
user = User.Query.get(objectId=self.objectId)
if user.objectId == self.objectId and user.sessionToken == session_token:
self.sessionToken = session_token
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment