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.
@@ -1,174 +1,23 @@
1
- """
2
- redis_dict.py
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
- # 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 = object()
83
-
84
- EncodeFuncType = Callable[[Any], str]
85
- DecodeFuncType = Callable[[str], Any]
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
- A Redis-backed dictionary-like data structure with support for advanced features, such as
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
- decoding_registry: DecodeType = {
198
- type('').__name__: str,
199
- type(1).__name__: int,
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, optional): A prefix for keys stored in Redis.
252
- expire (int, timedelta, optional): Expiration time for keys in seconds.
253
- preserve_expiration (bool, optional): Preserve the expiration count when the key is updated.
254
- **redis_kwargs: Additional keyword arguments passed to StrictRedis.
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
- self.redis: StrictRedis[Any] = StrictRedis(decode_responses=True, **redis_kwargs)
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 (Union[str, int, float, bool]): The input value to be validated.
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, value: Any) -> 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_, lambda x: x)(value)
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
- Extends RedisDict to support a custom type in the encode/decode mapping.
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
- 1. Have a class with an `encode` instance method and a `decode` class method.
404
- 2. Have a class and pass encoding and decoding functions, where
405
- `encode` converts the class instance to a string, and
406
- `decode` takes the string and recreates the class instance.
407
- 3. Have a class that already has serialization methods, that satisfies the:
408
- EncodeFuncType = Callable[[Any], str]
409
- DecodeFuncType = Callable[[str], Any]
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
- `custom_encode_method`
412
- `custom_decode_method` attributes.
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
- def __init__(self, name, age):
436
- self.name = name
437
- self.age = age
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
- def encode(self) -> str:
440
- return json.dumps(self.__dict__)
441
-
442
- @classmethod
443
- def decode(cls, encoded_str: str) -> 'Person':
444
- return cls(**json.loads(encoded_str))
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
- redis_dict.extends_type(Person)
266
+ Raises:
267
+ NotImplementedError
447
268
 
448
269
  Note:
449
- You can check for compliance of a class separately using the `new_type_compliance` method:
270
+ You can check for compliance of a class separately using the `new_type_compliance` method:
450
271
 
451
- This method raises a NotImplementedError if either `encode` or `decode` is `None`
452
- and the class does not implement the corresponding method.
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, decode_method_name=decode_method_name)
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.iteritems():
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.iterkeys()
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, optional): A search term to filter keys. Defaults to ''.
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
- Return the value for the given key if it exists, otherwise return the default value.
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
- Optional[Any]: The value associated with the key or the default value.
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 iterkeys(self) -> Iterator[str]:
662
- """
663
- Note: for python2 str is needed
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
- Note: for python2 str is needed
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 keys(self) -> List[str]:
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
- def iteritems(self) -> Iterator[Tuple[str, Any]]:
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 items(self) -> List[Tuple[str, Any]]:
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
- Returns:
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
- def values(self) -> List[Any]:
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. Analogous to a dictionary's pop method.
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
- Optional[Any]: The value associated with the key or the default value.
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
- try:
772
- value = self[key]
773
- except KeyError:
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
- del self[key]
779
- return value
669
+ return self._transform(value)
780
670
 
781
671
  def popitem(self) -> Tuple[str, Any]:
782
- """
783
- Remove and return a random (key, value) pair from the RedisDict as a tuple.
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
- found, value = self._load(key)
814
- if not found:
815
- self[key] = default_value
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
- return value
720
+
721
+ return self._transform(result)
818
722
 
819
723
  def copy(self) -> Dict[str, Any]:
820
- """
821
- Create a shallow copy of the RedisDict and return it as a standard Python dictionary.
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
- Return the approximate size of the RedisDict in memory, in bytes.
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
- Returns:
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
- Returns:
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