py-key-value-aio 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_key_value_aio-0.2.0/PKG-INFO +35 -0
- py_key_value_aio-0.2.0/README.md +1 -0
- py_key_value_aio-0.2.0/pyproject.toml +82 -0
- py_key_value_aio-0.2.0/src/key_value/aio/__init__.py +0 -0
- py_key_value_aio-0.2.0/src/key_value/aio/adapters/__init__.py +1 -0
- py_key_value_aio-0.2.0/src/key_value/aio/adapters/pydantic/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/adapters/pydantic/adapter.py +114 -0
- py_key_value_aio-0.2.0/src/key_value/aio/adapters/raise_on_missing/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/adapters/raise_on_missing/adapter.py +191 -0
- py_key_value_aio-0.2.0/src/key_value/aio/protocols/__init__.py +1 -0
- py_key_value_aio-0.2.0/src/key_value/aio/protocols/key_value.py +180 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/base.py +368 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/disk/__init__.py +4 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/disk/multi_store.py +151 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/disk/store.py +119 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/elasticsearch/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/elasticsearch/store.py +328 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/elasticsearch/utils.py +107 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/memcached/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/memcached/store.py +108 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/memory/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/memory/store.py +171 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/mongodb/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/mongodb/store.py +207 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/null/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/null/store.py +26 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/redis/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/redis/store.py +138 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/simple/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/simple/store.py +104 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/valkey/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/stores/valkey/store.py +127 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/__init__.py +0 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/base.py +51 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/passthrough_cache/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/passthrough_cache/wrapper.py +184 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/prefix_collections/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/prefix_collections/wrapper.py +79 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/prefix_keys/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/prefix_keys/wrapper.py +76 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/single_collection/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/single_collection/wrapper.py +83 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/statistics/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/statistics/wrapper.py +212 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/ttl_clamp/__init__.py +3 -0
- py_key_value_aio-0.2.0/src/key_value/aio/wrappers/ttl_clamp/wrapper.py +63 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: py-key-value-aio
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Async Key-Value
|
|
5
|
+
Classifier: Development Status :: 3 - Alpha
|
|
6
|
+
Classifier: Intended Audience :: Developers
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Dist: py-key-value-shared>=0.2.0
|
|
14
|
+
Requires-Dist: diskcache>=5.6.0 ; extra == 'disk'
|
|
15
|
+
Requires-Dist: pathvalidate>=3.3.1 ; extra == 'disk'
|
|
16
|
+
Requires-Dist: elasticsearch>=9.0.0 ; extra == 'elasticsearch'
|
|
17
|
+
Requires-Dist: aiohttp>=3.12 ; extra == 'elasticsearch'
|
|
18
|
+
Requires-Dist: aiomcache>=0.8.0 ; extra == 'memcached'
|
|
19
|
+
Requires-Dist: cachetools>=6.0.0 ; extra == 'memory'
|
|
20
|
+
Requires-Dist: pymongo>=4.15.0 ; extra == 'mongodb'
|
|
21
|
+
Requires-Dist: pydantic>=2.11.9 ; extra == 'pydantic'
|
|
22
|
+
Requires-Dist: redis>=6.0.0 ; extra == 'redis'
|
|
23
|
+
Requires-Dist: valkey-glide>=2.1.0 ; extra == 'valkey'
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Provides-Extra: disk
|
|
26
|
+
Provides-Extra: elasticsearch
|
|
27
|
+
Provides-Extra: memcached
|
|
28
|
+
Provides-Extra: memory
|
|
29
|
+
Provides-Extra: mongodb
|
|
30
|
+
Provides-Extra: pydantic
|
|
31
|
+
Provides-Extra: redis
|
|
32
|
+
Provides-Extra: valkey
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
See the root [README.md](../README.md) for more information.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
See the root [README.md](../README.md) for more information.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "py-key-value-aio"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Async Key-Value"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
classifiers = [
|
|
8
|
+
"Development Status :: 3 - Alpha",
|
|
9
|
+
"Intended Audience :: Developers",
|
|
10
|
+
"License :: OSI Approved :: MIT License",
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"Programming Language :: Python :: 3.10",
|
|
13
|
+
"Programming Language :: Python :: 3.11",
|
|
14
|
+
"Programming Language :: Python :: 3.12",
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"py-key-value-shared>=0.2.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["uv_build>=0.8.2,<0.9.0"]
|
|
24
|
+
build-backend = "uv_build"
|
|
25
|
+
|
|
26
|
+
[tool.uv.build-backend]
|
|
27
|
+
module-name = "key_value.aio"
|
|
28
|
+
|
|
29
|
+
[tool.uv.sources]
|
|
30
|
+
py-key-value-shared = { workspace = true }
|
|
31
|
+
py-key-value-shared-test = { workspace = true }
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
memory = ["cachetools>=6.0.0"]
|
|
35
|
+
disk = ["diskcache>=5.6.0", "pathvalidate>=3.3.1",]
|
|
36
|
+
redis = ["redis>=6.0.0"]
|
|
37
|
+
mongodb = ["pymongo>=4.15.0"]
|
|
38
|
+
valkey = ["valkey-glide>=2.1.0"]
|
|
39
|
+
memcached = ["aiomcache>=0.8.0"]
|
|
40
|
+
elasticsearch = ["elasticsearch>=9.0.0", "aiohttp>=3.12"]
|
|
41
|
+
pydantic = ["pydantic>=2.11.9"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
asyncio_mode = "auto"
|
|
45
|
+
addopts = ["--inline-snapshot=disable","-vv","-s"]
|
|
46
|
+
markers = [
|
|
47
|
+
"skip_on_ci: Skip running the test when running on CI",
|
|
48
|
+
]
|
|
49
|
+
timeout = 10
|
|
50
|
+
|
|
51
|
+
env_files = [".env"]
|
|
52
|
+
|
|
53
|
+
[dependency-groups]
|
|
54
|
+
dev = [
|
|
55
|
+
"py-key-value-aio[memory,disk,redis,elasticsearch,memcached,mongodb]",
|
|
56
|
+
"py-key-value-aio[valkey]; platform_system != 'Windows'",
|
|
57
|
+
"py-key-value-aio[pydantic]",
|
|
58
|
+
"pytest",
|
|
59
|
+
"pytest-mock",
|
|
60
|
+
"pytest-asyncio",
|
|
61
|
+
"ruff",
|
|
62
|
+
"diskcache-stubs>=5.6.3.6.20240818",
|
|
63
|
+
"pytest-dotenv>=0.5.2",
|
|
64
|
+
"dirty-equals>=0.10.0",
|
|
65
|
+
"inline-snapshot>=0.29.0",
|
|
66
|
+
"pytest-redis>=3.1.3",
|
|
67
|
+
"basedpyright>=1.31.5",
|
|
68
|
+
"pytest-timeout>=2.4.0",
|
|
69
|
+
"ast-comments>=1.2.3",
|
|
70
|
+
"docker>=7.1.0",
|
|
71
|
+
"py-key-value-shared-test>=0.2.0",
|
|
72
|
+
|
|
73
|
+
]
|
|
74
|
+
lint = [
|
|
75
|
+
"ruff"
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[tool.ruff]
|
|
79
|
+
extend="../../pyproject.toml"
|
|
80
|
+
|
|
81
|
+
[tool.pyright]
|
|
82
|
+
extends = "../../pyproject.toml"
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from typing import Any, Generic, TypeVar
|
|
3
|
+
|
|
4
|
+
from key_value.shared.errors import DeserializationError, SerializationError
|
|
5
|
+
from pydantic import BaseModel, ValidationError
|
|
6
|
+
from pydantic_core import PydanticSerializationError
|
|
7
|
+
|
|
8
|
+
from key_value.aio.protocols.key_value import AsyncKeyValue
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T", bound=BaseModel)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PydanticAdapter(Generic[T]):
|
|
14
|
+
"""Adapter around a KVStore-compliant Store that allows type-safe persistence of Pydantic models."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, key_value: AsyncKeyValue, pydantic_model: type[T], default_collection: str | None = None) -> None:
|
|
17
|
+
self.key_value: AsyncKeyValue = key_value
|
|
18
|
+
self.pydantic_model: type[T] = pydantic_model
|
|
19
|
+
self.default_collection: str | None = default_collection
|
|
20
|
+
|
|
21
|
+
def _validate_model(self, value: dict[str, Any]) -> T:
|
|
22
|
+
try:
|
|
23
|
+
return self.pydantic_model.model_validate(obj=value)
|
|
24
|
+
except ValidationError as e:
|
|
25
|
+
msg = f"Invalid Pydantic model: {e}"
|
|
26
|
+
raise DeserializationError(msg) from e
|
|
27
|
+
|
|
28
|
+
def _serialize_model(self, value: T) -> dict[str, Any]:
|
|
29
|
+
try:
|
|
30
|
+
return value.model_dump(mode="json")
|
|
31
|
+
except PydanticSerializationError as e:
|
|
32
|
+
msg = f"Invalid Pydantic model: {e}"
|
|
33
|
+
raise SerializationError(msg) from e
|
|
34
|
+
|
|
35
|
+
async def get(self, key: str, *, collection: str | None = None) -> T | None:
|
|
36
|
+
"""Get and validate a model by key.
|
|
37
|
+
|
|
38
|
+
Returns the parsed model instance, or None if not present.
|
|
39
|
+
Raises DeserializationError if the stored data cannot be validated as the model.
|
|
40
|
+
"""
|
|
41
|
+
collection = collection or self.default_collection
|
|
42
|
+
|
|
43
|
+
if value := await self.key_value.get(key=key, collection=collection):
|
|
44
|
+
return self._validate_model(value=value)
|
|
45
|
+
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
async def get_many(self, keys: list[str], *, collection: str | None = None) -> list[T | None]:
|
|
49
|
+
"""Batch get and validate models by keys, preserving order.
|
|
50
|
+
|
|
51
|
+
Each element is either a parsed model instance or None if missing.
|
|
52
|
+
"""
|
|
53
|
+
collection = collection or self.default_collection
|
|
54
|
+
|
|
55
|
+
values: list[dict[str, Any] | None] = await self.key_value.get_many(keys=keys, collection=collection)
|
|
56
|
+
|
|
57
|
+
return [self._validate_model(value=value) if value else None for value in values]
|
|
58
|
+
|
|
59
|
+
async def put(self, key: str, value: T, *, collection: str | None = None, ttl: float | None = None) -> None:
|
|
60
|
+
"""Serialize and store a model.
|
|
61
|
+
|
|
62
|
+
Propagates SerializationError if the model cannot be serialized.
|
|
63
|
+
"""
|
|
64
|
+
collection = collection or self.default_collection
|
|
65
|
+
|
|
66
|
+
value_dict: dict[str, Any] = self._serialize_model(value=value)
|
|
67
|
+
|
|
68
|
+
await self.key_value.put(key=key, value=value_dict, collection=collection, ttl=ttl)
|
|
69
|
+
|
|
70
|
+
async def put_many(self, keys: list[str], values: Sequence[T], *, collection: str | None = None, ttl: float | None = None) -> None:
|
|
71
|
+
"""Serialize and store multiple models, preserving order alignment with keys."""
|
|
72
|
+
collection = collection or self.default_collection
|
|
73
|
+
|
|
74
|
+
value_dicts: list[dict[str, Any]] = [self._serialize_model(value=value) for value in values]
|
|
75
|
+
|
|
76
|
+
await self.key_value.put_many(keys=keys, values=value_dicts, collection=collection, ttl=ttl)
|
|
77
|
+
|
|
78
|
+
async def delete(self, key: str, *, collection: str | None = None) -> bool:
|
|
79
|
+
"""Delete a model by key. Returns True if a value was deleted, else False."""
|
|
80
|
+
collection = collection or self.default_collection
|
|
81
|
+
|
|
82
|
+
return await self.key_value.delete(key=key, collection=collection)
|
|
83
|
+
|
|
84
|
+
async def delete_many(self, keys: list[str], *, collection: str | None = None) -> int:
|
|
85
|
+
"""Delete multiple models by key. Returns the count of deleted entries."""
|
|
86
|
+
collection = collection or self.default_collection
|
|
87
|
+
|
|
88
|
+
return await self.key_value.delete_many(keys=keys, collection=collection)
|
|
89
|
+
|
|
90
|
+
async def ttl(self, key: str, *, collection: str | None = None) -> tuple[T | None, float | None]:
|
|
91
|
+
"""Get a model and its TTL seconds if present.
|
|
92
|
+
|
|
93
|
+
Returns (model, ttl_seconds) or (None, None) if missing.
|
|
94
|
+
"""
|
|
95
|
+
collection = collection or self.default_collection
|
|
96
|
+
|
|
97
|
+
entry: dict[str, Any] | None
|
|
98
|
+
ttl_info: float | None
|
|
99
|
+
|
|
100
|
+
entry, ttl_info = await self.key_value.ttl(key=key, collection=collection)
|
|
101
|
+
|
|
102
|
+
if entry is not None:
|
|
103
|
+
model_validate: T = self._validate_model(value=entry)
|
|
104
|
+
return (model_validate, ttl_info)
|
|
105
|
+
|
|
106
|
+
return (None, None)
|
|
107
|
+
|
|
108
|
+
async def ttl_many(self, keys: list[str], *, collection: str | None = None) -> list[tuple[T | None, float | None]]:
|
|
109
|
+
"""Batch get models with TTLs. Each element is (model|None, ttl_seconds|None)."""
|
|
110
|
+
collection = collection or self.default_collection
|
|
111
|
+
|
|
112
|
+
entries: list[tuple[dict[str, Any] | None, float | None]] = await self.key_value.ttl_many(keys=keys, collection=collection)
|
|
113
|
+
|
|
114
|
+
return [(self._validate_model(value=entry) if entry else None, ttl_info) for entry, ttl_info in entries]
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from typing import Any, Literal, overload
|
|
3
|
+
|
|
4
|
+
from key_value.shared.errors import MissingKeyError
|
|
5
|
+
|
|
6
|
+
from key_value.aio.protocols.key_value import AsyncKeyValue
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RaiseOnMissingAdapter:
|
|
10
|
+
"""Adapter around a KVStore that raises on missing values for get/get_many/ttl/ttl_many.
|
|
11
|
+
|
|
12
|
+
When `raise_on_missing=True`, methods raise `MissingKeyError` instead of returning None.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, key_value: AsyncKeyValue) -> None:
|
|
16
|
+
self.key_value: AsyncKeyValue = key_value
|
|
17
|
+
|
|
18
|
+
@overload
|
|
19
|
+
async def get(self, key: str, *, collection: str | None = None, raise_on_missing: Literal[False] = False) -> dict[str, Any] | None: ...
|
|
20
|
+
|
|
21
|
+
@overload
|
|
22
|
+
async def get(self, key: str, *, collection: str | None = None, raise_on_missing: Literal[True]) -> dict[str, Any]: ...
|
|
23
|
+
|
|
24
|
+
async def get(
|
|
25
|
+
self,
|
|
26
|
+
key: str,
|
|
27
|
+
*,
|
|
28
|
+
collection: str | None = None,
|
|
29
|
+
raise_on_missing: bool = False,
|
|
30
|
+
) -> dict[str, Any] | None:
|
|
31
|
+
"""Retrieve a value by key from the specified collection.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
key: The key to retrieve the value from.
|
|
35
|
+
collection: The collection to retrieve the value from. If no collection is provided, it will use the default collection.
|
|
36
|
+
raise_on_missing: Whether to raise a MissingKeyError if the key is not found.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
The value associated with the key. If the key is not found, None will be returned.
|
|
40
|
+
"""
|
|
41
|
+
result = await self.key_value.get(key=key, collection=collection)
|
|
42
|
+
|
|
43
|
+
if result is not None:
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
if raise_on_missing:
|
|
47
|
+
raise MissingKeyError(operation="get", collection=collection, key=key)
|
|
48
|
+
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
@overload
|
|
52
|
+
async def get_many(
|
|
53
|
+
self, keys: list[str], *, collection: str | None = None, raise_on_missing: Literal[False] = False
|
|
54
|
+
) -> list[dict[str, Any] | None]: ...
|
|
55
|
+
|
|
56
|
+
@overload
|
|
57
|
+
async def get_many(
|
|
58
|
+
self, keys: list[str], *, collection: str | None = None, raise_on_missing: Literal[True]
|
|
59
|
+
) -> list[dict[str, Any]]: ...
|
|
60
|
+
|
|
61
|
+
async def get_many(
|
|
62
|
+
self, keys: list[str], *, collection: str | None = None, raise_on_missing: bool = False
|
|
63
|
+
) -> list[dict[str, Any]] | list[dict[str, Any] | None]:
|
|
64
|
+
"""Retrieve multiple values by key from the specified collection.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
keys: The keys to retrieve the values from.
|
|
68
|
+
collection: The collection to retrieve keys from. If no collection is provided, it will use the default collection.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The values for the keys, or [] if the key is not found.
|
|
72
|
+
"""
|
|
73
|
+
results: list[dict[str, Any] | None] = await self.key_value.get_many(collection=collection, keys=keys)
|
|
74
|
+
|
|
75
|
+
for i, key in enumerate(keys):
|
|
76
|
+
if results[i] is None and raise_on_missing:
|
|
77
|
+
raise MissingKeyError(operation="get_many", collection=collection, key=key)
|
|
78
|
+
|
|
79
|
+
return results
|
|
80
|
+
|
|
81
|
+
@overload
|
|
82
|
+
async def ttl(
|
|
83
|
+
self, key: str, *, collection: str | None = None, raise_on_missing: Literal[False] = False
|
|
84
|
+
) -> tuple[dict[str, Any] | None, float | None]: ...
|
|
85
|
+
|
|
86
|
+
@overload
|
|
87
|
+
async def ttl(
|
|
88
|
+
self, key: str, *, collection: str | None = None, raise_on_missing: Literal[True]
|
|
89
|
+
) -> tuple[dict[str, Any], float | None]: ...
|
|
90
|
+
|
|
91
|
+
async def ttl(
|
|
92
|
+
self, key: str, *, collection: str | None = None, raise_on_missing: bool = False
|
|
93
|
+
) -> tuple[dict[str, Any] | None, float | None]:
|
|
94
|
+
"""Retrieve the value and TTL information for a key-value pair from the specified collection.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
key: The key to retrieve the TTL information from.
|
|
98
|
+
collection: The collection to retrieve the TTL information from. If no collection is provided,
|
|
99
|
+
it will use the default collection.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
The value and TTL information for the key. If the key is not found, (None, None) will be returned.
|
|
103
|
+
"""
|
|
104
|
+
value, ttl = await self.key_value.ttl(key=key, collection=collection)
|
|
105
|
+
|
|
106
|
+
if value is not None:
|
|
107
|
+
return value, ttl
|
|
108
|
+
|
|
109
|
+
if raise_on_missing:
|
|
110
|
+
raise MissingKeyError(operation="ttl", collection=collection, key=key)
|
|
111
|
+
|
|
112
|
+
return (None, None)
|
|
113
|
+
|
|
114
|
+
@overload
|
|
115
|
+
async def ttl_many(
|
|
116
|
+
self, keys: list[str], *, collection: str | None = None, raise_on_missing: Literal[False] = False
|
|
117
|
+
) -> list[tuple[dict[str, Any] | None, float | None]]: ...
|
|
118
|
+
|
|
119
|
+
@overload
|
|
120
|
+
async def ttl_many(
|
|
121
|
+
self, keys: list[str], *, collection: str | None = None, raise_on_missing: Literal[True]
|
|
122
|
+
) -> list[tuple[dict[str, Any], float | None]]: ...
|
|
123
|
+
|
|
124
|
+
async def ttl_many(
|
|
125
|
+
self, keys: list[str], *, collection: str | None = None, raise_on_missing: bool = False
|
|
126
|
+
) -> list[tuple[dict[str, Any], float | None]] | list[tuple[dict[str, Any] | None, float | None]]:
|
|
127
|
+
"""Retrieve multiple values and TTL information by key from the specified collection.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
keys: The keys to retrieve the values and TTL information from.
|
|
131
|
+
collection: The collection to retrieve keys from. If no collection is provided, it will use the default collection.
|
|
132
|
+
"""
|
|
133
|
+
results: list[tuple[dict[str, Any] | None, float | None]] = await self.key_value.ttl_many(collection=collection, keys=keys)
|
|
134
|
+
|
|
135
|
+
for i, key in enumerate(keys):
|
|
136
|
+
if results[i][0] is None and raise_on_missing:
|
|
137
|
+
raise MissingKeyError(operation="ttl_many", collection=collection, key=key)
|
|
138
|
+
|
|
139
|
+
return results
|
|
140
|
+
|
|
141
|
+
async def put(self, key: str, value: dict[str, Any], *, collection: str | None = None, ttl: float | None = None) -> None:
|
|
142
|
+
"""Store a key-value pair in the specified collection with optional TTL.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
key: The key to store the value in.
|
|
146
|
+
value: The value to store.
|
|
147
|
+
collection: The collection to store the value in. If no collection is provided, it will use the default collection.
|
|
148
|
+
ttl: The optional time-to-live (expiry duration) for the key-value pair. Defaults to no TTL. Note: The
|
|
149
|
+
backend store will convert the provided format to its own internal format.
|
|
150
|
+
"""
|
|
151
|
+
return await self.key_value.put(key=key, value=value, collection=collection, ttl=ttl)
|
|
152
|
+
|
|
153
|
+
async def put_many(
|
|
154
|
+
self,
|
|
155
|
+
keys: list[str],
|
|
156
|
+
values: Sequence[dict[str, Any]],
|
|
157
|
+
*,
|
|
158
|
+
collection: str | None = None,
|
|
159
|
+
ttl: Sequence[float | None] | float | None = None,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Store multiple key-value pairs in the specified collection.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
keys: The keys to store the values in.
|
|
165
|
+
values: The values to store.
|
|
166
|
+
collection: The collection to store keys in. If no collection is provided, it will use the default collection.
|
|
167
|
+
ttl: The optional time-to-live (expiry duration) for the key-value pairs. Defaults to no TTL. Note: The
|
|
168
|
+
backend store will convert the provided format to its own internal format.
|
|
169
|
+
"""
|
|
170
|
+
return await self.key_value.put_many(keys=keys, values=values, collection=collection, ttl=ttl)
|
|
171
|
+
|
|
172
|
+
async def delete(self, key: str, *, collection: str | None = None) -> bool:
|
|
173
|
+
"""Delete a key-value pair from the specified collection.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
key: The key to delete the value from.
|
|
177
|
+
collection: The collection to delete the value from. If no collection is provided, it will use the default collection.
|
|
178
|
+
"""
|
|
179
|
+
return await self.key_value.delete(key=key, collection=collection)
|
|
180
|
+
|
|
181
|
+
async def delete_many(self, keys: list[str], *, collection: str | None = None) -> int:
|
|
182
|
+
"""Delete multiple key-value pairs from the specified collection.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
keys: The keys to delete the values from.
|
|
186
|
+
collection: The collection to delete keys from. If no collection is provided, it will use the default collection.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
The number of keys deleted.
|
|
190
|
+
"""
|
|
191
|
+
return await self.key_value.delete_many(keys=keys, collection=collection)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from key_value.aio.protocols.key_value import AsyncKeyValue as AsyncKeyValue
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from typing import Any, Protocol, runtime_checkable
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@runtime_checkable
|
|
6
|
+
class AsyncKeyValueProtocol(Protocol):
|
|
7
|
+
"""A subset of KV operations: get/put/delete and TTL variants, including bulk calls."""
|
|
8
|
+
|
|
9
|
+
async def get(
|
|
10
|
+
self,
|
|
11
|
+
key: str,
|
|
12
|
+
*,
|
|
13
|
+
collection: str | None = None,
|
|
14
|
+
) -> dict[str, Any] | None:
|
|
15
|
+
"""Retrieve a value by key from the specified collection.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
key: The key to retrieve the value from.
|
|
19
|
+
collection: The collection to retrieve the value from. If no collection is provided, it will use the default collection.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
The value associated with the key. If the key is not found, None will be returned.
|
|
23
|
+
"""
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
async def ttl(self, key: str, *, collection: str | None = None) -> tuple[dict[str, Any] | None, float | None]:
|
|
27
|
+
"""Retrieve the value and TTL information for a key-value pair from the specified collection.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
key: The key to retrieve the TTL information from.
|
|
31
|
+
collection: The collection to retrieve the TTL information from. If no collection is provided,
|
|
32
|
+
it will use the default collection.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
The value and TTL information for the key. If the key is not found, (None, None) will be returned.
|
|
36
|
+
"""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
async def put(self, key: str, value: dict[str, Any], *, collection: str | None = None, ttl: float | None = None) -> None:
|
|
40
|
+
"""Store a key-value pair in the specified collection with optional TTL.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
key: The key to store the value in.
|
|
44
|
+
value: The value to store.
|
|
45
|
+
collection: The collection to store the value in. If no collection is provided, it will use the default collection.
|
|
46
|
+
ttl: The optional time-to-live (expiry duration) for the key-value pair. Defaults to no TTL. Note: The
|
|
47
|
+
backend store will convert the provided format to its own internal format.
|
|
48
|
+
"""
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
async def delete(self, key: str, *, collection: str | None = None) -> bool:
|
|
52
|
+
"""Delete a key-value pair from the specified collection.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
key: The key to delete the value from.
|
|
56
|
+
collection: The collection to delete the value from. If no collection is provided, it will use the default collection.
|
|
57
|
+
"""
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
async def get_many(self, keys: list[str], *, collection: str | None = None) -> list[dict[str, Any] | None]:
|
|
61
|
+
"""Retrieve multiple values by key from the specified collection.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
keys: The keys to retrieve the values from.
|
|
65
|
+
collection: The collection to retrieve keys from. If no collection is provided, it will use the default collection.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
A list of values for the keys. Each value is either a dict or None if the key is not found.
|
|
69
|
+
"""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
async def ttl_many(self, keys: list[str], *, collection: str | None = None) -> list[tuple[dict[str, Any] | None, float | None]]:
|
|
73
|
+
"""Retrieve multiple values and TTL information by key from the specified collection.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
keys: The keys to retrieve the values and TTL information from.
|
|
77
|
+
collection: The collection to retrieve keys from. If no collection is provided, it will use the default collection.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
A list of tuples containing (value, ttl) for each key. Each tuple contains either (dict, float) or (None, None) if the
|
|
81
|
+
key is not found.
|
|
82
|
+
"""
|
|
83
|
+
...
|
|
84
|
+
|
|
85
|
+
async def put_many(
|
|
86
|
+
self,
|
|
87
|
+
keys: list[str],
|
|
88
|
+
values: Sequence[dict[str, Any]],
|
|
89
|
+
*,
|
|
90
|
+
collection: str | None = None,
|
|
91
|
+
ttl: Sequence[float | None] | float | None = None,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Store multiple key-value pairs in the specified collection.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
keys: The keys to store the values in.
|
|
97
|
+
values: The values to store.
|
|
98
|
+
collection: The collection to store keys in. If no collection is provided, it will use the default collection.
|
|
99
|
+
ttl: The optional time-to-live (expiry duration) for the key-value pairs. Defaults to no TTL. Note: The
|
|
100
|
+
backend store will convert the provided format to its own internal format.
|
|
101
|
+
"""
|
|
102
|
+
...
|
|
103
|
+
|
|
104
|
+
async def delete_many(self, keys: list[str], *, collection: str | None = None) -> int:
|
|
105
|
+
"""Delete multiple key-value pairs from the specified collection.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
keys: The keys to delete the values from.
|
|
109
|
+
collection: The collection to delete keys from. If no collection is provided, it will use the default collection.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
The number of keys deleted.
|
|
113
|
+
"""
|
|
114
|
+
...
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@runtime_checkable
|
|
118
|
+
class AsyncCullProtocol(Protocol):
|
|
119
|
+
async def cull(self) -> None:
|
|
120
|
+
"""Cull the store.
|
|
121
|
+
|
|
122
|
+
This will remove all expired keys from the store.
|
|
123
|
+
"""
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@runtime_checkable
|
|
128
|
+
class AsyncEnumerateKeysProtocol(Protocol):
|
|
129
|
+
"""Protocol segment to enumerate keys in a collection."""
|
|
130
|
+
|
|
131
|
+
async def keys(self, collection: str | None = None, *, limit: int | None = None) -> list[str]:
|
|
132
|
+
"""List all keys in the specified collection.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
collection: The collection to list the keys from. If no collection is provided, it will use the default collection.
|
|
136
|
+
limit: The maximum number of keys to list. The behavior when no limit is provided is store-dependent.
|
|
137
|
+
"""
|
|
138
|
+
...
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@runtime_checkable
|
|
142
|
+
class AsyncEnumerateCollectionsProtocol(Protocol):
|
|
143
|
+
async def collections(self, *, limit: int | None = None) -> list[str]:
|
|
144
|
+
"""List all available collection names (may include empty collections).
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
limit: The maximum number of collections to list. The behavior when no limit is provided is store-dependent.
|
|
148
|
+
"""
|
|
149
|
+
...
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@runtime_checkable
|
|
153
|
+
class AsyncDestroyStoreProtocol(Protocol):
|
|
154
|
+
"""Protocol segment for store-destruction semantics."""
|
|
155
|
+
|
|
156
|
+
async def destroy(self) -> bool:
|
|
157
|
+
"""Destroy the keystore.
|
|
158
|
+
|
|
159
|
+
This will clear all collections and keys from the store.
|
|
160
|
+
"""
|
|
161
|
+
...
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@runtime_checkable
|
|
165
|
+
class AsyncDestroyCollectionProtocol(Protocol):
|
|
166
|
+
async def destroy_collection(self, collection: str) -> bool:
|
|
167
|
+
"""Destroy the specified collection.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
collection: The collection to destroy.
|
|
171
|
+
"""
|
|
172
|
+
...
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class AsyncKeyValue(AsyncKeyValueProtocol, Protocol):
|
|
176
|
+
"""A protocol for key-value store operations.
|
|
177
|
+
|
|
178
|
+
Includes basic operations: get, put, delete, ttl
|
|
179
|
+
Includes bulk operations: get_many, put_many, delete_many, ttl_many.
|
|
180
|
+
"""
|