Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add File backend #31

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 34 additions & 9 deletions dcache/backends.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import tempfile
from json.decoder import JSONDecodeError
from pathlib import Path

from dcache import serializers
from dcache.exceptions import NotExistError


Expand Down Expand Up @@ -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:
Expand All @@ -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)
44 changes: 43 additions & 1 deletion dcache/serializers.py
Original file line number Diff line number Diff line change
@@ -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)
75 changes: 75 additions & 0 deletions tests/backends/test_file.py
Original file line number Diff line number Diff line change
@@ -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}
13 changes: 13 additions & 0 deletions tests/serializers/test_json.py
Original file line number Diff line number Diff line change
@@ -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)
18 changes: 18 additions & 0 deletions tests/serializers/test_raw.py
Original file line number Diff line number Diff line change
@@ -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)