Skip to content

Commit c6739ea

Browse files
strawgateCopilot
andauthored
Switch adapters to using the protocol instead of stores (#2)
* Switch adapters to using the protocol instead of stores * Update test workflows * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> * Bump lock --------- Co-authored-by: Copilot <[email protected]>
1 parent c4feea5 commit c6739ea

File tree

10 files changed

+29
-64
lines changed

10 files changed

+29
-64
lines changed

.github/workflows/publish-py-kv-store-adapter.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@ name: Publish py-kv-store-adapter to PyPI
33
on:
44
release:
55
types: [created]
6-
push:
7-
branches:
8-
- main
9-
pull_request:
10-
branches:
11-
- main
126
workflow_dispatch:
137

148
jobs:

.github/workflows/test_pull_request.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
name: Run tests for pull requests
1+
name: Run tests for pull requests and merges
22

33
on:
4-
release:
5-
types: [created]
64
pull_request:
75
branches:
86
- main
7+
push:
8+
branches:
9+
- main
910
workflow_dispatch:
1011

1112
jobs:

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ For detailed configuration options and all available stores, see [DEVELOPING.md]
7777
We strive to support atomicity and consistency across all stores and operations in the KVStoreProtocol. That being said,
7878
there are operations available via the BaseKVStore class which are management operations like listing keys, listing collections, clearing collections, culling expired entries, etc. These operations may not be atomic, may be eventually consistent across stores, or may have other limitations (like limited to returning a certain number of keys).
7979

80-
## Adapters
80+
## Protocol Adapters
8181

82-
The library provides an adapter pattern simplifying the user of the protocol/store. Adapters themselves do not implement the `KVStoreProtocol` interface and cannot be nested. Adapters can be used with wrappers and stores interchangeably.
82+
The library provides an adapter pattern simplifying the use of the protocol/store. Adapters themselves do not implement the `KVStoreProtocol` interface and cannot be nested. Adapters can be used with anything that implements the `KVStoreProtocol` interface but do not comply with the full `BaseKVStore` interface and thus lack management operations like listing keys, listing collections, clearing collections, culling expired entries, etc.
8383

8484
The following adapters are available:
8585

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "kv-store-adapter"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
description = "A pluggable interface for KV Stores"
55
readme = "README.md"
66
requires-python = ">=3.10"

src/kv_store_adapter/adapters/pydantic.py

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,20 @@
44
from pydantic_core import PydanticSerializationError
55

66
from kv_store_adapter.errors import DeserializationError, SerializationError
7-
from kv_store_adapter.stores.base.unmanaged import BaseKVStore
8-
from kv_store_adapter.types import TTLInfo
7+
from kv_store_adapter.types import KVStoreProtocol
98

109
T = TypeVar("T", bound=BaseModel)
1110

1211

1312
class PydanticAdapter(Generic[T]):
14-
"""Adapter around a KV Store that allows type-safe persistence of Pydantic models."""
13+
"""Adapter around a KVStoreProtocol-compliant Store that allows type-safe persistence of Pydantic models."""
1514

16-
def __init__(self, store: BaseKVStore, pydantic_model: type[T]) -> None:
17-
self.store: BaseKVStore = store
15+
def __init__(self, store_protocol: KVStoreProtocol, pydantic_model: type[T]) -> None:
16+
self.store_protocol: KVStoreProtocol = store_protocol
1817
self.pydantic_model: type[T] = pydantic_model
1918

2019
async def get(self, collection: str, key: str) -> T | None:
21-
if value := await self.store.get(collection=collection, key=key):
20+
if value := await self.store_protocol.get(collection=collection, key=key):
2221
try:
2322
return self.pydantic_model.model_validate(obj=value)
2423
except ValidationError as e:
@@ -34,25 +33,10 @@ async def put(self, collection: str, key: str, value: T, *, ttl: float | None =
3433
msg = f"Invalid Pydantic model: {e}"
3534
raise SerializationError(msg) from e
3635

37-
await self.store.put(collection=collection, key=key, value=value_dict, ttl=ttl)
36+
await self.store_protocol.put(collection=collection, key=key, value=value_dict, ttl=ttl)
3837

3938
async def delete(self, collection: str, key: str) -> bool:
40-
return await self.store.delete(collection=collection, key=key)
39+
return await self.store_protocol.delete(collection=collection, key=key)
4140

4241
async def exists(self, collection: str, key: str) -> bool:
43-
return await self.store.exists(collection=collection, key=key)
44-
45-
async def keys(self, collection: str) -> list[str]:
46-
return await self.store.keys(collection=collection)
47-
48-
async def clear_collection(self, collection: str) -> int:
49-
return await self.store.clear_collection(collection=collection)
50-
51-
async def ttl(self, collection: str, key: str) -> TTLInfo | None:
52-
return await self.store.ttl(collection=collection, key=key)
53-
54-
async def list_collections(self) -> list[str]:
55-
return await self.store.list_collections()
56-
57-
async def cull(self) -> None:
58-
await self.store.cull()
42+
return await self.store_protocol.exists(collection=collection, key=key)
Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
from typing import Any
22

3-
from kv_store_adapter.stores.base.unmanaged import BaseKVStore
4-
from kv_store_adapter.types import TTLInfo
3+
from kv_store_adapter.types import KVStoreProtocol
54

65

76
class SingleCollectionAdapter:
8-
"""Adapter around a KV Store that only allows one collection."""
7+
"""Adapter around a KVStoreProtocol-compliant Store that only allows one collection."""
98

10-
def __init__(self, store: BaseKVStore, collection: str) -> None:
11-
self.store: BaseKVStore = store
9+
def __init__(self, store: KVStoreProtocol, collection: str) -> None:
10+
self.store: KVStoreProtocol = store
1211
self.collection: str = collection
1312

1413
async def get(self, key: str) -> dict[str, Any] | None:
@@ -22,9 +21,3 @@ async def delete(self, key: str) -> bool:
2221

2322
async def exists(self, key: str) -> bool:
2423
return await self.store.exists(collection=self.collection, key=key)
25-
26-
async def keys(self) -> list[str]:
27-
return await self.store.keys(collection=self.collection)
28-
29-
async def ttl(self, key: str) -> TTLInfo | None:
30-
return await self.store.ttl(collection=self.collection, key=key)

src/kv_store_adapter/stores/disk/store.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from pathlib import Path
12
from typing import Any, overload
23

34
from diskcache import Cache
@@ -20,15 +21,18 @@ class DiskStore(BaseManagedKVStore):
2021
def __init__(self, *, cache: Cache) -> None: ...
2122

2223
@overload
23-
def __init__(self, *, path: str, size_limit: int | None = None) -> None: ...
24+
def __init__(self, *, path: Path | str, size_limit: int | None = None) -> None: ...
2425

25-
def __init__(self, *, cache: Cache | None = None, path: str | None = None, size_limit: int | None = None) -> None:
26+
def __init__(self, *, cache: Cache | None = None, path: Path | str | None = None, size_limit: int | None = None) -> None:
2627
"""Initialize the in-memory cache.
2728
2829
Args:
2930
disk_cache: The disk cache to use.
3031
size_limit: The maximum size of the disk cache. Defaults to 1GB.
3132
"""
33+
if isinstance(path, Path):
34+
path = str(object=path)
35+
3236
self._cache = cache or Cache(directory=path, size_limit=size_limit or DEFAULT_DISK_STORE_SIZE_LIMIT)
3337

3438
super().__init__()

tests/adapters/test_pydantic.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ async def store(self) -> MemoryStore:
4242

4343
@pytest.fixture
4444
async def user_adapter(self, store: MemoryStore) -> PydanticAdapter[User]:
45-
return PydanticAdapter[User](store=store, pydantic_model=User)
45+
return PydanticAdapter[User](store_protocol=store, pydantic_model=User)
4646

4747
@pytest.fixture
4848
async def product_adapter(self, store: MemoryStore) -> PydanticAdapter[Product]:
49-
return PydanticAdapter[Product](store=store, pydantic_model=Product)
49+
return PydanticAdapter[Product](store_protocol=store, pydantic_model=Product)
5050

5151
@pytest.fixture
5252
async def order_adapter(self, store: MemoryStore) -> PydanticAdapter[Order]:
53-
return PydanticAdapter[Order](store=store, pydantic_model=Order)
53+
return PydanticAdapter[Order](store_protocol=store, pydantic_model=Order)
5454

5555
async def test_simple_adapter(self, user_adapter: PydanticAdapter[User]):
5656
await user_adapter.put(collection="test", key="test", value=SAMPLE_USER)

tests/adapters/test_single_collection.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,3 @@ async def test_put_exists_delete_exists(self, adapter: SingleCollectionAdapter):
2626
assert await adapter.exists(key="test")
2727
assert await adapter.delete(key="test")
2828
assert await adapter.exists(key="test") is False
29-
30-
async def test_put_keys(self, adapter: SingleCollectionAdapter):
31-
await adapter.put(key="test", value={"test": "test"})
32-
assert await adapter.keys() == ["test"]
33-
34-
async def test_put_keys_delete_keys_get(self, adapter: SingleCollectionAdapter):
35-
await adapter.put(key="test", value={"test": "test"})
36-
assert await adapter.keys() == ["test"]
37-
assert await adapter.delete(key="test")
38-
assert await adapter.keys() == []
39-
assert await adapter.get(key="test") is None

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)