redis-dict 2.7.0__py3-none-any.whl → 3.1.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 +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
|
|