redis-dict 2.6.0__py3-none-any.whl → 3.0.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|