redis-dict 2.7.0__py3-none-any.whl → 3.1.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 +242 -336
- redis_dict/py.typed +0 -0
- redis_dict/type_management.py +276 -0
- {redis_dict-2.7.0.dist-info → redis_dict-3.1.0.dist-info}/METADATA +74 -34
- redis_dict-3.1.0.dist-info/RECORD +9 -0
- redis_dict-2.7.0.dist-info/RECORD +0 -6
- {redis_dict-2.7.0.dist-info → redis_dict-3.1.0.dist-info}/LICENSE +0 -0
- {redis_dict-2.7.0.dist-info → redis_dict-3.1.0.dist-info}/WHEEL +0 -0
- {redis_dict-2.7.0.dist-info → redis_dict-3.1.0.dist-info}/top_level.txt +0 -0
@@ -1,174 +1,23 @@
|
|
1
|
-
"""
|
2
|
-
|
3
|
-
|
4
|
-
|
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
|
-
# Types imports
|
70
|
-
import json
|
71
|
-
from datetime import datetime, time, timedelta, date
|
72
|
-
from decimal import Decimal
|
73
|
-
from uuid import UUID
|
74
|
-
from collections import OrderedDict, defaultdict
|
75
|
-
import base64
|
76
|
-
|
77
|
-
from typing import Any, Callable, Dict, Iterator, Set, List, Tuple, Union, Optional
|
1
|
+
"""Redis Dict module."""
|
2
|
+
from typing import Any, Dict, Iterator, List, Tuple, Union, Optional, Type
|
3
|
+
|
4
|
+
from datetime import timedelta
|
78
5
|
from contextlib import contextmanager
|
6
|
+
from collections.abc import Mapping
|
79
7
|
|
80
8
|
from redis import StrictRedis
|
81
9
|
|
82
|
-
SENTINEL
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
EncodeType = Dict[str, EncodeFuncType]
|
88
|
-
DecodeType = Dict[str, DecodeFuncType]
|
89
|
-
|
90
|
-
|
91
|
-
def _create_default_encode(custom_encode_method: str) -> EncodeFuncType:
|
92
|
-
def default_encode(obj: Any) -> str:
|
93
|
-
return getattr(obj, custom_encode_method)() # type: ignore[no-any-return]
|
94
|
-
return default_encode
|
95
|
-
|
96
|
-
|
97
|
-
def _create_default_decode(cls: type, custom_decode_method: str) -> DecodeFuncType:
|
98
|
-
def default_decode(encoded_str: str) -> Any:
|
99
|
-
return getattr(cls, custom_decode_method)(encoded_str)
|
100
|
-
return default_decode
|
101
|
-
|
102
|
-
|
103
|
-
def _decode_tuple(val: str) -> Tuple[Any, ...]:
|
104
|
-
"""
|
105
|
-
Deserialize a JSON-formatted string to a tuple.
|
106
|
-
|
107
|
-
This function takes a JSON-formatted string, deserializes it to a list, and
|
108
|
-
then converts the list to a tuple.
|
109
|
-
|
110
|
-
Args:
|
111
|
-
val (str): A JSON-formatted string representing a list.
|
112
|
-
|
113
|
-
Returns:
|
114
|
-
Tuple[Any, ...]: A tuple with the deserialized values from the input string.
|
115
|
-
"""
|
116
|
-
return tuple(json.loads(val))
|
117
|
-
|
118
|
-
|
119
|
-
def _encode_tuple(val: Tuple[Any, ...]) -> str:
|
120
|
-
"""
|
121
|
-
Serialize a tuple to a JSON-formatted string.
|
122
|
-
|
123
|
-
This function takes a tuple, converts it to a list, and then serializes
|
124
|
-
the list to a JSON-formatted string.
|
125
|
-
|
126
|
-
Args:
|
127
|
-
val (Tuple[Any, ...]): A tuple with values to be serialized.
|
128
|
-
|
129
|
-
Returns:
|
130
|
-
str: A JSON-formatted string representing the input tuple.
|
131
|
-
"""
|
132
|
-
return json.dumps(list(val))
|
133
|
-
|
134
|
-
|
135
|
-
def _decode_set(val: str) -> Set[Any]:
|
136
|
-
"""
|
137
|
-
Deserialize a JSON-formatted string to a set.
|
138
|
-
|
139
|
-
This function takes a JSON-formatted string, deserializes it to a list, and
|
140
|
-
then converts the list to a set.
|
141
|
-
|
142
|
-
Args:
|
143
|
-
val (str): A JSON-formatted string representing a list.
|
144
|
-
|
145
|
-
Returns:
|
146
|
-
set[Any]: A set with the deserialized values from the input string.
|
147
|
-
"""
|
148
|
-
return set(json.loads(val))
|
149
|
-
|
150
|
-
|
151
|
-
def _encode_set(val: Set[Any]) -> str:
|
152
|
-
"""
|
153
|
-
Serialize a set to a JSON-formatted string.
|
154
|
-
|
155
|
-
This function takes a set, converts it to a list, and then serializes the
|
156
|
-
list to a JSON-formatted string.
|
157
|
-
|
158
|
-
Args:
|
159
|
-
val (set[Any]): A set with values to be serialized.
|
160
|
-
|
161
|
-
Returns:
|
162
|
-
str: A JSON-formatted string representing the input set.
|
163
|
-
"""
|
164
|
-
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
|
165
14
|
|
166
15
|
|
167
16
|
# pylint: disable=R0902, R0904
|
168
17
|
class RedisDict:
|
169
|
-
"""
|
170
|
-
|
171
|
-
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.
|
172
21
|
|
173
22
|
This class provides a dictionary-like interface that interacts with a Redis database, allowing
|
174
23
|
for efficient storage and retrieval of key-value pairs. It supports various data types, including
|
@@ -186,78 +35,37 @@ class RedisDict:
|
|
186
35
|
This functionality enables various use cases, such as managing encrypted data in Redis,
|
187
36
|
To implement this, simply create and register your custom encoding and decoding functions.
|
188
37
|
By delegating serialization to redis-dict, reduce complexity and have simple code in the codebase.
|
189
|
-
|
190
|
-
Attributes:
|
191
|
-
decoding_registry (Dict[str, DecodeFuncType]): Mapping of decoding transformation functions based on type
|
192
|
-
encoding_registry (Dict[str, EncodeFuncType]): Mapping of encoding transformation functions based on type
|
193
|
-
namespace (str): A string used as a prefix for Redis keys to separate data in different namespaces.
|
194
|
-
expire (Union[int, None]): An optional expiration time for keys, in seconds.
|
195
|
-
|
196
38
|
"""
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
type(0.1).__name__: float,
|
201
|
-
type(True).__name__: lambda x: x == "True",
|
202
|
-
type(None).__name__: lambda x: None,
|
203
|
-
|
204
|
-
"list": json.loads,
|
205
|
-
"dict": json.loads,
|
206
|
-
"tuple": _decode_tuple,
|
207
|
-
type(set()).__name__: _decode_set,
|
208
|
-
|
209
|
-
datetime.__name__: datetime.fromisoformat,
|
210
|
-
date.__name__: date.fromisoformat,
|
211
|
-
time.__name__: time.fromisoformat,
|
212
|
-
timedelta.__name__: lambda x: timedelta(seconds=float(x)),
|
213
|
-
|
214
|
-
Decimal.__name__: Decimal,
|
215
|
-
complex.__name__: lambda x: complex(*map(float, x.split(','))),
|
216
|
-
bytes.__name__: base64.b64decode,
|
217
|
-
|
218
|
-
UUID.__name__: UUID,
|
219
|
-
OrderedDict.__name__: lambda x: OrderedDict(json.loads(x)),
|
220
|
-
defaultdict.__name__: lambda x: defaultdict(type(None), json.loads(x)),
|
221
|
-
frozenset.__name__: lambda x: frozenset(json.loads(x)),
|
222
|
-
}
|
223
|
-
|
224
|
-
encoding_registry: EncodeType = {
|
225
|
-
"list": json.dumps,
|
226
|
-
"dict": json.dumps,
|
227
|
-
"tuple": _encode_tuple,
|
228
|
-
type(set()).__name__: _encode_set,
|
229
|
-
|
230
|
-
datetime.__name__: datetime.isoformat,
|
231
|
-
date.__name__: date.isoformat,
|
232
|
-
time.__name__: time.isoformat,
|
233
|
-
timedelta.__name__: lambda x: str(x.total_seconds()),
|
234
|
-
|
235
|
-
complex.__name__: lambda x: f"{x.real},{x.imag}",
|
236
|
-
bytes.__name__: lambda x: base64.b64encode(x).decode('ascii'),
|
237
|
-
OrderedDict.__name__: lambda x: json.dumps(list(x.items())),
|
238
|
-
defaultdict.__name__: lambda x: json.dumps(dict(x)),
|
239
|
-
frozenset.__name__: lambda x: json.dumps(list(x)),
|
240
|
-
}
|
39
|
+
|
40
|
+
encoding_registry: EncodeType = enc_reg
|
41
|
+
decoding_registry: DecodeType = dec_reg
|
241
42
|
|
242
43
|
def __init__(self,
|
243
44
|
namespace: str = 'main',
|
244
45
|
expire: Union[int, timedelta, None] = None,
|
245
46
|
preserve_expiration: Optional[bool] = False,
|
47
|
+
redis: "Optional[StrictRedis[Any]]" = None,
|
246
48
|
**redis_kwargs: Any) -> None:
|
247
49
|
"""
|
248
50
|
Initialize a RedisDict instance.
|
249
51
|
|
52
|
+
Init the RedisDict instance.
|
53
|
+
|
250
54
|
Args:
|
251
|
-
namespace (str
|
252
|
-
expire (int, timedelta, optional): Expiration time for keys
|
253
|
-
preserve_expiration (bool, optional): Preserve
|
254
|
-
|
55
|
+
namespace (str): A prefix for keys stored in Redis.
|
56
|
+
expire (Union[int, timedelta, None], optional): Expiration time for keys.
|
57
|
+
preserve_expiration (Optional[bool], optional): Preserve expiration on key updates.
|
58
|
+
redis (Optional[StrictRedis[Any]], optional): A Redis connection instance.
|
59
|
+
**redis_kwargs (Any): Additional kwargs for Redis connection if not provided.
|
255
60
|
"""
|
256
61
|
|
257
62
|
self.namespace: str = namespace
|
258
63
|
self.expire: Union[int, timedelta, None] = expire
|
259
64
|
self.preserve_expiration: Optional[bool] = preserve_expiration
|
260
|
-
|
65
|
+
if redis:
|
66
|
+
redis.connection_pool.connection_kwargs["decode_responses"] = True
|
67
|
+
|
68
|
+
self.redis: StrictRedis[Any] = redis or StrictRedis(decode_responses=True, **redis_kwargs)
|
261
69
|
self.get_redis: StrictRedis[Any] = self.redis
|
262
70
|
|
263
71
|
self.custom_encode_method = "encode"
|
@@ -288,7 +96,7 @@ class RedisDict:
|
|
288
96
|
length does not exceed the maximum allowed size (500 MB).
|
289
97
|
|
290
98
|
Args:
|
291
|
-
val (
|
99
|
+
val (Any): The input value to be validated.
|
292
100
|
val_type (str): The type of the input value ("str", "int", "float", or "bool").
|
293
101
|
|
294
102
|
Returns:
|
@@ -298,7 +106,19 @@ class RedisDict:
|
|
298
106
|
return len(val) < self._max_string_size
|
299
107
|
return True
|
300
108
|
|
301
|
-
def _format_value(self, key: str,
|
109
|
+
def _format_value(self, key: str, value: Any) -> str:
|
110
|
+
"""Format a valid value with the type and encoded representation of the value.
|
111
|
+
|
112
|
+
Args:
|
113
|
+
key (str): The key of the value to be formatted.
|
114
|
+
value (Any): The value to be encoded and formatted.
|
115
|
+
|
116
|
+
Raises:
|
117
|
+
ValueError: If the value or key fail validation.
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
str: The formatted value with the type and encoded representation of the value.
|
121
|
+
"""
|
302
122
|
store_type, key = type(value).__name__, str(key)
|
303
123
|
if not self._valid_input(value, store_type) or not self._valid_input(key, "str"):
|
304
124
|
raise ValueError("Invalid input value or key size exceeded the maximum limit.")
|
@@ -355,7 +175,7 @@ class RedisDict:
|
|
355
175
|
Any: The transformed Python object.
|
356
176
|
"""
|
357
177
|
type_, value = result.split(':', 1)
|
358
|
-
return self.decoding_registry.get(type_,
|
178
|
+
return self.decoding_registry.get(type_, _default_decoder)(value)
|
359
179
|
|
360
180
|
def new_type_compliance(
|
361
181
|
self,
|
@@ -363,8 +183,7 @@ class RedisDict:
|
|
363
183
|
encode_method_name: Optional[str] = None,
|
364
184
|
decode_method_name: Optional[str] = None,
|
365
185
|
) -> None:
|
366
|
-
"""
|
367
|
-
Checks if a class complies with the required encoding and decoding methods.
|
186
|
+
"""Check if a class complies with the required encoding and decoding methods.
|
368
187
|
|
369
188
|
Args:
|
370
189
|
class_type (type): The class to check for compliance.
|
@@ -386,6 +205,7 @@ class RedisDict:
|
|
386
205
|
raise NotImplementedError(
|
387
206
|
f"Class {class_type.__name__} does not implement the required {decode_method_name} class method.")
|
388
207
|
|
208
|
+
# pylint: disable=too-many-arguments
|
389
209
|
def extends_type(
|
390
210
|
self,
|
391
211
|
class_type: type,
|
@@ -395,30 +215,21 @@ class RedisDict:
|
|
395
215
|
decoding_method_name: Optional[str] = None,
|
396
216
|
) -> None:
|
397
217
|
"""
|
398
|
-
|
218
|
+
Extend RedisDict to support a custom type in the encode/decode mapping.
|
399
219
|
|
400
220
|
This method enables serialization of instances based on their type,
|
401
221
|
allowing for custom types, specialized storage formats, and more.
|
402
222
|
There are three ways to add custom types:
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
223
|
+
1. Have a class with an `encode` instance method and a `decode` class method.
|
224
|
+
2. Have a class and pass encoding and decoding functions, where
|
225
|
+
`encode` converts the class instance to a string, and
|
226
|
+
`decode` takes the string and recreates the class instance.
|
227
|
+
3. Have a class that already has serialization methods, that satisfies the:
|
228
|
+
EncodeFuncType = Callable[[Any], str]
|
229
|
+
DecodeFuncType = Callable[[str], Any]
|
410
230
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
Args:
|
415
|
-
class_type (Type[type]): The class `__name__` will become the key for the encoding and decoding functions.
|
416
|
-
encode (Optional[EncodeFuncType]): function that encodes an object into a storable string format.
|
417
|
-
This function should take an instance of `class_type` as input and return a string.
|
418
|
-
decode (Optional[DecodeFuncType]): function that decodes a string back into an object of `class_type`.
|
419
|
-
This function should take a string as input and return an instance of `class_type`.
|
420
|
-
encoding_method_name (str, optional): Name of encoding method of the class for redis-dict custom types.
|
421
|
-
decoding_method_name (str, optional): Name of decoding method of the class for redis-dict custom types.
|
231
|
+
`custom_encode_method`
|
232
|
+
`custom_decode_method`
|
422
233
|
|
423
234
|
If no encoding or decoding function is provided, default to use the `encode` and `decode` methods of the class.
|
424
235
|
|
@@ -431,25 +242,35 @@ class RedisDict:
|
|
431
242
|
attributes of the RedisDict instance
|
432
243
|
|
433
244
|
Example:
|
434
|
-
class Person:
|
435
|
-
|
436
|
-
|
437
|
-
|
245
|
+
>>> class Person:
|
246
|
+
... def __init__(self, name, age):
|
247
|
+
... self.name = name
|
248
|
+
... self.age = age
|
249
|
+
...
|
250
|
+
... def encode(self) -> str:
|
251
|
+
... return json.dumps(self.__dict__)
|
252
|
+
...
|
253
|
+
... @classmethod
|
254
|
+
... def decode(cls, encoded_str: str) -> 'Person':
|
255
|
+
... return cls(**json.loads(encoded_str))
|
256
|
+
...
|
257
|
+
>>> redis_dict.extends_type(Person)
|
438
258
|
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
259
|
+
Args:
|
260
|
+
class_type (type): The class `__name__` will become the key for the encoding and decoding functions.
|
261
|
+
encode (Optional[EncodeFuncType]): function that encodes an object into a storable string format.
|
262
|
+
decode (Optional[DecodeFuncType]): function that decodes a string back into an object of `class_type`.
|
263
|
+
encoding_method_name (str, optional): Name of encoding method of the class for redis-dict custom types.
|
264
|
+
decoding_method_name (str, optional): Name of decoding method of the class for redis-dict custom types.
|
445
265
|
|
446
|
-
|
266
|
+
Raises:
|
267
|
+
NotImplementedError
|
447
268
|
|
448
269
|
Note:
|
449
|
-
|
270
|
+
You can check for compliance of a class separately using the `new_type_compliance` method:
|
450
271
|
|
451
|
-
|
452
|
-
|
272
|
+
This method raises a NotImplementedError if either `encode` or `decode` is `None`
|
273
|
+
and the class does not implement the corresponding method.
|
453
274
|
"""
|
454
275
|
|
455
276
|
if encode is None or decode is None:
|
@@ -460,7 +281,7 @@ class RedisDict:
|
|
460
281
|
|
461
282
|
if decode is None:
|
462
283
|
decode_method_name = decoding_method_name or self.custom_decode_method
|
463
|
-
self.new_type_compliance(class_type,
|
284
|
+
self.new_type_compliance(class_type, decode_method_name=decode_method_name)
|
464
285
|
decode = _create_default_decode(class_type, decode_method_name)
|
465
286
|
|
466
287
|
type_name = class_type.__name__
|
@@ -479,7 +300,7 @@ class RedisDict:
|
|
479
300
|
"""
|
480
301
|
if len(self) != len(other):
|
481
302
|
return False
|
482
|
-
for key, value in self.
|
303
|
+
for key, value in self.items():
|
483
304
|
if value != other.get(key, SENTINEL):
|
484
305
|
return False
|
485
306
|
return True
|
@@ -561,7 +382,7 @@ class RedisDict:
|
|
561
382
|
Returns:
|
562
383
|
Iterator[str]: An iterator over the keys of the RedisDict.
|
563
384
|
"""
|
564
|
-
self._iter = self.
|
385
|
+
self._iter = self.keys()
|
565
386
|
return self
|
566
387
|
|
567
388
|
def __repr__(self) -> str:
|
@@ -582,15 +403,105 @@ class RedisDict:
|
|
582
403
|
"""
|
583
404
|
return str(self.to_dict())
|
584
405
|
|
406
|
+
def __or__(self, other: Dict[str, Any]) -> Dict[str, Any]:
|
407
|
+
"""Implement the | operator (dict union).
|
408
|
+
|
409
|
+
Returns a new dictionary with items from both dictionaries.
|
410
|
+
|
411
|
+
Args:
|
412
|
+
other (Dict[str, Any]): The dictionary to merge with.
|
413
|
+
|
414
|
+
Raises:
|
415
|
+
TypeError: If other does not adhere to Mapping.
|
416
|
+
|
417
|
+
Returns:
|
418
|
+
Dict[str, Any]: A new dictionary containing items from both dictionaries.
|
419
|
+
"""
|
420
|
+
if not isinstance(other, Mapping):
|
421
|
+
raise TypeError(f"unsupported operand type(s) for |: '{type(other).__name__}' and 'RedisDict'")
|
422
|
+
|
423
|
+
result = {}
|
424
|
+
result.update(self.to_dict())
|
425
|
+
result.update(other)
|
426
|
+
return result
|
427
|
+
|
428
|
+
def __ror__(self, other: Dict[str, Any]) -> Dict[str, Any]:
|
429
|
+
"""
|
430
|
+
Implement the reverse | operator.
|
431
|
+
|
432
|
+
Called when RedisDict is on the right side of |.
|
433
|
+
|
434
|
+
Args:
|
435
|
+
other (Dict[str, Any]): The dictionary to merge with.
|
436
|
+
|
437
|
+
Raises:
|
438
|
+
TypeError: If other does not adhere to Mapping.
|
439
|
+
|
440
|
+
Returns:
|
441
|
+
Dict[str, Any]: A new dictionary containing items from both dictionaries.
|
442
|
+
"""
|
443
|
+
if not isinstance(other, Mapping):
|
444
|
+
raise TypeError(f"unsupported operand type(s) for |: 'RedisDict' and '{type(other).__name__}'")
|
445
|
+
|
446
|
+
result = {}
|
447
|
+
result.update(other)
|
448
|
+
result.update(self.to_dict())
|
449
|
+
return result
|
450
|
+
|
451
|
+
def __ior__(self, other: Dict[str, Any]) -> 'RedisDict':
|
452
|
+
"""
|
453
|
+
Implement the |= operator (in-place union).
|
454
|
+
|
455
|
+
Modifies the current dictionary by adding items from other.
|
456
|
+
|
457
|
+
Args:
|
458
|
+
other (Dict[str, Any]): The dictionary to merge with.
|
459
|
+
|
460
|
+
Raises:
|
461
|
+
TypeError: If other does not adhere to Mapping.
|
462
|
+
|
463
|
+
Returns:
|
464
|
+
RedisDict: The modified RedisDict instance.
|
465
|
+
"""
|
466
|
+
if not isinstance(other, Mapping):
|
467
|
+
raise TypeError(f"unsupported operand type(s) for |: '{type(other).__name__}' and 'RedisDict'")
|
468
|
+
|
469
|
+
self.update(other)
|
470
|
+
return self
|
471
|
+
|
472
|
+
@classmethod
|
473
|
+
def __class_getitem__(cls: Type['RedisDict'], key: Any) -> Type['RedisDict']:
|
474
|
+
"""
|
475
|
+
Enable type hinting support like RedisDict[str, Any].
|
476
|
+
|
477
|
+
Args:
|
478
|
+
key (Any): The type parameter(s) used in the type hint.
|
479
|
+
|
480
|
+
Returns:
|
481
|
+
Type[RedisDict]: The class itself, enabling type hint usage.
|
482
|
+
"""
|
483
|
+
return cls
|
484
|
+
|
485
|
+
def __reversed__(self) -> Iterator[str]:
|
486
|
+
"""
|
487
|
+
Implement reversed() built-in.
|
488
|
+
|
489
|
+
Returns an iterator over dictionary keys in reverse insertion order.
|
490
|
+
|
491
|
+
Warning:
|
492
|
+
RedisDict Currently does not support 'insertion order' as property thus also not reversed.
|
493
|
+
|
494
|
+
Returns:
|
495
|
+
Iterator[str]: An iterator yielding the dictionary keys in reverse order.
|
496
|
+
"""
|
497
|
+
return reversed(list(self.keys()))
|
498
|
+
|
585
499
|
def __next__(self) -> str:
|
586
500
|
"""
|
587
501
|
Get the next item in the iterator.
|
588
502
|
|
589
503
|
Returns:
|
590
504
|
str: The next item in the iterator.
|
591
|
-
|
592
|
-
Raises:
|
593
|
-
StopIteration: If there are no more items.
|
594
505
|
"""
|
595
506
|
return next(self._iter)
|
596
507
|
|
@@ -601,8 +512,6 @@ class RedisDict:
|
|
601
512
|
Returns:
|
602
513
|
str: The next item in the iterator.
|
603
514
|
|
604
|
-
Raises:
|
605
|
-
StopIteration: If there are no more items.
|
606
515
|
"""
|
607
516
|
return next(self)
|
608
517
|
|
@@ -633,7 +542,7 @@ class RedisDict:
|
|
633
542
|
Scan for Redis keys matching the given search term.
|
634
543
|
|
635
544
|
Args:
|
636
|
-
search_term (str
|
545
|
+
search_term (str): A search term to filter keys. Defaults to ''.
|
637
546
|
|
638
547
|
Returns:
|
639
548
|
Iterator[str]: An iterator of matching Redis keys.
|
@@ -642,8 +551,8 @@ class RedisDict:
|
|
642
551
|
return self.get_redis.scan_iter(match=search_query)
|
643
552
|
|
644
553
|
def get(self, key: str, default: Optional[Any] = None) -> Any:
|
645
|
-
"""
|
646
|
-
|
554
|
+
"""Return the value for the given key if it exists, otherwise return the default value.
|
555
|
+
|
647
556
|
Analogous to a dictionary's get method.
|
648
557
|
|
649
558
|
Args:
|
@@ -651,23 +560,30 @@ class RedisDict:
|
|
651
560
|
default (Optional[Any], optional): The value to return if the key is not found.
|
652
561
|
|
653
562
|
Returns:
|
654
|
-
|
563
|
+
Any: The value associated with the key or the default value.
|
655
564
|
"""
|
656
565
|
found, item = self._load(key)
|
657
566
|
if not found:
|
658
567
|
return default
|
659
568
|
return item
|
660
569
|
|
661
|
-
def
|
662
|
-
"""
|
663
|
-
|
570
|
+
def keys(self) -> Iterator[str]:
|
571
|
+
"""Return an Iterator of keys in the RedisDict, analogous to a dictionary's keys method.
|
572
|
+
|
573
|
+
Returns:
|
574
|
+
Iterator[str]: A list of keys in the RedisDict.
|
664
575
|
"""
|
665
576
|
to_rm = len(self.namespace) + 1
|
666
577
|
return (str(item[to_rm:]) for item in self._scan_keys())
|
667
578
|
|
668
579
|
def key(self, search_term: str = '') -> Optional[str]:
|
669
|
-
"""
|
670
|
-
|
580
|
+
"""Return the first value for search_term if it exists, otherwise return None.
|
581
|
+
|
582
|
+
Args:
|
583
|
+
search_term (str): A search term to filter keys. Defaults to ''.
|
584
|
+
|
585
|
+
Returns:
|
586
|
+
str: The first key associated with the given search term.
|
671
587
|
"""
|
672
588
|
to_rm = len(self.namespace) + 1
|
673
589
|
search_query = self._create_iter_query(search_term)
|
@@ -677,18 +593,11 @@ class RedisDict:
|
|
677
593
|
|
678
594
|
return None
|
679
595
|
|
680
|
-
def
|
681
|
-
"""
|
682
|
-
Return a list of keys in the RedisDict, analogous to a dictionary's keys method.
|
683
|
-
|
684
|
-
Returns:
|
685
|
-
List[str]: A list of keys in the RedisDict.
|
686
|
-
"""
|
687
|
-
return list(self.iterkeys())
|
596
|
+
def items(self) -> Iterator[Tuple[str, Any]]:
|
597
|
+
"""Return a list of key-value pairs (tuples) in the RedisDict, analogous to a dictionary's items method.
|
688
598
|
|
689
|
-
|
690
|
-
|
691
|
-
Note: for python2 str is needed
|
599
|
+
Yields:
|
600
|
+
Iterator[Tuple[str, Any]]: A list of key-value pairs in the RedisDict.
|
692
601
|
"""
|
693
602
|
to_rm = len(self.namespace) + 1
|
694
603
|
for item in self._scan_keys():
|
@@ -697,31 +606,14 @@ class RedisDict:
|
|
697
606
|
except KeyError:
|
698
607
|
pass
|
699
608
|
|
700
|
-
def
|
701
|
-
"""
|
702
|
-
Return a list of key-value pairs (tuples) in the RedisDict, analogous to a dictionary's items method.
|
609
|
+
def values(self) -> Iterator[Any]:
|
610
|
+
"""Analogous to a dictionary's values method.
|
703
611
|
|
704
|
-
|
705
|
-
List[Tuple[str, Any]]: A list of key-value pairs in the RedisDict.
|
706
|
-
"""
|
707
|
-
return list(self.iteritems())
|
612
|
+
Return a list of values in the RedisDict,
|
708
613
|
|
709
|
-
|
710
|
-
"""
|
711
|
-
Return a list of values in the RedisDict, analogous to a dictionary's values method.
|
712
|
-
|
713
|
-
Returns:
|
614
|
+
Yields:
|
714
615
|
List[Any]: A list of values in the RedisDict.
|
715
616
|
"""
|
716
|
-
return list(self.itervalues())
|
717
|
-
|
718
|
-
def itervalues(self) -> Iterator[Any]:
|
719
|
-
"""
|
720
|
-
Iterate over the values in the RedisDict.
|
721
|
-
|
722
|
-
Returns:
|
723
|
-
Iterator[Any]: An iterator of values in the RedisDict.
|
724
|
-
"""
|
725
617
|
to_rm = len(self.namespace) + 1
|
726
618
|
for item in self._scan_keys():
|
727
619
|
try:
|
@@ -730,8 +622,7 @@ class RedisDict:
|
|
730
622
|
pass
|
731
623
|
|
732
624
|
def to_dict(self) -> Dict[str, Any]:
|
733
|
-
"""
|
734
|
-
Convert the RedisDict to a Python dictionary.
|
625
|
+
"""Convert the RedisDict to a Python dictionary.
|
735
626
|
|
736
627
|
Returns:
|
737
628
|
Dict[str, Any]: A dictionary with the same key-value pairs as the RedisDict.
|
@@ -739,8 +630,7 @@ class RedisDict:
|
|
739
630
|
return dict(self.items())
|
740
631
|
|
741
632
|
def clear(self) -> None:
|
742
|
-
"""
|
743
|
-
Remove all key-value pairs from the RedisDict in one batch operation using pipelining.
|
633
|
+
"""Remove all key-value pairs from the RedisDict in one batch operation using pipelining.
|
744
634
|
|
745
635
|
This method mimics the behavior of the `clear` method from a standard Python dictionary.
|
746
636
|
Redis pipelining is employed to group multiple commands into a single request, minimizing
|
@@ -754,33 +644,33 @@ class RedisDict:
|
|
754
644
|
del self[key]
|
755
645
|
|
756
646
|
def pop(self, key: str, default: Union[Any, object] = SENTINEL) -> Any:
|
757
|
-
"""
|
647
|
+
"""Analogous to a dictionary's pop method.
|
648
|
+
|
758
649
|
Remove the value associated with the given key and return it, or return the default value
|
759
|
-
if the key is not found.
|
650
|
+
if the key is not found.
|
760
651
|
|
761
652
|
Args:
|
762
653
|
key (str): The key to remove the value.
|
763
654
|
default (Optional[Any], optional): The value to return if the key is not found.
|
764
655
|
|
765
656
|
Returns:
|
766
|
-
|
657
|
+
Any: The value associated with the key or the default value.
|
767
658
|
|
768
659
|
Raises:
|
769
660
|
KeyError: If the key is not found and no default value is provided.
|
770
661
|
"""
|
771
|
-
|
772
|
-
|
773
|
-
|
662
|
+
formatted_key = self._format_key(key)
|
663
|
+
value = self.get_redis.execute_command("GETDEL", formatted_key)
|
664
|
+
if value is None:
|
774
665
|
if default is not SENTINEL:
|
775
666
|
return default
|
776
|
-
raise
|
667
|
+
raise KeyError(formatted_key)
|
777
668
|
|
778
|
-
|
779
|
-
return value
|
669
|
+
return self._transform(value)
|
780
670
|
|
781
671
|
def popitem(self) -> Tuple[str, Any]:
|
782
|
-
"""
|
783
|
-
|
672
|
+
"""Remove and return a random (key, value) pair from the RedisDict as a tuple.
|
673
|
+
|
784
674
|
This method is analogous to the `popitem` method of a standard Python dictionary.
|
785
675
|
|
786
676
|
Returns:
|
@@ -799,7 +689,8 @@ class RedisDict:
|
|
799
689
|
continue
|
800
690
|
|
801
691
|
def setdefault(self, key: str, default_value: Optional[Any] = None) -> Any:
|
802
|
-
"""
|
692
|
+
"""Get value under key, and if not present set default value.
|
693
|
+
|
803
694
|
Return the value associated with the given key if it exists, otherwise set the value to the
|
804
695
|
default value and return it. Analogous to a dictionary's setdefault method.
|
805
696
|
|
@@ -810,15 +701,28 @@ class RedisDict:
|
|
810
701
|
Returns:
|
811
702
|
Any: The value associated with the key or the default value.
|
812
703
|
"""
|
813
|
-
|
814
|
-
|
815
|
-
|
704
|
+
formatted_key = self._format_key(key)
|
705
|
+
formatted_value = self._format_value(key, default_value)
|
706
|
+
|
707
|
+
# Setting {"get": True} enables parsing of the redis result as "GET", instead of "SET" command
|
708
|
+
options = {"get": True}
|
709
|
+
args = ["SET", formatted_key, formatted_value, "NX", "GET"]
|
710
|
+
if self.preserve_expiration:
|
711
|
+
args.append("KEEPTTL")
|
712
|
+
elif self.expire is not None:
|
713
|
+
expire_val = int(self.expire.total_seconds()) if isinstance(self.expire, timedelta) else self.expire
|
714
|
+
expire_str = str(1) if expire_val <= 1 else str(expire_val)
|
715
|
+
args.extend(["EX", expire_str])
|
716
|
+
|
717
|
+
result = self.get_redis.execute_command(*args, **options)
|
718
|
+
if result is None:
|
816
719
|
return default_value
|
817
|
-
|
720
|
+
|
721
|
+
return self._transform(result)
|
818
722
|
|
819
723
|
def copy(self) -> Dict[str, Any]:
|
820
|
-
"""
|
821
|
-
|
724
|
+
"""Create a shallow copy of the RedisDict and return it as a standard Python dictionary.
|
725
|
+
|
822
726
|
This method is analogous to the `copy` method of a standard Python dictionary
|
823
727
|
|
824
728
|
Returns:
|
@@ -841,7 +745,8 @@ class RedisDict:
|
|
841
745
|
self[key] = value
|
842
746
|
|
843
747
|
def fromkeys(self, iterable: List[str], value: Optional[Any] = None) -> 'RedisDict':
|
844
|
-
"""
|
748
|
+
"""Create a new RedisDict from an iterable of key-value pairs.
|
749
|
+
|
845
750
|
Create a new RedisDict with keys from the provided iterable and values set to the given value.
|
846
751
|
This method is analogous to the `fromkeys` method of a standard Python dictionary, populating
|
847
752
|
the RedisDict with the keys from the iterable and setting their corresponding values to the
|
@@ -861,10 +766,11 @@ class RedisDict:
|
|
861
766
|
return self
|
862
767
|
|
863
768
|
def __sizeof__(self) -> int:
|
864
|
-
"""
|
865
|
-
|
769
|
+
"""Return the approximate size of the RedisDict in memory, in bytes.
|
770
|
+
|
866
771
|
This method is analogous to the `__sizeof__` method of a standard Python dictionary, estimating
|
867
772
|
the memory consumption of the RedisDict based on the serialized in-memory representation.
|
773
|
+
Should be changed to redis view of the size.
|
868
774
|
|
869
775
|
Returns:
|
870
776
|
int: The approximate size of the RedisDict in memory, in bytes.
|
@@ -906,13 +812,12 @@ class RedisDict:
|
|
906
812
|
# compatibility with Python 3.9 typing
|
907
813
|
@contextmanager
|
908
814
|
def expire_at(self, sec_epoch: Union[int, timedelta]) -> Iterator[None]:
|
909
|
-
"""
|
910
|
-
Context manager to set the expiration time for keys in the RedisDict.
|
815
|
+
"""Context manager to set the expiration time for keys in the RedisDict.
|
911
816
|
|
912
817
|
Args:
|
913
818
|
sec_epoch (int, timedelta): The expiration duration is set using either an integer or a timedelta.
|
914
819
|
|
915
|
-
|
820
|
+
Yields:
|
916
821
|
ContextManager: A context manager during which the expiration time is the time set.
|
917
822
|
"""
|
918
823
|
self.expire, temp = sec_epoch, self.expire
|
@@ -924,7 +829,7 @@ class RedisDict:
|
|
924
829
|
"""
|
925
830
|
Context manager to create a Redis pipeline for batch operations.
|
926
831
|
|
927
|
-
|
832
|
+
Yields:
|
928
833
|
ContextManager: A context manager to create a Redis pipeline batching all operations within the context.
|
929
834
|
"""
|
930
835
|
top_level = False
|
@@ -1006,7 +911,8 @@ class RedisDict:
|
|
1006
911
|
return dict(self.redis.info())
|
1007
912
|
|
1008
913
|
def get_ttl(self, key: str) -> Optional[int]:
|
1009
|
-
"""
|
914
|
+
"""Get the Time To Live from Redis.
|
915
|
+
|
1010
916
|
Get the Time To Live (TTL) in seconds for a given key. If the key does not exist or does not have an
|
1011
917
|
associated `expire`, return None.
|
1012
918
|
|