"""
@author: JH Prinz
"""
import functools
import weakref
from .base import StorableObject
from six import exec_
# =============================================================================
# Loader Proxy
# =============================================================================
[docs]
class LoaderProxy(object):
"""
A proxy that loads an underlying object if attributes are accessed
"""
__slots__ = ['_subject', '__uuid__', '_store', '__weakref__']
# add a global stash to remember existing Proxy objects
_stash = weakref.WeakValueDictionary()
@classmethod
def new(cls, store, uid):
obj = LoaderProxy._stash.get(uid)
if obj is not None:
return obj
else:
obj = cls(store, uid)
LoaderProxy._stash[uid] = obj
return obj
[docs]
def __init__(self, store, uid):
self.__uuid__ = uid
self._store = store
self._subject = None
@property
def __subject__(self):
if self._subject is not None:
obj = self._subject()
if obj is not None:
return obj
ref = self._load_()
if ref is None:
return None
self._subject = weakref.ref(ref)
return ref
@property
def reversed(self):
return LoaderProxy.new(self._store, StorableObject.ruuid(self.__uuid__))
@property
def _reversed(self):
return LoaderProxy.new(self._store, StorableObject.ruuid(self.__uuid__))
def __eq__(self, other):
if self is other:
return True
if hasattr(other, '__uuid__'):
return self.__uuid__ == other.__uuid__
return NotImplemented
def __getitem__(self, item):
return self.__subject__[item]
def __ne__(self, other):
return not self == other
def __hash__(self):
return self.__uuid__ & 1152921504606846975
def __len__(self):
return len(self.__subject__)
@property
def __class__(self):
return self._store.content_class
def __getattr__(self, item):
return getattr(self.__subject__, item)
def _load_(self):
"""
Call the loader and get the referenced object
"""
try:
return self._store.load(self.__uuid__)
except KeyError:
if type(self.__uuid__) is int:
raise RuntimeWarning(
'Index %s is not in store. This should never happen!' %
self._idx)
else:
raise RuntimeWarning(
'Object %s is not in store. Attach it using fallbacks.' %
self._idx)
[docs]
class DelayedLoader(object):
"""
Descriptor class to handle proxy objects in attributes
If a proxy is stored in an attribute then the full object will be returned
"""
def __get__(self, instance, owner):
if instance is not None:
obj = instance._lazy[self]
if hasattr(obj, '_idx'):
return obj.__subject__
else:
return obj
else:
return self
def __set__(self, instance, value):
instance._lazy[self] = value
[docs]
def lazy_loading_attributes(*attributes):
"""
Set attributes in the decorated class to be handled as lazy loaded objects.
An attribute that is added here will be turned into a special descriptor
that will dynamically load an objects if it is represented internally as a
LoaderProxy object and will return the real object, not the proxy!
The second thing you can do is that saving using the `.write()` command will
automatically remove the real object and turn the stored object into
a proxy
Notes
-----
This decorator will obfuscate the __init__ signature in Python 2.
This is fixed in Python 3.4+
Examples
--------
Set an attribute to a LoaderProxy
>>> my_obj.lazy_attribute = LoaderProxy(snapshot_store, 13)
>>> print my_obj.lazy_attribute
openpathsampling.Snapshot object
It will not return the proxy. This is completely hidden.
If you want to use the intelligent saving that will remove the reference
to the object you can do
>>> sample_store.write('parent', index, my_sample)
After this call the attribute `my_sample.parent` will be turned into
a proxy
"""
def _decorator(cls):
for attr in attributes:
setattr(cls, attr, DelayedLoader())
_super_init = cls.__init__
code = 'def _init(self, %s):'
source_code = '\n'.join(code)
cc = compile(source_code, '<string>', 'exec')
#exec cc in locals()
exec_(cc, locals())
@functools.wraps(cls.__init__)
def _init(self, *args, **kwargs):
self._lazy = {}
_super_init(self, *args, **kwargs)
cls.__init__ = _init
return cls
return _decorator