Skip to content

Commit

Permalink
✨ MemoryStore
Browse files Browse the repository at this point in the history
  • Loading branch information
simonwoerpel committed Sep 7, 2024
1 parent edfa709 commit 62539d0
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 3 deletions.
5 changes: 4 additions & 1 deletion anystore/store/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from anystore.settings import Settings
from anystore.store.base import BaseStore
from anystore.store.fs import Store
from anystore.store.memory import MemoryStore
from anystore.util import ensure_uri


Expand All @@ -25,6 +26,8 @@ def get_store(settings: Settings | None = None, **kwargs) -> BaseStore:
uri = settings.uri
uri = ensure_uri(uri)
parsed = urlparse(uri)
if parsed.scheme == "memory":
return MemoryStore(uri=uri)
if parsed.scheme == "redis":
try:
from anystore.store.redis import RedisStore
Expand All @@ -42,4 +45,4 @@ def get_store(settings: Settings | None = None, **kwargs) -> BaseStore:
return Store(**kwargs)


__all__ = ["get_store", "Store", "RedisStore", "SqlStore"]
__all__ = ["get_store", "Store", "MemoryStore"]
61 changes: 61 additions & 0 deletions anystore/store/memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
Simple memory dictionary store
"""

from datetime import datetime, timedelta
from typing import Any, Generator

from anystore.exceptions import DoesNotExist
from anystore.logging import get_logger
from anystore.store.base import BaseStore
from anystore.types import Value


log = get_logger(__name__)


class MemoryStore(BaseStore):
def __init__(self, **data):
super().__init__(**data)
self._store: dict[str, Any] = {}
self._ttl: dict[str, datetime] = {}

def _write(self, key: str, value: Value, **kwargs) -> None:
self._store[key] = value
ttl = kwargs.get("ttl")
if ttl:
self._ttl[key] = datetime.now() + timedelta(seconds=ttl)

def _read(self, key: str, raise_on_nonexist: bool | None = True, **kwargs) -> Any:
self._check_ttl(key)
# `None` could be stored as an actual value, to implement `raise_on_nonexist`
# we need to check this first:
if raise_on_nonexist and not self._exists(key):
raise DoesNotExist
res = self._store.get(key)
# mimic fs read mode:
if kwargs.get("mode") == "r" and isinstance(res, bytes):
res = res.decode()
return res

def _exists(self, key: str) -> bool:
self._check_ttl(key)
return key in self._store

def _delete(self, key: str) -> None:
self._store.pop(key, None)

def _get_key_prefix(self) -> str:
return "anystore"

def _iterate_keys(self, prefix: str | None = None) -> Generator[str, None, None]:
prefix = self.get_key(prefix or "")
key_prefix = self._get_key_prefix()
for key in self._store:
if key.startswith(prefix):
yield key[len(key_prefix) + 1 :]

def _check_ttl(self, key: str) -> None:
ttl = self._ttl.get(key)
if ttl and datetime.now() > ttl:
self._delete(key)
10 changes: 8 additions & 2 deletions tests/test_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from anystore.exceptions import DoesNotExist
from anystore.store import Store, get_store
from anystore.store.base import BaseStore
from anystore.store.memory import MemoryStore
from anystore.store.redis import RedisStore
from anystore.store.sql import SqlStore
from tests.conftest import setup_s3
Expand Down Expand Up @@ -42,7 +43,7 @@ def _test_store(uri: str) -> bool:
# iterate
keys = [k for k in store.iterate_keys()]
assert len(keys) == 3
# assert all(store.exists(k) for k in keys)
assert all(store.exists(k) for k in keys)
keys = [k for k in store.iterate_keys("foo")]
assert keys[0] == "foo/bar/baz"
assert len(keys) == 1
Expand All @@ -56,7 +57,7 @@ def _test_store(uri: str) -> bool:
assert store.get("popped", raise_on_nonexist=False) is None

# ttl
if isinstance(store, (RedisStore, SqlStore)):
if isinstance(store, (RedisStore, SqlStore, MemoryStore)):
store.put("expired", 1, ttl=1)
assert store.get("expired") == 1
time.sleep(1)
Expand All @@ -79,6 +80,10 @@ def test_store_sql(tmp_path):
assert _test_store(f"sqlite:///{tmp_path}/db.sqlite")


def test_store_memory():
assert _test_store("memory:///")


def test_store_fs(tmp_path, fixtures_path):
assert _test_store(tmp_path)

Expand Down Expand Up @@ -115,3 +120,4 @@ def test_store_intialize(fixtures_path):
assert store.uri == "file:///tmp/cache"

store = Store(uri="s3://anystore", raise_on_nonexist=False)
assert store.raise_on_nonexist is False

0 comments on commit 62539d0

Please sign in to comment.