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.
Files changed (46) hide show
  1. py_key_value_aio-0.2.0/PKG-INFO +35 -0
  2. py_key_value_aio-0.2.0/README.md +1 -0
  3. py_key_value_aio-0.2.0/pyproject.toml +82 -0
  4. py_key_value_aio-0.2.0/src/key_value/aio/__init__.py +0 -0
  5. py_key_value_aio-0.2.0/src/key_value/aio/adapters/__init__.py +1 -0
  6. py_key_value_aio-0.2.0/src/key_value/aio/adapters/pydantic/__init__.py +3 -0
  7. py_key_value_aio-0.2.0/src/key_value/aio/adapters/pydantic/adapter.py +114 -0
  8. py_key_value_aio-0.2.0/src/key_value/aio/adapters/raise_on_missing/__init__.py +3 -0
  9. py_key_value_aio-0.2.0/src/key_value/aio/adapters/raise_on_missing/adapter.py +191 -0
  10. py_key_value_aio-0.2.0/src/key_value/aio/protocols/__init__.py +1 -0
  11. py_key_value_aio-0.2.0/src/key_value/aio/protocols/key_value.py +180 -0
  12. py_key_value_aio-0.2.0/src/key_value/aio/stores/base.py +368 -0
  13. py_key_value_aio-0.2.0/src/key_value/aio/stores/disk/__init__.py +4 -0
  14. py_key_value_aio-0.2.0/src/key_value/aio/stores/disk/multi_store.py +151 -0
  15. py_key_value_aio-0.2.0/src/key_value/aio/stores/disk/store.py +119 -0
  16. py_key_value_aio-0.2.0/src/key_value/aio/stores/elasticsearch/__init__.py +3 -0
  17. py_key_value_aio-0.2.0/src/key_value/aio/stores/elasticsearch/store.py +328 -0
  18. py_key_value_aio-0.2.0/src/key_value/aio/stores/elasticsearch/utils.py +107 -0
  19. py_key_value_aio-0.2.0/src/key_value/aio/stores/memcached/__init__.py +3 -0
  20. py_key_value_aio-0.2.0/src/key_value/aio/stores/memcached/store.py +108 -0
  21. py_key_value_aio-0.2.0/src/key_value/aio/stores/memory/__init__.py +3 -0
  22. py_key_value_aio-0.2.0/src/key_value/aio/stores/memory/store.py +171 -0
  23. py_key_value_aio-0.2.0/src/key_value/aio/stores/mongodb/__init__.py +3 -0
  24. py_key_value_aio-0.2.0/src/key_value/aio/stores/mongodb/store.py +207 -0
  25. py_key_value_aio-0.2.0/src/key_value/aio/stores/null/__init__.py +3 -0
  26. py_key_value_aio-0.2.0/src/key_value/aio/stores/null/store.py +26 -0
  27. py_key_value_aio-0.2.0/src/key_value/aio/stores/redis/__init__.py +3 -0
  28. py_key_value_aio-0.2.0/src/key_value/aio/stores/redis/store.py +138 -0
  29. py_key_value_aio-0.2.0/src/key_value/aio/stores/simple/__init__.py +3 -0
  30. py_key_value_aio-0.2.0/src/key_value/aio/stores/simple/store.py +104 -0
  31. py_key_value_aio-0.2.0/src/key_value/aio/stores/valkey/__init__.py +3 -0
  32. py_key_value_aio-0.2.0/src/key_value/aio/stores/valkey/store.py +127 -0
  33. py_key_value_aio-0.2.0/src/key_value/aio/wrappers/__init__.py +0 -0
  34. py_key_value_aio-0.2.0/src/key_value/aio/wrappers/base.py +51 -0
  35. py_key_value_aio-0.2.0/src/key_value/aio/wrappers/passthrough_cache/__init__.py +3 -0
  36. py_key_value_aio-0.2.0/src/key_value/aio/wrappers/passthrough_cache/wrapper.py +184 -0
  37. py_key_value_aio-0.2.0/src/key_value/aio/wrappers/prefix_collections/__init__.py +3 -0
  38. py_key_value_aio-0.2.0/src/key_value/aio/wrappers/prefix_collections/wrapper.py +79 -0
  39. py_key_value_aio-0.2.0/src/key_value/aio/wrappers/prefix_keys/__init__.py +3 -0
  40. py_key_value_aio-0.2.0/src/key_value/aio/wrappers/prefix_keys/wrapper.py +76 -0
  41. py_key_value_aio-0.2.0/src/key_value/aio/wrappers/single_collection/__init__.py +3 -0
  42. py_key_value_aio-0.2.0/src/key_value/aio/wrappers/single_collection/wrapper.py +83 -0
  43. py_key_value_aio-0.2.0/src/key_value/aio/wrappers/statistics/__init__.py +3 -0
  44. py_key_value_aio-0.2.0/src/key_value/aio/wrappers/statistics/wrapper.py +212 -0
  45. py_key_value_aio-0.2.0/src/key_value/aio/wrappers/ttl_clamp/__init__.py +3 -0
  46. 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,3 @@
1
+ from key_value.aio.adapters.pydantic.adapter import PydanticAdapter
2
+
3
+ __all__ = ["PydanticAdapter"]
@@ -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,3 @@
1
+ from key_value.aio.adapters.raise_on_missing.adapter import RaiseOnMissingAdapter
2
+
3
+ __all__ = ["RaiseOnMissingAdapter"]
@@ -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
+ """