Skip to content

Commit

Permalink
add read_blindly_error flag
Browse files Browse the repository at this point in the history
  • Loading branch information
JJ11teen committed Aug 29, 2022
1 parent ac5690b commit 22602a4
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 13 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,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 read (get) operations without ensuring etags, you can set `read_blindly=True`. This can be set in the constructor, or dynamically on the cloud-mapping instance. Blindly reading a value that doesn't exist in the cloud will return the mapping's current value of `read_blindly_default` (which itself defaults to `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 on the cloud-mapping instance. Blindly reading a value that doesn't exist in the cloud will only raise a KeyError if `read_blindly_error=True`, otherwise it will return the current value of `read_blindly_default` (which itself defaults to `None`). All of these can be changed dynamically and set at initialisation.

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
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = cloud-mappings
version = 1.1.0
version = 1.2.0
author = Lucas Sargent
author_email = lucas.sargent@outlook.com
description = MutableMapping interfaces for common cloud storage providers
Expand Down
29 changes: 21 additions & 8 deletions src/cloudmappings/cloudstoragemapping.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from functools import partial
from typing import Any, Callable, Dict, List, MutableMapping
from typing import Any, Callable, Dict, List, MutableMapping, Type

from .storageproviders.storageprovider import StorageProvider

Expand All @@ -16,9 +16,12 @@ class CloudMapping(MutableMapping):
read_blindly : bool, default=False
Whether to read blindly or not by default. See `read_blindly` attribute for more
information.
read_blindly_error : bool, default=False
Whether to raise `KeyError`s when read_blindly is enabled and the key does not have a value
in the cloud.
read_blindly_default : Any, default=None
The value to return when read_blindly is enabled and the key does not have
a value in the cloud.
The value to return when read_blindly is enabled, the key does not have a value in the
cloud, and read_blindly_error is `False`.
ordered_dumps_funcs : List[Callable]
An ordered list of functions to pass values through before saving bytes to the cloud.
The last function must return a bytes-like object.
Expand All @@ -37,25 +40,32 @@ class CloudMapping(MutableMapping):
When read blindly is `True`, a cloud-mapping will return the latest cloud version
for any key accessed, including keys it has no prior knowledge of (ie not in it's etag
dict). If there is no value for a key in the cloud, the current value of
dict). If there is no value for a key in the cloud, whether a `KeyValue` error is
raised is controlled by the `read_blindly_error` flag. If `False`, the current value of
`read_blindly_default` will be returned.
When read blindly is `True` a cloud-mapping will not raise `KeyValue` or
`cloudmappings.errors.KeySyncError` errors for read/get operations.
When read blindly is `True` a cloud-mapping will not raise `cloudmappings.errors.KeySyncError`
errors for read/get operations.
By default a cloud-mapping is instantiated with read blindly set to `False`.
"""

read_blindly_error: bool
"""Whether to raise a `KeyValue` error when read_blindly is `True` and the key does not have
a value in the cloud. If `True`, this takes prescedence over `read_blindly_default`.
"""

read_blindly_default: Any
"""The value to return when read_blindly is `True` and the key does not have
a value in the cloud.
"""The value to return when read_blindly is `True`, the key does not have a value in the cloud,
and read_blindly_error is `False`.
"""

def __init__(
self,
storage_provider: StorageProvider,
sync_initially: bool = True,
read_blindly: bool = False,
read_blindly_error: bool = False,
read_blindly_default: Any = None,
ordered_dumps_funcs: List[Callable] = None,
ordered_loads_funcs: List[Callable] = None,
Expand Down Expand Up @@ -87,6 +97,7 @@ def __init__(
self._ordered_loads_funcs = ordered_loads_funcs if ordered_loads_funcs is not None else []

self.read_blindly = read_blindly
self.read_blindly_error = read_blindly_error
self.read_blindly_default = read_blindly_default

if self._storage_provider.create_if_not_exists() and sync_initially:
Expand Down Expand Up @@ -141,6 +152,8 @@ def __getitem__(self, key: str) -> Any:
key=self._encode_key(key), etag=None if self.read_blindly else self._etags[key]
)
if self.read_blindly and value is None:
if self.read_blindly_error:
raise KeyError(key)
return self.read_blindly_default
for loads in self._ordered_loads_funcs:
value = loads(value)
Expand Down
28 changes: 25 additions & 3 deletions tests/tests/2_singlecloudmapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,38 @@ 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_read_blindly_defaults_none(self, storage_provider: StorageProvider, test_id: str):
def test_read_blindy(self, storage_provider: StorageProvider, test_id: str):
cm = CloudMapping(storage_provider=storage_provider, sync_initially=False)
key = test_id + "/read-blindly-test"

# CloudMappings default to not getting blindly:
assert not cm.read_blindly
# If get_blindly, values default to None
with pytest.raises(KeyError):
cm[key]

cm.read_blindly = True
assert cm[key] is None

def test_read_blindly_error(self, storage_provider: StorageProvider, test_id: str):
cm = CloudMapping(storage_provider=storage_provider, sync_initially=False, read_blindly=True)
key = test_id + "/read-blindly-error-test"

assert not cm.read_blindly_error
assert cm[key] is None

cm.read_blindly_error = True
with pytest.raises(KeyError):
cm[key]

def test_read_blindly_default(self, storage_provider: StorageProvider, test_id: str):
cm = CloudMapping(storage_provider=storage_provider, sync_initially=False, read_blindly=True)
key = test_id + "/read-blindly-default-test"

assert cm.read_blindly_default is None
assert cm[key] is None

cm.read_blindly_default = 10
assert cm[key] == 10

def test_complex_keys(self, storage_provider: StorageProvider, test_id: str):
cm = CloudMapping(storage_provider=storage_provider, sync_initially=False)
key1 = test_id + "/here/are/some/sub/dirs"
Expand Down

0 comments on commit 22602a4

Please sign in to comment.