Skip to content

Commit

Permalink
object auto-loading by secondary key
Browse files Browse the repository at this point in the history
  • Loading branch information
divi255 committed Nov 30, 2019
1 parent f0a5933 commit f7b7dc3
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 6 deletions.
22 changes: 22 additions & 0 deletions doc/factory.rst
Expand Up @@ -18,6 +18,9 @@ SmartObjectFactory class
Object auto-loading
===================

By primary key
--------------

If *autoload=True* argument is used on factory creation, the factory will try
automatically load Smart Objects from the storage when **factory.get()** method
is called and the object doesn't exist.
Expand All @@ -33,6 +36,25 @@ If *allow_empty=False* for the object storage, **factory.get()** method raises
*LookupError* (for RDBMS storages) or *FileNotFoundError* (for file-based
storages) exceptions in case if storage doesn't have an object with such PK.

By property
-----------

SmartObjectFactory also supports loading objects by property as secondary key.
To use this feature:

* Factory **autoload** option should be set to *True*
* For get method, **prop** argument should be specified
* You may also specify storage ID to load missing objects from, if not
specified - default storage is used
* Objects are auto-loaded from the storage in two cases:

* when object is missing in factory
* when *get_all=True* is specified for **factory.get()** method.

.. note::

Loading objects by prop is supported only for RDBMS storages

Object cache size limit
=======================

Expand Down
38 changes: 33 additions & 5 deletions smartobject/factory.py
Expand Up @@ -139,7 +139,7 @@ def touch(self, obj):
obj = obj._get_primary_key()
self._objects_last_access[obj] = time.perf_counter()

def get(self, key=None, prop=None):
def get(self, key=None, prop=None, storage_id=None, get_all=False, opts={}):
"""
Get Smart Object from factory
Expand All @@ -148,6 +148,11 @@ def get(self, key=None, prop=None):
returned
prop: object prop (should be indexed). If no prop specified, object
is looked up by primary key
storage_id: if prop is specified, look up for objects in specified
storage, if no objects present in factory. storage should
support
get_all: always lookup for all objects in storage
opts: options for object constructor if loaded from storage
Raises:
KeyError: if object with such key doesn't exist (for primary key)
FileNotFoundError, LookupError: if auto-load is on but object not
Expand All @@ -162,9 +167,32 @@ def get(self, key=None, prop=None):
return self._objects.copy()
elif prop is not None:
result = []
for obj in self._objects_by_prop[prop][key]:
self.touch(obj)
result.append(obj)
if not get_all:
for obj in self._objects_by_prop[prop][key]:
self.touch(obj)
result.append(obj)
if self.autoload and (get_all or not result):
from . import storage
try:
objects = storage.get_storage(storage_id).load_by_prop(
key, prop)
except RuntimeError:
objects = []
for d in objects:
if 'data' in d:
logger.debug(
f'Creating object {self._object_class.__name__}'
)
o = self._object_class(**opts)
o.set_prop(d['data'],
_allow_readonly=True,
sync=False,
save=False)
o.after_load(opts=d.get('info', {}))
o.sync()
if o._get_primary_key() not in self._objects:
self.create(obj=o, save=False)
result.append(o)
return result
else:
try:
Expand All @@ -173,7 +201,7 @@ def get(self, key=None, prop=None):
return obj
except KeyError:
if self.autoload:
obj = self._object_class()
obj = self._object_class(**opts)
obj._set_primary_key(key)
obj.load()
self.append(obj)
Expand Down
22 changes: 21 additions & 1 deletion smartobject/storage.py
Expand Up @@ -64,6 +64,16 @@ def load(self, pk, **kwargs):
"""
return {}

def load_by_prop(self, key, prop, **kwargs):
"""
Load object data from the storage by secondary key
Args:
key: object key value
prop: object key name
"""
raise RuntimeError('Not implemented')

def load_all(self, **kwargs):
"""
Load object data for all objecta and return it as list generator
Expand Down Expand Up @@ -260,7 +270,17 @@ def load_all(self, **kwargs):
while True:
d = result.fetchone()
if d is None: break
yield { 'data': dict(d) }
yield {'data': dict(d)}

def load_by_prop(self, key, prop, **kwargs):
with self.__lock:
result = self.get_db().execute(
self.sa.text(f'select * from {self.table} where {prop}=:value'),
value=key)
while True:
d = result.fetchone()
if d is None: break
yield {'data': dict(d)}

def get_prop(self, pk, prop, **kwargs):
with self.__lock:
Expand Down
24 changes: 24 additions & 0 deletions tests/test.py
Expand Up @@ -566,5 +566,29 @@ def test_factory_lru():
with pytest.raises(RuntimeError):
factory.cleanup_storage()

def test_factory_load_by_secondary():
db = _prepare_t2_db()
storage = smartobject.SQLAStorage(db, 't2')
smartobject.define_storage(storage)
factory = smartobject.SmartObjectFactory(T2, autoload=True, autosave=True)
factory.add_index('login')
o1 = factory.create()
o2 = factory.create()
o3 = factory.create()
o4 = factory.create()
o1.set_prop('login', 'test')
o2.set_prop('login', 'test')
o3.set_prop('login', 'test2')
o4.set_prop('login', 'test3')
factory.save()
factory.clear()
test1 = factory.get(o1.id)
tests = factory.get('test', prop='login')
assert len(tests) == 1
tests = factory.get('test', prop='login', get_all=True)
assert len(tests) == 2
tests = factory.get('test2', prop='login', get_all=True)
assert len(tests) == 1

clean()
test_factory_load_by_secondary()

0 comments on commit f7b7dc3

Please sign in to comment.