redis-dict 2.6.0__py3-none-any.whl → 3.0.0__py3-none-any.whl
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.
- redis_dict/__init__.py +17 -0
- redis_dict.py → redis_dict/core.py +237 -299
- redis_dict/py.typed +0 -0
- redis_dict/type_management.py +273 -0
- {redis_dict-2.6.0.dist-info → redis_dict-3.0.0.dist-info}/METADATA +118 -34
- redis_dict-3.0.0.dist-info/RECORD +9 -0
- {redis_dict-2.6.0.dist-info → redis_dict-3.0.0.dist-info}/WHEEL +1 -1
- redis_dict-2.6.0.dist-info/RECORD +0 -6
- {redis_dict-2.6.0.dist-info → redis_dict-3.0.0.dist-info}/LICENSE +0 -0
- {redis_dict-2.6.0.dist-info → redis_dict-3.0.0.dist-info}/top_level.txt +0 -0
@@ -1,169 +1,23 @@
|
|
1
|
-
"""
|
2
|
-
|
3
|
-
|
4
|
-
RedisDict is a Python library that provides a convenient and familiar interface for
|
5
|
-
interacting with Redis as if it were a Python dictionary. The simple yet powerful library
|
6
|
-
enables you to manage key-value pairs in Redis using native Python syntax of dictionary. It supports
|
7
|
-
various data types, including strings, integers, floats, booleans, lists, and dictionaries,
|
8
|
-
and includes additional utility functions for more complex use cases.
|
9
|
-
|
10
|
-
By leveraging Redis for efficient key-value storage, RedisDict allows for high-performance
|
11
|
-
data management and is particularly useful for handling large datasets that may exceed local
|
12
|
-
memory capacity.
|
13
|
-
|
14
|
-
## Features
|
15
|
-
|
16
|
-
* **Dictionary-like interface**: Use familiar Python dictionary syntax to interact with Redis.
|
17
|
-
* **Data Type Support**: Comprehensive support for various data types, including strings,
|
18
|
-
integers, floats, booleans, lists, dictionaries, sets, and tuples.
|
19
|
-
* **Pipelining support**: Use pipelines for batch operations to improve performance.
|
20
|
-
* **Expiration Support**: Enables the setting of expiration times either globally or individually
|
21
|
-
per key, through the use of context managers.
|
22
|
-
* **Efficiency and Scalability**: RedisDict is designed for use with large datasets and is
|
23
|
-
optimized for efficiency. It retrieves only the data needed for a particular operation,
|
24
|
-
ensuring efficient memory usage and fast performance.
|
25
|
-
* **Namespace Management**: Provides simple and efficient namespace handling to help organize
|
26
|
-
and manage data in Redis, streamlining data access and manipulation.
|
27
|
-
* **Distributed Computing**: With its ability to seamlessly connect to other instances or
|
28
|
-
servers with access to the same Redis instance, RedisDict enables easy distributed computing.
|
29
|
-
* **Custom data types**: Add custom types and transformations to suit your specific needs.
|
30
|
-
|
31
|
-
New feature
|
32
|
-
|
33
|
-
Custom extendable Validity checks on keys, and values.to support redis-dict base exceptions with messages from
|
34
|
-
enabling detailed reporting on the reasons for specific validation failures. This refactor would allow users
|
35
|
-
to configure which validity checks to execute, integrate custom validation functions, and specify whether
|
36
|
-
to raise an error on validation failures or to drop the operation and log a warning.
|
37
|
-
|
38
|
-
For example, in a caching scenario, data should only be cached if it falls within defined minimum and
|
39
|
-
maximum size constraints. This approach enables straightforward dictionary set operations while ensuring
|
40
|
-
that only meaningful data is cached: values greater than 10 MB and less than 100 MB should be cached;
|
41
|
-
otherwise, they will be dropped.
|
42
|
-
|
43
|
-
>>> def my_custom_validity_check(value: str) -> None:
|
44
|
-
\"""
|
45
|
-
Validates the size of the input.
|
46
|
-
|
47
|
-
Args:
|
48
|
-
value (str): string to validate.
|
49
|
-
|
50
|
-
Raises:
|
51
|
-
RedisDictValidityException: If the length of the input is not within the allowed range.
|
52
|
-
\"""
|
53
|
-
min_size = 10 * 1024: # Minimum size: 10 KB
|
54
|
-
max_size = 10 * 1024 * 1024: # Maximum size: 10 MB
|
55
|
-
if len(value) < min_size
|
56
|
-
raise RedisDictValidityException(f"value is too small: {len(value)} bytes")
|
57
|
-
if len(value) > max_size
|
58
|
-
raise RedisDictValidityException(f"value is too large: {len(value)} bytes")
|
59
|
-
|
60
|
-
>>> cache = RedisDict(namespace='cache_valid_results_for_1_minute',
|
61
|
-
... expire=60,
|
62
|
-
... custom_valid_values_checks=[my_custom_validity_check], # extend with new valid check
|
63
|
-
... validity_exception_suppress=True) # when value is invalid, don't store, and don't raise an exception.
|
64
|
-
>>> too_small = "too small to cache"
|
65
|
-
>>> cache["1234"] = too_small # Since the value is below 10kb, thus there is no reason to cache the value.
|
66
|
-
>>> cache.get("1234") is None
|
67
|
-
>>> True
|
68
|
-
"""
|
69
|
-
import json
|
1
|
+
"""Redis Dict module."""
|
2
|
+
from typing import Any, Dict, Iterator, List, Tuple, Union, Optional, Type
|
70
3
|
|
71
4
|
from datetime import timedelta
|
72
|
-
from typing import Any, Callable, Dict, Iterator, Set, List, Tuple, Union, Optional
|
73
5
|
from contextlib import contextmanager
|
6
|
+
from collections.abc import Mapping
|
74
7
|
|
75
8
|
from redis import StrictRedis
|
76
9
|
|
77
|
-
SENTINEL
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
EncodeType = Dict[str, EncodeFuncType]
|
83
|
-
DecodeType = Dict[str, DecodeFuncType]
|
84
|
-
|
85
|
-
|
86
|
-
def _create_default_encode(custom_encode_method: str) -> EncodeFuncType:
|
87
|
-
def default_encode(obj: Any) -> str:
|
88
|
-
return getattr(obj, custom_encode_method)() # type: ignore[no-any-return]
|
89
|
-
return default_encode
|
90
|
-
|
91
|
-
|
92
|
-
def _create_default_decode(cls: type, custom_decode_method: str) -> DecodeFuncType:
|
93
|
-
def default_decode(encoded_str: str) -> Any:
|
94
|
-
return getattr(cls, custom_decode_method)(encoded_str)
|
95
|
-
return default_decode
|
96
|
-
|
97
|
-
|
98
|
-
def _decode_tuple(val: str) -> Tuple[Any, ...]:
|
99
|
-
"""
|
100
|
-
Deserialize a JSON-formatted string to a tuple.
|
101
|
-
|
102
|
-
This function takes a JSON-formatted string, deserializes it to a list, and
|
103
|
-
then converts the list to a tuple.
|
104
|
-
|
105
|
-
Args:
|
106
|
-
val (str): A JSON-formatted string representing a list.
|
107
|
-
|
108
|
-
Returns:
|
109
|
-
Tuple[Any, ...]: A tuple with the deserialized values from the input string.
|
110
|
-
"""
|
111
|
-
return tuple(json.loads(val))
|
112
|
-
|
113
|
-
|
114
|
-
def _encode_tuple(val: Tuple[Any, ...]) -> str:
|
115
|
-
"""
|
116
|
-
Serialize a tuple to a JSON-formatted string.
|
117
|
-
|
118
|
-
This function takes a tuple, converts it to a list, and then serializes
|
119
|
-
the list to a JSON-formatted string.
|
120
|
-
|
121
|
-
Args:
|
122
|
-
val (Tuple[Any, ...]): A tuple with values to be serialized.
|
123
|
-
|
124
|
-
Returns:
|
125
|
-
str: A JSON-formatted string representing the input tuple.
|
126
|
-
"""
|
127
|
-
return json.dumps(list(val))
|
128
|
-
|
129
|
-
|
130
|
-
def _decode_set(val: str) -> Set[Any]:
|
131
|
-
"""
|
132
|
-
Deserialize a JSON-formatted string to a set.
|
133
|
-
|
134
|
-
This function takes a JSON-formatted string, deserializes it to a list, and
|
135
|
-
then converts the list to a set.
|
136
|
-
|
137
|
-
Args:
|
138
|
-
val (str): A JSON-formatted string representing a list.
|
139
|
-
|
140
|
-
Returns:
|
141
|
-
set[Any]: A set with the deserialized values from the input string.
|
142
|
-
"""
|
143
|
-
return set(json.loads(val))
|
144
|
-
|
145
|
-
|
146
|
-
def _encode_set(val: Set[Any]) -> str:
|
147
|
-
"""
|
148
|
-
Serialize a set to a JSON-formatted string.
|
149
|
-
|
150
|
-
This function takes a set, converts it to a list, and then serializes the
|
151
|
-
list to a JSON-formatted string.
|
152
|
-
|
153
|
-
Args:
|
154
|
-
val (set[Any]): A set with values to be serialized.
|
155
|
-
|
156
|
-
Returns:
|
157
|
-
str: A JSON-formatted string representing the input set.
|
158
|
-
"""
|
159
|
-
return json.dumps(list(val))
|
10
|
+
from redis_dict.type_management import SENTINEL, EncodeFuncType, DecodeFuncType, EncodeType, DecodeType
|
11
|
+
from redis_dict.type_management import _create_default_encode, _create_default_decode, _default_decoder
|
12
|
+
from redis_dict.type_management import encoding_registry as enc_reg
|
13
|
+
from redis_dict.type_management import decoding_registry as dec_reg
|
160
14
|
|
161
15
|
|
162
16
|
# pylint: disable=R0902, R0904
|
163
17
|
class RedisDict:
|
164
|
-
"""
|
165
|
-
|
166
|
-
custom data types, pipelining, and key expiration.
|
18
|
+
"""Python dictionary with Redis as backend.
|
19
|
+
|
20
|
+
With support for advanced features, such as custom data types, pipelining, and key expiration.
|
167
21
|
|
168
22
|
This class provides a dictionary-like interface that interacts with a Redis database, allowing
|
169
23
|
for efficient storage and retrieval of key-value pairs. It supports various data types, including
|
@@ -190,45 +44,34 @@ class RedisDict:
|
|
190
44
|
|
191
45
|
"""
|
192
46
|
|
193
|
-
|
194
|
-
|
195
|
-
type(1).__name__: int,
|
196
|
-
type(0.1).__name__: float,
|
197
|
-
type(True).__name__: lambda x: x == "True",
|
198
|
-
type(None).__name__: lambda x: None,
|
199
|
-
|
200
|
-
"list": json.loads,
|
201
|
-
"dict": json.loads,
|
202
|
-
"tuple": _decode_tuple,
|
203
|
-
type(set()).__name__: _decode_set,
|
204
|
-
}
|
205
|
-
|
206
|
-
encoding_registry: EncodeType = {
|
207
|
-
"list": json.dumps,
|
208
|
-
"dict": json.dumps,
|
209
|
-
"tuple": _encode_tuple,
|
210
|
-
type(set()).__name__: _encode_set,
|
211
|
-
}
|
47
|
+
encoding_registry: EncodeType = enc_reg
|
48
|
+
decoding_registry: DecodeType = dec_reg
|
212
49
|
|
213
50
|
def __init__(self,
|
214
51
|
namespace: str = 'main',
|
215
52
|
expire: Union[int, timedelta, None] = None,
|
216
53
|
preserve_expiration: Optional[bool] = False,
|
54
|
+
redis: "Optional[StrictRedis[Any]]" = None,
|
217
55
|
**redis_kwargs: Any) -> None:
|
218
|
-
"""
|
219
|
-
|
56
|
+
"""Initialize a RedisDict instance.
|
57
|
+
|
58
|
+
Init the RedisDict instance.
|
220
59
|
|
221
60
|
Args:
|
222
|
-
namespace (str
|
223
|
-
expire (int, timedelta, optional): Expiration time for keys
|
224
|
-
preserve_expiration (bool, optional): Preserve
|
225
|
-
|
61
|
+
namespace (str): A prefix for keys stored in Redis.
|
62
|
+
expire (Union[int, timedelta, None], optional): Expiration time for keys.
|
63
|
+
preserve_expiration (Optional[bool], optional): Preserve expiration on key updates.
|
64
|
+
redis (Optional[StrictRedis[Any]], optional): A Redis connection instance.
|
65
|
+
**redis_kwargs (Any): Additional kwargs for Redis connection if not provided.
|
226
66
|
"""
|
227
67
|
|
228
68
|
self.namespace: str = namespace
|
229
69
|
self.expire: Union[int, timedelta, None] = expire
|
230
70
|
self.preserve_expiration: Optional[bool] = preserve_expiration
|
231
|
-
|
71
|
+
if redis:
|
72
|
+
redis.connection_pool.connection_kwargs["decode_responses"] = True
|
73
|
+
|
74
|
+
self.redis: StrictRedis[Any] = redis or StrictRedis(decode_responses=True, **redis_kwargs)
|
232
75
|
self.get_redis: StrictRedis[Any] = self.redis
|
233
76
|
|
234
77
|
self.custom_encode_method = "encode"
|
@@ -259,7 +102,7 @@ class RedisDict:
|
|
259
102
|
length does not exceed the maximum allowed size (500 MB).
|
260
103
|
|
261
104
|
Args:
|
262
|
-
val (
|
105
|
+
val (Any): The input value to be validated.
|
263
106
|
val_type (str): The type of the input value ("str", "int", "float", or "bool").
|
264
107
|
|
265
108
|
Returns:
|
@@ -269,6 +112,26 @@ class RedisDict:
|
|
269
112
|
return len(val) < self._max_string_size
|
270
113
|
return True
|
271
114
|
|
115
|
+
def _format_value(self, key: str, value: Any) -> str:
|
116
|
+
"""Format a valid value with the type and encoded representation of the value.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
key (str): The key of the value to be formatted.
|
120
|
+
value (Any): The value to be encoded and formatted.
|
121
|
+
|
122
|
+
Raises:
|
123
|
+
ValueError: If the value or key fail validation.
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
str: The formatted value with the type and encoded representation of the value.
|
127
|
+
"""
|
128
|
+
store_type, key = type(value).__name__, str(key)
|
129
|
+
if not self._valid_input(value, store_type) or not self._valid_input(key, "str"):
|
130
|
+
raise ValueError("Invalid input value or key size exceeded the maximum limit.")
|
131
|
+
encoded_value = self.encoding_registry.get(store_type, lambda x: x)(value) # type: ignore
|
132
|
+
|
133
|
+
return f'{store_type}:{encoded_value}'
|
134
|
+
|
272
135
|
def _store(self, key: str, value: Any) -> None:
|
273
136
|
"""
|
274
137
|
Store a value in Redis with the given key.
|
@@ -285,18 +148,12 @@ class RedisDict:
|
|
285
148
|
Allowing for simple dict set operation, but only cache data that makes sense.
|
286
149
|
|
287
150
|
"""
|
288
|
-
store_type, key = type(value).__name__, str(key)
|
289
|
-
if not self._valid_input(value, store_type) or not self._valid_input(key, "str"):
|
290
|
-
raise ValueError("Invalid input value or key size exceeded the maximum limit.")
|
291
|
-
value = self.encoding_registry.get(store_type, lambda x: x)(value) # type: ignore
|
292
|
-
|
293
|
-
store_value = f'{store_type}:{value}'
|
294
151
|
formatted_key = self._format_key(key)
|
295
|
-
|
152
|
+
formatted_value = self._format_value(key, value)
|
296
153
|
if self.preserve_expiration and self.redis.exists(formatted_key):
|
297
|
-
self.redis.set(formatted_key,
|
154
|
+
self.redis.set(formatted_key, formatted_value, keepttl=True)
|
298
155
|
else:
|
299
|
-
self.redis.set(formatted_key,
|
156
|
+
self.redis.set(formatted_key, formatted_value, ex=self.expire)
|
300
157
|
|
301
158
|
def _load(self, key: str) -> Tuple[bool, Any]:
|
302
159
|
"""
|
@@ -311,8 +168,7 @@ class RedisDict:
|
|
311
168
|
result = self.get_redis.get(self._format_key(key))
|
312
169
|
if result is None:
|
313
170
|
return False, None
|
314
|
-
|
315
|
-
return True, self.decoding_registry.get(type_, lambda x: x)(value)
|
171
|
+
return True, self._transform(result)
|
316
172
|
|
317
173
|
def _transform(self, result: str) -> Any:
|
318
174
|
"""
|
@@ -325,7 +181,7 @@ class RedisDict:
|
|
325
181
|
Any: The transformed Python object.
|
326
182
|
"""
|
327
183
|
type_, value = result.split(':', 1)
|
328
|
-
return self.decoding_registry.get(type_,
|
184
|
+
return self.decoding_registry.get(type_, _default_decoder)(value)
|
329
185
|
|
330
186
|
def new_type_compliance(
|
331
187
|
self,
|
@@ -333,8 +189,7 @@ class RedisDict:
|
|
333
189
|
encode_method_name: Optional[str] = None,
|
334
190
|
decode_method_name: Optional[str] = None,
|
335
191
|
) -> None:
|
336
|
-
"""
|
337
|
-
Checks if a class complies with the required encoding and decoding methods.
|
192
|
+
"""Check if a class complies with the required encoding and decoding methods.
|
338
193
|
|
339
194
|
Args:
|
340
195
|
class_type (type): The class to check for compliance.
|
@@ -356,6 +211,7 @@ class RedisDict:
|
|
356
211
|
raise NotImplementedError(
|
357
212
|
f"Class {class_type.__name__} does not implement the required {decode_method_name} class method.")
|
358
213
|
|
214
|
+
# pylint: disable=too-many-arguments
|
359
215
|
def extends_type(
|
360
216
|
self,
|
361
217
|
class_type: type,
|
@@ -364,31 +220,21 @@ class RedisDict:
|
|
364
220
|
encoding_method_name: Optional[str] = None,
|
365
221
|
decoding_method_name: Optional[str] = None,
|
366
222
|
) -> None:
|
367
|
-
"""
|
368
|
-
Extends RedisDict to support a custom type in the encode/decode mapping.
|
223
|
+
"""Extend RedisDict to support a custom type in the encode/decode mapping.
|
369
224
|
|
370
225
|
This method enables serialization of instances based on their type,
|
371
226
|
allowing for custom types, specialized storage formats, and more.
|
372
227
|
There are three ways to add custom types:
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
228
|
+
1. Have a class with an `encode` instance method and a `decode` class method.
|
229
|
+
2. Have a class and pass encoding and decoding functions, where
|
230
|
+
`encode` converts the class instance to a string, and
|
231
|
+
`decode` takes the string and recreates the class instance.
|
232
|
+
3. Have a class that already has serialization methods, that satisfies the:
|
233
|
+
EncodeFuncType = Callable[[Any], str]
|
234
|
+
DecodeFuncType = Callable[[str], Any]
|
380
235
|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
Args:
|
385
|
-
class_type (Type[type]): The class `__name__` will become the key for the encoding and decoding functions.
|
386
|
-
encode (Optional[EncodeFuncType]): function that encodes an object into a storable string format.
|
387
|
-
This function should take an instance of `class_type` as input and return a string.
|
388
|
-
decode (Optional[DecodeFuncType]): function that decodes a string back into an object of `class_type`.
|
389
|
-
This function should take a string as input and return an instance of `class_type`.
|
390
|
-
encoding_method_name (str, optional): Name of encoding method of the class for redis-dict custom types.
|
391
|
-
decoding_method_name (str, optional): Name of decoding method of the class for redis-dict custom types.
|
236
|
+
`custom_encode_method`
|
237
|
+
`custom_decode_method`
|
392
238
|
|
393
239
|
If no encoding or decoding function is provided, default to use the `encode` and `decode` methods of the class.
|
394
240
|
|
@@ -415,11 +261,21 @@ class RedisDict:
|
|
415
261
|
|
416
262
|
redis_dict.extends_type(Person)
|
417
263
|
|
264
|
+
Args:
|
265
|
+
class_type (type): The class `__name__` will become the key for the encoding and decoding functions.
|
266
|
+
encode (Optional[EncodeFuncType]): function that encodes an object into a storable string format.
|
267
|
+
decode (Optional[DecodeFuncType]): function that decodes a string back into an object of `class_type`.
|
268
|
+
encoding_method_name (str, optional): Name of encoding method of the class for redis-dict custom types.
|
269
|
+
decoding_method_name (str, optional): Name of decoding method of the class for redis-dict custom types.
|
270
|
+
|
271
|
+
Raises:
|
272
|
+
NotImplementedError
|
273
|
+
|
418
274
|
Note:
|
419
|
-
|
275
|
+
You can check for compliance of a class separately using the `new_type_compliance` method:
|
420
276
|
|
421
|
-
|
422
|
-
|
277
|
+
This method raises a NotImplementedError if either `encode` or `decode` is `None`
|
278
|
+
and the class does not implement the corresponding method.
|
423
279
|
"""
|
424
280
|
|
425
281
|
if encode is None or decode is None:
|
@@ -430,7 +286,7 @@ class RedisDict:
|
|
430
286
|
|
431
287
|
if decode is None:
|
432
288
|
decode_method_name = decoding_method_name or self.custom_decode_method
|
433
|
-
self.new_type_compliance(class_type,
|
289
|
+
self.new_type_compliance(class_type, decode_method_name=decode_method_name)
|
434
290
|
decode = _create_default_decode(class_type, decode_method_name)
|
435
291
|
|
436
292
|
type_name = class_type.__name__
|
@@ -449,7 +305,7 @@ class RedisDict:
|
|
449
305
|
"""
|
450
306
|
if len(self) != len(other):
|
451
307
|
return False
|
452
|
-
for key, value in self.
|
308
|
+
for key, value in self.items():
|
453
309
|
if value != other.get(key, SENTINEL):
|
454
310
|
return False
|
455
311
|
return True
|
@@ -531,7 +387,7 @@ class RedisDict:
|
|
531
387
|
Returns:
|
532
388
|
Iterator[str]: An iterator over the keys of the RedisDict.
|
533
389
|
"""
|
534
|
-
self._iter = self.
|
390
|
+
self._iter = self.keys()
|
535
391
|
return self
|
536
392
|
|
537
393
|
def __repr__(self) -> str:
|
@@ -552,15 +408,102 @@ class RedisDict:
|
|
552
408
|
"""
|
553
409
|
return str(self.to_dict())
|
554
410
|
|
411
|
+
def __or__(self, other: Dict[str, Any]) -> Dict[str, Any]:
|
412
|
+
"""
|
413
|
+
Implements the | operator (dict union).
|
414
|
+
Returns a new dictionary with items from both dictionaries.
|
415
|
+
|
416
|
+
Args:
|
417
|
+
other (Dict[str, Any]): The dictionary to merge with.
|
418
|
+
|
419
|
+
Raises:
|
420
|
+
TypeError: If other does not adhere to Mapping.
|
421
|
+
|
422
|
+
Returns:
|
423
|
+
Dict[str, Any]: A new dictionary containing items from both dictionaries.
|
424
|
+
"""
|
425
|
+
if not isinstance(other, Mapping):
|
426
|
+
raise TypeError(f"unsupported operand type(s) for |: '{type(other).__name__}' and 'RedisDict'")
|
427
|
+
|
428
|
+
result = {}
|
429
|
+
result.update(self.to_dict())
|
430
|
+
result.update(other)
|
431
|
+
return result
|
432
|
+
|
433
|
+
def __ror__(self, other: Dict[str, Any]) -> Dict[str, Any]:
|
434
|
+
"""
|
435
|
+
Implements the reverse | operator.
|
436
|
+
Called when RedisDict is on the right side of |.
|
437
|
+
|
438
|
+
Args:
|
439
|
+
other (Dict[str, Any]): The dictionary to merge with.
|
440
|
+
|
441
|
+
Raises:
|
442
|
+
TypeError: If other does not adhere to Mapping.
|
443
|
+
|
444
|
+
Returns:
|
445
|
+
Dict[str, Any]: A new dictionary containing items from both dictionaries.
|
446
|
+
"""
|
447
|
+
if not isinstance(other, Mapping):
|
448
|
+
raise TypeError(f"unsupported operand type(s) for |: 'RedisDict' and '{type(other).__name__}'")
|
449
|
+
|
450
|
+
result = {}
|
451
|
+
result.update(other)
|
452
|
+
result.update(self.to_dict())
|
453
|
+
return result
|
454
|
+
|
455
|
+
def __ior__(self, other: Dict[str, Any]) -> 'RedisDict':
|
456
|
+
"""
|
457
|
+
Implements the |= operator (in-place union).
|
458
|
+
Modifies the current dictionary by adding items from other.
|
459
|
+
|
460
|
+
Args:
|
461
|
+
other (Dict[str, Any]): The dictionary to merge with.
|
462
|
+
|
463
|
+
Raises:
|
464
|
+
TypeError: If other does not adhere to Mapping.
|
465
|
+
|
466
|
+
Returns:
|
467
|
+
RedisDict: The modified RedisDict instance.
|
468
|
+
"""
|
469
|
+
if not isinstance(other, Mapping):
|
470
|
+
raise TypeError(f"unsupported operand type(s) for |: '{type(other).__name__}' and 'RedisDict'")
|
471
|
+
|
472
|
+
self.update(other)
|
473
|
+
return self
|
474
|
+
|
475
|
+
@classmethod
|
476
|
+
def __class_getitem__(cls: Type['RedisDict'], key: Any) -> Type['RedisDict']:
|
477
|
+
"""
|
478
|
+
Enables type hinting support like RedisDict[str, Any].
|
479
|
+
|
480
|
+
Args:
|
481
|
+
key (Any): The type parameter(s) used in the type hint.
|
482
|
+
|
483
|
+
Returns:
|
484
|
+
Type[RedisDict]: The class itself, enabling type hint usage.
|
485
|
+
"""
|
486
|
+
return cls
|
487
|
+
|
488
|
+
def __reversed__(self) -> Iterator[str]:
|
489
|
+
"""
|
490
|
+
Implements reversed() built-in:
|
491
|
+
Returns an iterator over dictionary keys in reverse insertion order.
|
492
|
+
|
493
|
+
Warning:
|
494
|
+
RedisDict Currently does not support 'insertion order' as property thus also not reversed.
|
495
|
+
|
496
|
+
Returns:
|
497
|
+
Iterator[str]: An iterator yielding the dictionary keys in reverse order.
|
498
|
+
"""
|
499
|
+
return reversed(list(self.keys()))
|
500
|
+
|
555
501
|
def __next__(self) -> str:
|
556
502
|
"""
|
557
503
|
Get the next item in the iterator.
|
558
504
|
|
559
505
|
Returns:
|
560
506
|
str: The next item in the iterator.
|
561
|
-
|
562
|
-
Raises:
|
563
|
-
StopIteration: If there are no more items.
|
564
507
|
"""
|
565
508
|
return next(self._iter)
|
566
509
|
|
@@ -571,8 +514,6 @@ class RedisDict:
|
|
571
514
|
Returns:
|
572
515
|
str: The next item in the iterator.
|
573
516
|
|
574
|
-
Raises:
|
575
|
-
StopIteration: If there are no more items.
|
576
517
|
"""
|
577
518
|
return next(self)
|
578
519
|
|
@@ -603,7 +544,7 @@ class RedisDict:
|
|
603
544
|
Scan for Redis keys matching the given search term.
|
604
545
|
|
605
546
|
Args:
|
606
|
-
search_term (str
|
547
|
+
search_term (str): A search term to filter keys. Defaults to ''.
|
607
548
|
|
608
549
|
Returns:
|
609
550
|
Iterator[str]: An iterator of matching Redis keys.
|
@@ -612,8 +553,8 @@ class RedisDict:
|
|
612
553
|
return self.get_redis.scan_iter(match=search_query)
|
613
554
|
|
614
555
|
def get(self, key: str, default: Optional[Any] = None) -> Any:
|
615
|
-
"""
|
616
|
-
|
556
|
+
"""Return the value for the given key if it exists, otherwise return the default value.
|
557
|
+
|
617
558
|
Analogous to a dictionary's get method.
|
618
559
|
|
619
560
|
Args:
|
@@ -621,23 +562,30 @@ class RedisDict:
|
|
621
562
|
default (Optional[Any], optional): The value to return if the key is not found.
|
622
563
|
|
623
564
|
Returns:
|
624
|
-
|
565
|
+
Any: The value associated with the key or the default value.
|
625
566
|
"""
|
626
567
|
found, item = self._load(key)
|
627
568
|
if not found:
|
628
569
|
return default
|
629
570
|
return item
|
630
571
|
|
631
|
-
def
|
632
|
-
"""
|
633
|
-
|
572
|
+
def keys(self) -> Iterator[str]:
|
573
|
+
"""Return an Iterator of keys in the RedisDict, analogous to a dictionary's keys method.
|
574
|
+
|
575
|
+
Returns:
|
576
|
+
Iterator[str]: A list of keys in the RedisDict.
|
634
577
|
"""
|
635
578
|
to_rm = len(self.namespace) + 1
|
636
579
|
return (str(item[to_rm:]) for item in self._scan_keys())
|
637
580
|
|
638
581
|
def key(self, search_term: str = '') -> Optional[str]:
|
639
|
-
"""
|
640
|
-
|
582
|
+
"""Return the first value for search_term if it exists, otherwise return None.
|
583
|
+
|
584
|
+
Args:
|
585
|
+
search_term (str): A search term to filter keys. Defaults to ''.
|
586
|
+
|
587
|
+
Returns:
|
588
|
+
str: The first key associated with the given search term.
|
641
589
|
"""
|
642
590
|
to_rm = len(self.namespace) + 1
|
643
591
|
search_query = self._create_iter_query(search_term)
|
@@ -647,18 +595,11 @@ class RedisDict:
|
|
647
595
|
|
648
596
|
return None
|
649
597
|
|
650
|
-
def
|
651
|
-
"""
|
652
|
-
Return a list of keys in the RedisDict, analogous to a dictionary's keys method.
|
598
|
+
def items(self) -> Iterator[Tuple[str, Any]]:
|
599
|
+
"""Return a list of key-value pairs (tuples) in the RedisDict, analogous to a dictionary's items method.
|
653
600
|
|
654
|
-
|
655
|
-
|
656
|
-
"""
|
657
|
-
return list(self.iterkeys())
|
658
|
-
|
659
|
-
def iteritems(self) -> Iterator[Tuple[str, Any]]:
|
660
|
-
"""
|
661
|
-
Note: for python2 str is needed
|
601
|
+
Yields:
|
602
|
+
Iterator[Tuple[str, Any]]: A list of key-value pairs in the RedisDict.
|
662
603
|
"""
|
663
604
|
to_rm = len(self.namespace) + 1
|
664
605
|
for item in self._scan_keys():
|
@@ -667,31 +608,14 @@ class RedisDict:
|
|
667
608
|
except KeyError:
|
668
609
|
pass
|
669
610
|
|
670
|
-
def
|
671
|
-
"""
|
672
|
-
Return a list of key-value pairs (tuples) in the RedisDict, analogous to a dictionary's items method.
|
611
|
+
def values(self) -> Iterator[Any]:
|
612
|
+
"""Analogous to a dictionary's values method.
|
673
613
|
|
674
|
-
|
675
|
-
List[Tuple[str, Any]]: A list of key-value pairs in the RedisDict.
|
676
|
-
"""
|
677
|
-
return list(self.iteritems())
|
678
|
-
|
679
|
-
def values(self) -> List[Any]:
|
680
|
-
"""
|
681
|
-
Return a list of values in the RedisDict, analogous to a dictionary's values method.
|
614
|
+
Return a list of values in the RedisDict,
|
682
615
|
|
683
|
-
|
616
|
+
Yields:
|
684
617
|
List[Any]: A list of values in the RedisDict.
|
685
618
|
"""
|
686
|
-
return list(self.itervalues())
|
687
|
-
|
688
|
-
def itervalues(self) -> Iterator[Any]:
|
689
|
-
"""
|
690
|
-
Iterate over the values in the RedisDict.
|
691
|
-
|
692
|
-
Returns:
|
693
|
-
Iterator[Any]: An iterator of values in the RedisDict.
|
694
|
-
"""
|
695
619
|
to_rm = len(self.namespace) + 1
|
696
620
|
for item in self._scan_keys():
|
697
621
|
try:
|
@@ -700,8 +624,7 @@ class RedisDict:
|
|
700
624
|
pass
|
701
625
|
|
702
626
|
def to_dict(self) -> Dict[str, Any]:
|
703
|
-
"""
|
704
|
-
Convert the RedisDict to a Python dictionary.
|
627
|
+
"""Convert the RedisDict to a Python dictionary.
|
705
628
|
|
706
629
|
Returns:
|
707
630
|
Dict[str, Any]: A dictionary with the same key-value pairs as the RedisDict.
|
@@ -709,8 +632,7 @@ class RedisDict:
|
|
709
632
|
return dict(self.items())
|
710
633
|
|
711
634
|
def clear(self) -> None:
|
712
|
-
"""
|
713
|
-
Remove all key-value pairs from the RedisDict in one batch operation using pipelining.
|
635
|
+
"""Remove all key-value pairs from the RedisDict in one batch operation using pipelining.
|
714
636
|
|
715
637
|
This method mimics the behavior of the `clear` method from a standard Python dictionary.
|
716
638
|
Redis pipelining is employed to group multiple commands into a single request, minimizing
|
@@ -724,33 +646,33 @@ class RedisDict:
|
|
724
646
|
del self[key]
|
725
647
|
|
726
648
|
def pop(self, key: str, default: Union[Any, object] = SENTINEL) -> Any:
|
727
|
-
"""
|
649
|
+
"""Analogous to a dictionary's pop method.
|
650
|
+
|
728
651
|
Remove the value associated with the given key and return it, or return the default value
|
729
|
-
if the key is not found.
|
652
|
+
if the key is not found.
|
730
653
|
|
731
654
|
Args:
|
732
655
|
key (str): The key to remove the value.
|
733
656
|
default (Optional[Any], optional): The value to return if the key is not found.
|
734
657
|
|
735
658
|
Returns:
|
736
|
-
|
659
|
+
Any: The value associated with the key or the default value.
|
737
660
|
|
738
661
|
Raises:
|
739
662
|
KeyError: If the key is not found and no default value is provided.
|
740
663
|
"""
|
741
|
-
|
742
|
-
|
743
|
-
|
664
|
+
formatted_key = self._format_key(key)
|
665
|
+
value = self.get_redis.execute_command("GETDEL", formatted_key)
|
666
|
+
if value is None:
|
744
667
|
if default is not SENTINEL:
|
745
668
|
return default
|
746
|
-
raise
|
669
|
+
raise KeyError(formatted_key)
|
747
670
|
|
748
|
-
|
749
|
-
return value
|
671
|
+
return self._transform(value)
|
750
672
|
|
751
673
|
def popitem(self) -> Tuple[str, Any]:
|
752
|
-
"""
|
753
|
-
|
674
|
+
"""Remove and return a random (key, value) pair from the RedisDict as a tuple.
|
675
|
+
|
754
676
|
This method is analogous to the `popitem` method of a standard Python dictionary.
|
755
677
|
|
756
678
|
Returns:
|
@@ -769,7 +691,8 @@ class RedisDict:
|
|
769
691
|
continue
|
770
692
|
|
771
693
|
def setdefault(self, key: str, default_value: Optional[Any] = None) -> Any:
|
772
|
-
"""
|
694
|
+
"""Get value under key, and if not present set default value.
|
695
|
+
|
773
696
|
Return the value associated with the given key if it exists, otherwise set the value to the
|
774
697
|
default value and return it. Analogous to a dictionary's setdefault method.
|
775
698
|
|
@@ -780,15 +703,28 @@ class RedisDict:
|
|
780
703
|
Returns:
|
781
704
|
Any: The value associated with the key or the default value.
|
782
705
|
"""
|
783
|
-
|
784
|
-
|
785
|
-
|
706
|
+
formatted_key = self._format_key(key)
|
707
|
+
formatted_value = self._format_value(key, default_value)
|
708
|
+
|
709
|
+
# Setting {"get": True} enables parsing of the redis result as "GET", instead of "SET" command
|
710
|
+
options = {"get": True}
|
711
|
+
args = ["SET", formatted_key, formatted_value, "NX", "GET"]
|
712
|
+
if self.preserve_expiration:
|
713
|
+
args.append("KEEPTTL")
|
714
|
+
elif self.expire is not None:
|
715
|
+
expire_val = int(self.expire.total_seconds()) if isinstance(self.expire, timedelta) else self.expire
|
716
|
+
expire_str = str(1) if expire_val <= 1 else str(expire_val)
|
717
|
+
args.extend(["EX", expire_str])
|
718
|
+
|
719
|
+
result = self.get_redis.execute_command(*args, **options)
|
720
|
+
if result is None:
|
786
721
|
return default_value
|
787
|
-
|
722
|
+
|
723
|
+
return self._transform(result)
|
788
724
|
|
789
725
|
def copy(self) -> Dict[str, Any]:
|
790
|
-
"""
|
791
|
-
|
726
|
+
"""Create a shallow copy of the RedisDict and return it as a standard Python dictionary.
|
727
|
+
|
792
728
|
This method is analogous to the `copy` method of a standard Python dictionary
|
793
729
|
|
794
730
|
Returns:
|
@@ -811,7 +747,8 @@ class RedisDict:
|
|
811
747
|
self[key] = value
|
812
748
|
|
813
749
|
def fromkeys(self, iterable: List[str], value: Optional[Any] = None) -> 'RedisDict':
|
814
|
-
"""
|
750
|
+
"""Create a new RedisDict from an iterable of key-value pairs.
|
751
|
+
|
815
752
|
Create a new RedisDict with keys from the provided iterable and values set to the given value.
|
816
753
|
This method is analogous to the `fromkeys` method of a standard Python dictionary, populating
|
817
754
|
the RedisDict with the keys from the iterable and setting their corresponding values to the
|
@@ -831,10 +768,11 @@ class RedisDict:
|
|
831
768
|
return self
|
832
769
|
|
833
770
|
def __sizeof__(self) -> int:
|
834
|
-
"""
|
835
|
-
|
771
|
+
"""Return the approximate size of the RedisDict in memory, in bytes.
|
772
|
+
|
836
773
|
This method is analogous to the `__sizeof__` method of a standard Python dictionary, estimating
|
837
774
|
the memory consumption of the RedisDict based on the serialized in-memory representation.
|
775
|
+
Should be changed to redis view of the size.
|
838
776
|
|
839
777
|
Returns:
|
840
778
|
int: The approximate size of the RedisDict in memory, in bytes.
|
@@ -876,13 +814,12 @@ class RedisDict:
|
|
876
814
|
# compatibility with Python 3.9 typing
|
877
815
|
@contextmanager
|
878
816
|
def expire_at(self, sec_epoch: Union[int, timedelta]) -> Iterator[None]:
|
879
|
-
"""
|
880
|
-
Context manager to set the expiration time for keys in the RedisDict.
|
817
|
+
"""Context manager to set the expiration time for keys in the RedisDict.
|
881
818
|
|
882
819
|
Args:
|
883
820
|
sec_epoch (int, timedelta): The expiration duration is set using either an integer or a timedelta.
|
884
821
|
|
885
|
-
|
822
|
+
Yields:
|
886
823
|
ContextManager: A context manager during which the expiration time is the time set.
|
887
824
|
"""
|
888
825
|
self.expire, temp = sec_epoch, self.expire
|
@@ -894,7 +831,7 @@ class RedisDict:
|
|
894
831
|
"""
|
895
832
|
Context manager to create a Redis pipeline for batch operations.
|
896
833
|
|
897
|
-
|
834
|
+
Yields:
|
898
835
|
ContextManager: A context manager to create a Redis pipeline batching all operations within the context.
|
899
836
|
"""
|
900
837
|
top_level = False
|
@@ -976,7 +913,8 @@ class RedisDict:
|
|
976
913
|
return dict(self.redis.info())
|
977
914
|
|
978
915
|
def get_ttl(self, key: str) -> Optional[int]:
|
979
|
-
"""
|
916
|
+
"""Get the Time To Live from Redis.
|
917
|
+
|
980
918
|
Get the Time To Live (TTL) in seconds for a given key. If the key does not exist or does not have an
|
981
919
|
associated `expire`, return None.
|
982
920
|
|