diff --git a/dcache/backends.py b/dcache/backends.py index bb99e49..59b21a8 100644 --- a/dcache/backends.py +++ b/dcache/backends.py @@ -1,3 +1,8 @@ +import tempfile +from json.decoder import JSONDecodeError +from pathlib import Path + +from dcache import serializers from dcache.exceptions import NotExistError @@ -49,19 +54,12 @@ def __setitem__(self, key, value): class InMemory(dict, Base): """ - InMemory backend uses a dict to save the cached values. - - >>> from dcache.backends import InMemory as InMemoryBackend - ... - >>> backend = InMemoryBackend() - >>> backend['key'] = 'value' - >>> backend['key'] - 'value' + Backend that uses a Python dict to cache values. """ def __getitem__(self, *args, **kwargs): """ - Override `dict.__get__` to raise :class:`dcache.exceptions.NotExistError` + Override `dict.__getitem__` to raise :class:`dcache.exceptions.NotExistError` instead of the default KeyError. :raises NotExistError: @@ -73,3 +71,30 @@ def __getitem__(self, *args, **kwargs): return super().__getitem__(*args, **kwargs) except KeyError as e: raise NotExistError from e + + +class File(Base): + def __init__(self, filepath=None, serializer=None): + self.memory = InMemory() + self._filepath = filepath + self.serializer = serializer or serializers.Json() + + @property + def filepath(self): + if not self._filepath: + _, self._filepath = tempfile.mkstemp() + return Path(self._filepath) + + def __getitem__(self, key): + try: + with open(self.filepath, "r") as f: + data = self.serializer.load(f) + return data[key] + except (FileNotFoundError, JSONDecodeError, KeyError) as e: + raise NotExistError from e + + def __setitem__(self, key, value): + self.memory[key] = value + + with open(self.filepath, "w") as f: + self.serializer.dump(self.memory, f) diff --git a/dcache/serializers.py b/dcache/serializers.py index 4640904..7a1b2b6 100644 --- a/dcache/serializers.py +++ b/dcache/serializers.py @@ -1 +1,43 @@ -# TODO +import json + + +class Base: + def dump(self, data, file): + raise NotImplementedError + + def dumps(self, data): + raise NotImplementedError + + def load(self, file): + raise NotImplementedError + + def loads(self, data): + raise NotImplementedError + + +class Raw(Base): + def dump(self, data, file): + return file.write(data) + + def dumps(self, data): + return data + + def load(self, file): + return file.read() + + def loads(self, data): + return data + + +class Json(Base): + def dump(self, data, file): + return json.dump(data, file) + + def dumps(self, data): + return json.dumps(data) + + def load(self, file): + return json.load(file) + + def loads(self, data): + return json.loads(data) diff --git a/tests/backends/test_file.py b/tests/backends/test_file.py new file mode 100644 index 0000000..8ab73fa --- /dev/null +++ b/tests/backends/test_file.py @@ -0,0 +1,75 @@ +import json +import pickle +import tempfile +from uuid import uuid4 + +import pytest + +from dcache.backends import File +from dcache.exceptions import NotExistError + + +def test_set(): + backend, key, value = File(), str(uuid4()), str(uuid4()) + + backend[key] = value + + with open(backend.filepath) as f: + data = json.load(f) + + assert data == {key: value} + + +def test_get_not_existing_file(): + backend = File(filepath="/not_exist") + + with pytest.raises(NotExistError): + backend[str(uuid4())] + + +def test_get_empty_file(): + backend = File() + + with pytest.raises(NotExistError): + backend[str(uuid4())] + + +def test_get_not_existing_value(): + backend, key, other_key = File(), str(uuid4()), str(uuid4()) + + backend[key] = "" + + with pytest.raises(NotExistError): + backend[other_key] + + +def test_set_and_get(): + backend, key, value = File(), str(uuid4()), str(uuid4()) + + backend[key] = value + + assert backend[key] == value + + +def test_custom_filepath(): + with tempfile.NamedTemporaryFile() as f: + backend = File(filepath=f.name) + key, value = str(uuid4()), str(uuid4()) + + backend[key] = value + + data = json.load(f) + + assert data == {key: value} + + +def test_serializer(): + backend = File(serializer=pickle) + key, value = str(uuid4()), str(uuid4()) + + backend[key] = value + + with open(backend.filepath) as f: + data = pickle.load(f) + + assert data == {key: value} diff --git a/tests/serializers/test_json.py b/tests/serializers/test_json.py new file mode 100644 index 0000000..5d71d33 --- /dev/null +++ b/tests/serializers/test_json.py @@ -0,0 +1,13 @@ +import json + +from dcache.serializers import Json + + +def test_dumps(): + serializer, data = Json(), {"key": "value"} + assert serializer.dumps(data) == json.dumps(data) + + +def test_loads(): + serializer, serialized_data = Json(), json.dumps({"key": "value"}) + assert serializer.loads(serialized_data) == json.loads(serialized_data) diff --git a/tests/serializers/test_raw.py b/tests/serializers/test_raw.py new file mode 100644 index 0000000..9d675b8 --- /dev/null +++ b/tests/serializers/test_raw.py @@ -0,0 +1,18 @@ +from uuid import uuid4 + +from dcache.serializers import Raw + + +def test_dumps(): + serializer, value = Raw(), uuid4() + assert serializer.dumps(value) == value + + +def test_loads(): + serializer, value = Raw(), uuid4() + assert serializer.loads(value) == value + + +def test_dumps_loads(): + serializer, value = Raw(), uuid4() + assert serializer.dumps(value) == serializer.loads(value)