Commit 859fd3e9 by Roman Krejcik

lazy loaded objects

parent b3316278
...@@ -20,10 +20,19 @@ from parse_rest.connection import API_ROOT, ParseBase ...@@ -20,10 +20,19 @@ from parse_rest.connection import API_ROOT, ParseBase
from parse_rest.query import QueryManager 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): class ParseType(object):
type_mapping = {}
@staticmethod @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 is_parse_type = isinstance(parse_data, dict) and '__type' in parse_data
...@@ -31,28 +40,8 @@ class ParseType(object): ...@@ -31,28 +40,8 @@ class ParseType(object):
if not is_parse_type: if not is_parse_type:
return parse_data 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 native = ParseType.type_mapping.get(parse_data['__type'])
parse_type = parse_data['__type'] return native.from_native(**parse_data) if native else parse_data
# 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
@staticmethod @staticmethod
def convert_to_parse(python_object, as_pointer=False): def convert_to_parse(python_object, as_pointer=False):
...@@ -69,7 +58,7 @@ class ParseType(object): ...@@ -69,7 +58,7 @@ class ParseType(object):
transformation_map = { transformation_map = {
datetime.datetime: Date, datetime.datetime: Date,
Object: Pointer Object: Pointer
} }
if python_type in transformation_map: if python_type in transformation_map:
klass = transformation_map.get(python_type) klass = transformation_map.get(python_type)
...@@ -85,36 +74,18 @@ class ParseType(object): ...@@ -85,36 +74,18 @@ class ParseType(object):
return cls(**kw) return cls(**kw)
def _to_native(self): def _to_native(self):
return self._value raise NotImplementedError("_to_native must be overridden")
@complex_type('Pointer')
class Pointer(ParseType): class Pointer(ParseType):
@classmethod @classmethod
def _prevent_circular(cls, parent_class_name, objectData): def from_native(cls, **kw):
# TODO this should be replaced with more clever checking, instead of simple class mathching original id should be compared # create object with only objectId and unloaded flag. it is automatically loaded when any other field is accessed
# 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
klass = Object.factory(kw.get('className')) klass = Object.factory(kw.get('className'))
objectData = klass.GET("/" + kw.get('objectId')) return klass(objectId=kw.get('objectId'), _is_loaded=False)
# now lets check if we have circular references here
if parent_class_name:
objectData = cls._prevent_circular(parent_class_name, objectData)
# 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): def __init__(self, obj):
self._object = obj self._object = obj
...@@ -124,24 +95,25 @@ class Pointer(ParseType): ...@@ -124,24 +95,25 @@ class Pointer(ParseType):
'__type': 'Pointer', '__type': 'Pointer',
'className': self._object.__class__.__name__, 'className': self._object.__class__.__name__,
'objectId': self._object.objectId 'objectId': self._object.objectId
} }
@complex_type('Object')
class EmbeddedObject(ParseType): class EmbeddedObject(ParseType):
@classmethod @classmethod
def from_native(cls, parent_class_name=None, **kw): def from_native(cls, **kw):
if parent_class_name:
kw = Pointer._prevent_circular(parent_class_name, kw)
klass = Object.factory(kw.get('className')) klass = Object.factory(kw.get('className'))
return klass(**kw) return klass(**kw)
@complex_type()
class Relation(ParseType): class Relation(ParseType):
@classmethod @classmethod
def from_native(cls, **kw): def from_native(cls, **kw):
pass pass
@complex_type()
class Date(ParseType): class Date(ParseType):
FORMAT = '%Y-%m-%dT%H:%M:%S.%f%Z' FORMAT = '%Y-%m-%dT%H:%M:%S.%f%Z'
...@@ -167,6 +139,7 @@ class Date(ParseType): ...@@ -167,6 +139,7 @@ class Date(ParseType):
} }
@complex_type('Bytes')
class Binary(ParseType): class Binary(ParseType):
@classmethod @classmethod
...@@ -181,6 +154,7 @@ class Binary(ParseType): ...@@ -181,6 +154,7 @@ class Binary(ParseType):
return {'__type': 'Bytes', 'base64': self._encoded} return {'__type': 'Bytes', 'base64': self._encoded}
@complex_type()
class GeoPoint(ParseType): class GeoPoint(ParseType):
@classmethod @classmethod
...@@ -199,6 +173,7 @@ class GeoPoint(ParseType): ...@@ -199,6 +173,7 @@ class GeoPoint(ParseType):
} }
@complex_type()
class File(ParseType): class File(ParseType):
@classmethod @classmethod
...@@ -233,14 +208,10 @@ class Function(ParseBase): ...@@ -233,14 +208,10 @@ class Function(ParseBase):
return self.POST('/' + self.name, **kwargs) return self.POST('/' + self.name, **kwargs)
class ParseResource(ParseBase, Pointer): class ParseResource(ParseBase):
PROTECTED_ATTRIBUTES = ['objectId', 'createdAt', 'updatedAt'] PROTECTED_ATTRIBUTES = ['objectId', 'createdAt', 'updatedAt']
@classmethod
def retrieve(cls, resource_id):
return cls(**cls.GET('/' + resource_id))
@property @property
def _editable_attrs(self): def _editable_attrs(self):
protected_attrs = self.__class__.PROTECTED_ATTRIBUTES protected_attrs = self.__class__.PROTECTED_ATTRIBUTES
...@@ -248,19 +219,23 @@ class ParseResource(ParseBase, Pointer): ...@@ -248,19 +219,23 @@ class ParseResource(ParseBase, Pointer):
return dict([(k, v) for k, v in self.__dict__.items() if allowed(k)]) return dict([(k, v) for k, v in self.__dict__.items() if allowed(k)])
def __init__(self, **kw): def __init__(self, **kw):
for key, value in kw.items(): self.objectId = None
setattr(self, key, ParseType.convert_from_parse(value, self.__class__.__name__)) 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): def _to_native(self):
return ParseType.convert_to_parse(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): def _get_updated_datetime(self):
return self.__dict__.get('_updated_at') and self._updated_at._date return self.__dict__.get('_updated_at') and self._updated_at._date
...@@ -294,8 +269,7 @@ class ParseResource(ParseBase, Pointer): ...@@ -294,8 +269,7 @@ class ParseResource(ParseBase, Pointer):
call_back(response) call_back(response)
def _update(self, batch=False): def _update(self, batch=False):
response = self.__class__.PUT(self._absolute_url, batch=batch, response = self.__class__.PUT(self._absolute_url, batch=batch, **self._to_native())
**self._to_native())
def call_back(response_dict): def call_back(response_dict):
self.updatedAt = response_dict['updatedAt'] self.updatedAt = response_dict['updatedAt']
...@@ -307,19 +281,12 @@ class ParseResource(ParseBase, Pointer): ...@@ -307,19 +281,12 @@ class ParseResource(ParseBase, Pointer):
def delete(self, batch=False): def delete(self, batch=False):
response = self.__class__.DELETE(self._absolute_url, batch=batch) response = self.__class__.DELETE(self._absolute_url, batch=batch)
def call_back(response_dict):
self.__dict__ = {}
if batch: if batch:
return response, call_back return response, lambda response_dict: None
else:
call_back(response)
_absolute_url = property( _absolute_url = property(lambda self: '/'.join([self.__class__.ENDPOINT_ROOT, self.objectId]))
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) createdAt = property(_get_created_datetime, _set_created_datetime)
updatedAt = property(_get_updated_datetime, _set_updated_datetime) updatedAt = property(_get_updated_datetime, _set_updated_datetime)
...@@ -365,10 +332,7 @@ class Object(six.with_metaclass(ObjectMetaclass, ParseResource)): ...@@ -365,10 +332,7 @@ class Object(six.with_metaclass(ObjectMetaclass, ParseResource)):
@property @property
def as_pointer(self): def as_pointer(self):
return Pointer(**{ return Pointer(self)
'className': self.__class__.__name__,
'objectId': self.objectId
})
def increment(self, key, amount=1): def increment(self, key, amount=1):
""" """
......
...@@ -46,7 +46,7 @@ class User(ParseResource): ...@@ -46,7 +46,7 @@ class User(ParseResource):
if password is not None: if password is not None:
self = User.login(self.username, password) 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: if user.objectId == self.objectId and user.sessionToken == session_token:
self.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