Skip to content
Closed
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ Imagine you have a service where you want to cache 3 pydantic models in a single
import asyncio

from kv_store_adapter.adapters.pydantic import PydanticAdapter
from kv_store_adapter.stores.wrappers.single_collection import SingleCollectionWrapper
from kv_store_adapter.wrappers.single_collection import SingleCollectionWrapper
from kv_store_adapter.stores.memory import MemoryStore
from pydantic import BaseModel

Expand Down
6 changes: 0 additions & 6 deletions src/kv_store_adapter/stores/wrappers/__init__.py

This file was deleted.

81 changes: 0 additions & 81 deletions src/kv_store_adapter/stores/wrappers/passthrough_cache.py

This file was deleted.

76 changes: 0 additions & 76 deletions src/kv_store_adapter/stores/wrappers/prefix_collection.py

This file was deleted.

68 changes: 0 additions & 68 deletions src/kv_store_adapter/stores/wrappers/single_collection.py

This file was deleted.

15 changes: 15 additions & 0 deletions src/kv_store_adapter/wrappers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from .clamp_ttl import TTLClampWrapper
from .passthrough_cache import PassthroughCacheWrapper
from .prefix_collection import PrefixCollectionWrapper
from .prefix_key import PrefixKeyWrapper
from .single_collection import SingleCollectionWrapper
from .statistics import StatisticsWrapper

__all__ = [
"TTLClampWrapper",
"PassthroughCacheWrapper",
"PrefixCollectionWrapper",
"PrefixKeyWrapper",
"SingleCollectionWrapper",
"StatisticsWrapper"
]
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
from typing import Any

from typing_extensions import override
from kv_store_adapter.types import KVStoreProtocol

from kv_store_adapter.stores.base.unmanaged import BaseKVStore
from kv_store_adapter.types import TTLInfo


class TTLClampWrapper(BaseKVStore):
class TTLClampWrapper:
"""Wrapper that enforces a maximum TTL for puts into the store."""

def __init__(self, store: BaseKVStore, min_ttl: float, max_ttl: float, missing_ttl: float | None = None) -> None:
def __init__(self, store: KVStoreProtocol, min_ttl: float, max_ttl: float, missing_ttl: float | None = None) -> None:
"""Initialize the TTL clamp wrapper.

Args:
Expand All @@ -18,16 +15,14 @@ def __init__(self, store: BaseKVStore, min_ttl: float, max_ttl: float, missing_t
max_ttl: The maximum TTL for puts into the store.
missing_ttl: The TTL to use for entries that do not have a TTL. Defaults to None.
"""
self.store: BaseKVStore = store
self.store: KVStoreProtocol = store
self.min_ttl: float = min_ttl
self.max_ttl: float = max_ttl
self.missing_ttl: float | None = missing_ttl

@override
async def get(self, collection: str, key: str) -> dict[str, Any] | None:
return await self.store.get(collection=collection, key=key)

@override
async def put(self, collection: str, key: str, value: dict[str, Any], *, ttl: float | None = None) -> None:
if ttl is None and self.missing_ttl:
ttl = self.missing_ttl
Expand All @@ -40,30 +35,8 @@ async def put(self, collection: str, key: str, value: dict[str, Any], *, ttl: fl

await self.store.put(collection=collection, key=key, value=value, ttl=ttl)

@override
async def delete(self, collection: str, key: str) -> bool:
return await self.store.delete(collection=collection, key=key)

@override
async def exists(self, collection: str, key: str) -> bool:
return await self.store.exists(collection=collection, key=key)

@override
async def keys(self, collection: str) -> list[str]:
return await self.store.keys(collection=collection)

@override
async def clear_collection(self, collection: str) -> int:
return await self.store.clear_collection(collection=collection)

@override
async def ttl(self, collection: str, key: str) -> TTLInfo | None:
return await self.store.ttl(collection=collection, key=key)

@override
async def list_collections(self) -> list[str]:
return await self.store.list_collections()

@override
async def cull(self) -> None:
await self.store.cull()
return await self.store.exists(collection=collection, key=key)
43 changes: 43 additions & 0 deletions src/kv_store_adapter/wrappers/passthrough_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Any

from kv_store_adapter.types import KVStoreProtocol


class PassthroughCacheWrapper:
"""Wrapper that uses two stores, ideal for combining a local and distributed store."""

def __init__(self, primary_store: KVStoreProtocol, cache_store: KVStoreProtocol) -> None:
"""Initialize the passthrough cache wrapper. Items are first checked in the primary store and if not found, are
checked in the secondary store. Operations are performed on both stores but are not atomic.

Note: This wrapper only implements the core KVStoreProtocol operations. Operations like expiry culling
against the primary store will not be reflected in the cache store if the underlying stores support such operations.

Args:
primary_store: The primary store the data will live in.
cache_store: The write-through (likely ephemeral) cache to use.
"""
self.cache_store: KVStoreProtocol = cache_store
self.primary_store: KVStoreProtocol = primary_store

async def get(self, collection: str, key: str) -> dict[str, Any] | None:
if cache_store_value := await self.cache_store.get(collection=collection, key=key):
return cache_store_value

if primary_store_value := await self.primary_store.get(collection=collection, key=key):
# Cache the value from primary store (without TTL since we can't get it from protocol)
await self.cache_store.put(collection=collection, key=key, value=primary_store_value)
return primary_store_value
return None

async def put(self, collection: str, key: str, value: dict[str, Any], *, ttl: float | None = None) -> None:
_ = await self.cache_store.delete(collection=collection, key=key)
await self.primary_store.put(collection=collection, key=key, value=value, ttl=ttl)

async def delete(self, collection: str, key: str) -> bool:
deleted = await self.primary_store.delete(collection=collection, key=key)
_ = await self.cache_store.delete(collection=collection, key=key)
return deleted

async def exists(self, collection: str, key: str) -> bool:
return await self.get(collection=collection, key=key) is not None
Loading