Skip to content

Commit

Permalink
fix dynamic updates of blind reads
Browse files Browse the repository at this point in the history
  • Loading branch information
JJ11teen committed Jul 12, 2021
1 parent f430a80 commit b8c61d1
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 15 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ del cm["key"]

Each `cloud-mapping` keeps an internal dict of [etags](https://en.wikipedia.org/wiki/HTTP_ETag) which it uses to ensure it is only reading/overwriting/deleting data it expects to. If the value in storage is not what the `cloud-mapping` expects, a `cloudmappings.errors.KeySyncError()` will be thrown.

If you would like to enable get (read) operations without ensuring etags, you can set `get_blindly=True`. This can be set in the constructor, or dynamically turned on and off directly on the `cloud-mapping` instance. Blindly getting a value that doesn't exist in the cloud will return `None`.
If you would like to enable read (get) operations without ensuring etags, you can set `read_blindly=True`. This can be set in the constructor, or dynamically turned on and off with `set_read_blindly(True)` and `set_read_blindly(False)` respectively. Blindly reading a value that doesn't exist in the cloud will return `None`.

If you know what you are doing and you want an operation other than get to go through despite etags, you will need to sync your `cloud-mapping` with the cloud by calling either `.sync_with_cloud()` to sync all keys or `.sync_with_cloud(key_prefix)` to sync a specific key or subset of keys. By default `.sync_with_cloud()` is called on instantiation of a `cloud-mapping` if the underlying provider storage already exists. You may skip this initial sync by passing an additional `sync_initially=False` parameter when you instantiate your `cloud-mapping`.

Expand Down
17 changes: 12 additions & 5 deletions src/cloudmappings/cloudstoragemapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ def __init__(
self,
storage_provider: StorageProvider,
sync_initially: bool = True,
get_blindly: bool = False,
read_blindly: bool = False,
) -> None:
self._storage_provider = storage_provider
self._etags = {}
self.get_blindly = get_blindly
self._read_blindly = read_blindly
if self._storage_provider.create_if_not_exists() and sync_initially:
self.sync_with_cloud()

Expand All @@ -37,11 +37,17 @@ def sync_with_cloud(self, key_prefix: str = None) -> None:
def etags(self):
return self._etags

def get_read_blindly(self) -> bool:
return self._read_blindly

def set_read_blindly(self, read_blindly: bool):
self._read_blindly = read_blindly

def __getitem__(self, key: str) -> bytes:
if not self.get_blindly and key not in self._etags:
if not self._read_blindly and key not in self._etags:
raise KeyError(key)
return self._storage_provider.download_data(
key=self._encode_key(key), etag=None if self.get_blindly else self._etags[key]
key=self._encode_key(key), etag=None if self._read_blindly else self._etags[key]
)

def __setitem__(self, key: str, value: bytes) -> None:
Expand Down Expand Up @@ -86,7 +92,8 @@ def with_buffers(cls, input_buffers, output_buffers, *args, **kwargs) -> "CloudM

mapping.sync_with_cloud = raw_mapping.sync_with_cloud
mapping.etags = raw_mapping.etags
mapping.get_blindly = raw_mapping.get_blindly
mapping.get_read_blindly = raw_mapping.get_read_blindly
mapping.set_read_blindly = raw_mapping.set_read_blindly
return mapping

@classmethod
Expand Down
8 changes: 4 additions & 4 deletions tests/tests/2_singlecloudmapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ def test_basic_setting_and_getting(self, storage_provider: StorageProvider, test
assert cm[test_id + "-key-a"] == b"uncapitalised"
assert cm[test_id + "-key-3"] == b"three"

def test_get_blindly_defaults_none(self, storage_provider: StorageProvider, test_id: str):
def test_read_blindly_defaults_none(self, storage_provider: StorageProvider, test_id: str):
cm = CloudMapping(storage_provider=storage_provider, sync_initially=False)
key = test_id + "/get-blindly-test"
key = test_id + "/read-blindly-test"

# CloudMappings default to not getting blindly:
assert not cm.get_blindly
assert not cm.get_read_blindly()
# If get_blindly, values default to None
cm.get_blindly = True
cm.set_read_blindly(True)
assert cm[key] is None

def test_complex_keys(self, storage_provider: StorageProvider, test_id: str):
Expand Down
10 changes: 5 additions & 5 deletions tests/tests/3_multiplecloudmapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,25 @@ def test_no_ownership_error(self, storage_provider: StorageProvider, test_id: st
with pytest.raises(KeySyncError):
del sess_3[key]

def test_blind_get(self, storage_provider: StorageProvider, test_id: str):
def test_blind_read(self, storage_provider: StorageProvider, test_id: str):
sess_1 = CloudMapping(storage_provider=storage_provider, sync_initially=False)
sess_2 = CloudMapping(storage_provider=storage_provider, sync_initially=False, get_blindly=True)
sess_3 = CloudMapping(storage_provider=storage_provider, sync_initially=False, get_blindly=False)
sess_2 = CloudMapping(storage_provider=storage_provider, sync_initially=False, read_blindly=True)
sess_3 = CloudMapping(storage_provider=storage_provider, sync_initially=False, read_blindly=False)
key = test_id + "/concurrent/blind-get-test"

# Session 1 uploads data:
sess_1[key] = b"data"

# Session 2 blindly gets by default, but can be turned off:
assert sess_2[key] == b"data"
sess_2.get_blindly = False
sess_2.set_read_blindly(False)
with pytest.raises(KeyError):
sess_2[key]

# Session 3 dones't blindly get by default, but can be set to:
with pytest.raises(KeyError):
sess_3[key]
sess_3.get_blindly = True
sess_3.set_read_blindly(True)
assert sess_3[key] == b"data"

def test_manual_change_error(self, storage_provider: StorageProvider, test_id: str):
Expand Down
21 changes: 21 additions & 0 deletions tests/tests/4_cloudmappingutilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@


class CloudMappingUtilityTests:
def test_with_buffers_includes_extra(self, storage_provider: StorageProvider, test_id: str):
cm = CloudMapping.with_buffers(
[lambda i: i],
[lambda i: i],
storage_provider=storage_provider,
sync_initially=False,
)

key = test_id + "includes-extras"

# etags are inherited
assert cm.etags is not None
cm[key] = b"0"
assert key in cm.etags

# get_blindy is inherited
assert cm.get_read_blindly() == False
cm.set_read_blindly(True)
assert cm.get_read_blindly() == True
assert cm.get_read_blindly() == cm.d.get_read_blindly()

def test_with_buffers_fails_with_uneven_buffers(self, storage_provider: StorageProvider):
with pytest.raises(ValueError, match="equal number of input buffers as output buffers"):
CloudMapping.with_buffers(
Expand Down

0 comments on commit b8c61d1

Please sign in to comment.