summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2017-10-06 19:09:39 +0300
committerLars Wirzenius <liw@liw.fi>2017-10-09 21:00:41 +0300
commit4cd033e8f682f5a5ad2b64bc77b52b28f48e7020 (patch)
tree670d05be7da40b1000a158408ec79bef251ff77a
parent6bad303cda8caa13d871865edeb000c44379d8a3 (diff)
downloadqvisqve-4cd033e8f682f5a5ad2b64bc77b52b28f48e7020.tar.gz
Add: blob handling in ObjectStore
-rw-r--r--qvarn/__init__.py2
-rw-r--r--qvarn/objstore.py87
-rw-r--r--qvarn/objstore_tests.py36
3 files changed, 120 insertions, 5 deletions
diff --git a/qvarn/__init__.py b/qvarn/__init__.py
index 21eac23..b2d6bb1 100644
--- a/qvarn/__init__.py
+++ b/qvarn/__init__.py
@@ -50,6 +50,8 @@ from .objstore import (
UnknownKey,
WrongKeyType,
KeyValueError,
+ NoSuchObject,
+ BlobKeyCollision,
flatten_object,
)
diff --git a/qvarn/objstore.py b/qvarn/objstore.py
index 9449a74..aeab6bc 100644
--- a/qvarn/objstore.py
+++ b/qvarn/objstore.py
@@ -68,6 +68,12 @@ class ObjectStoreInterface: # pragma: no cover
if key not in known_keys:
raise UnknownKey(key)
+ def check_value_types(self, **keys):
+ known_keys = self.get_known_keys()
+ for key in keys:
+ if type(keys[key]) is not known_keys[key]:
+ raise KeyValueError(key, keys[key])
+
def create_object(self, obj, auxtable=True, **keys):
raise NotImplementedError()
@@ -80,11 +86,21 @@ class ObjectStoreInterface: # pragma: no cover
def find_objects(self, cond):
raise NotImplementedError()
+ def create_blob(self, blob, subpath=None, **keys):
+ raise NotImplementedError()
+
+ def get_blob(self, subpath=None, **keys):
+ raise NotImplementedError()
+
+ def remove_blob(self, blob, subpath=None, **keys):
+ raise NotImplementedError()
+
class MemoryObjectStore(ObjectStoreInterface):
def __init__(self):
self._objs = []
+ self._blobs = []
self._known_keys = {}
def get_known_keys(self):
@@ -97,17 +113,56 @@ class MemoryObjectStore(ObjectStoreInterface):
self._known_keys = keys
def create_object(self, obj, auxtable=True, **keys):
- self.check_all_keys_are_allowed(**keys)
qvarn.log.log(
'trace', msg_text='Creating object', object=repr(obj), keys=keys)
- for key in keys:
- if type(keys[key]) is not self._known_keys[key]:
- raise KeyValueError(key, keys[key])
+ self.check_all_keys_are_allowed(**keys)
+ self.check_value_types(**keys)
+ self._check_unique_object(**keys)
+ self._objs.append((obj, keys))
+ def _check_unique_object(self, **keys):
for _, k in self._objs:
if self._keys_match(k, keys):
raise KeyCollision(k)
- self._objs.append((obj, keys))
+
+ def create_blob(self, blob, **keys):
+ qvarn.log.log('trace', msg_text='Creating blob', keys=keys)
+ subpath = keys.pop('subpath')
+ self.check_all_keys_are_allowed(**keys)
+ self.check_value_types(**keys)
+ self._check_unique_blob(subpath, **keys)
+ if not self.get_objects(**keys):
+ raise NoSuchObject(keys)
+ self._blobs.append((blob, subpath, keys))
+
+ def _check_unique_blob(self, subpath, **keys):
+ for _, s, k in self._blobs:
+ if self._keys_match(k, keys) and s == subpath:
+ raise BlobKeyCollision(subpath, k)
+
+ def get_blob(self, **keys):
+ subpath = keys.pop('subpath')
+ self.check_all_keys_are_allowed(**keys)
+ self.check_value_types(**keys)
+ blobs = [
+ b
+ for b, s, k in self._blobs
+ if self._keys_match(k, keys) and s == subpath
+ ]
+ assert len(blobs) <= 1
+ if not blobs:
+ raise NoSuchObject(keys)
+ return blobs[0]
+
+ def remove_blob(self, **keys):
+ subpath = keys.pop('subpath')
+ self.check_all_keys_are_allowed(**keys)
+ self.check_value_types(**keys)
+ self._blobs = [
+ b
+ for b, s, k in self._blobs
+ if not self._keys_match(k, keys) or s != subpath
+ ]
def remove_objects(self, **keys):
self.check_all_keys_are_allowed(**keys)
@@ -250,6 +305,15 @@ class PostgresObjectStore(ObjectStoreInterface): # pragma: no cover
obj = row.pop('_obj')
return keys, obj
+ def create_blob(self, blob, subpath=None, **keys):
+ raise NotImplementedError()
+
+ def get_blob(self, subpath=None, **keys):
+ raise NotImplementedError()
+
+ def remove_blob(self, blob, subpath=None, **keys):
+ raise NotImplementedError()
+
class KeyCollision(Exception):
@@ -257,6 +321,13 @@ class KeyCollision(Exception):
super().__init__('Cannot add object with same keys: %r' % keys)
+class BlobKeyCollision(Exception):
+
+ def __init__(self, subpath, keys):
+ super().__init__(
+ 'Cannot add blob with same keys: subpath=%s %r' % (subpath, keys))
+
+
class UnknownKey(Exception):
def __init__(self, key):
@@ -277,6 +348,12 @@ class KeyValueError(Exception):
super().__init__('Key %r value %r has the wrong type' % (key, value))
+class NoSuchObject(Exception):
+
+ def __init__(self, keys):
+ super().__init__('No object/blob with keys {}'.format(keys))
+
+
def flatten_object(obj):
return list(sorted(set(_flatten(obj))))
diff --git a/qvarn/objstore_tests.py b/qvarn/objstore_tests.py
index 5103d47..f8d631a 100644
--- a/qvarn/objstore_tests.py
+++ b/qvarn/objstore_tests.py
@@ -28,6 +28,8 @@ class ObjectStoreTests(unittest.TestCase):
self.obj2 = {
'name': 'this is my other object',
}
+ self.blob1 = 'my first blob'
+ self.blob2 = 'my other blob'
def create_store(self, **keys):
store = qvarn.MemoryObjectStore()
@@ -123,6 +125,40 @@ class ObjectStoreTests(unittest.TestCase):
[({'key': '1st'}, self.obj1)]
)
+ def test_has_no_blob_initially(self):
+ store = self.create_store(key=str)
+ store.create_object(self.obj1, key='1st')
+ with self.assertRaises(qvarn.NoSuchObject):
+ store.get_blob(key='1st', subpath='blob')
+
+ def test_add_blob_to_nonexistent_parent_fails(self):
+ store = self.create_store(key=str)
+ store.create_object(self.obj1, key='1st')
+ with self.assertRaises(qvarn.NoSuchObject):
+ store.create_blob(self.blob1, key='2nd', subpath='blob')
+
+ def test_adds_blob(self):
+ store = self.create_store(key=str)
+ store.create_object(self.obj1, key='1st')
+ store.create_blob(self.blob1, key='1st', subpath='blob')
+ blob = store.get_blob(key='1st', subpath='blob')
+ self.assertEqual(blob, self.blob1)
+
+ def test_add_blob_twice_fails(self):
+ store = self.create_store(key=str)
+ store.create_object(self.obj1, key='1st')
+ store.create_blob(self.blob1, key='1st', subpath='blob')
+ with self.assertRaises(qvarn.BlobKeyCollision):
+ store.create_blob(self.blob1, key='1st', subpath='blob')
+
+ def test_removes_blob(self):
+ store = self.create_store(key=str)
+ store.create_object(self.obj1, key='1st')
+ store.create_blob(self.blob1, key='1st', subpath='blob')
+ store.remove_blob(key='1st', subpath='blob')
+ with self.assertRaises(qvarn.NoSuchObject):
+ store.get_blob(key='1st', subpath='blob')
+
class FlattenObjectsTests(unittest.TestCase):