redis-dict 2.6.0__py3-none-any.whl → 3.0.0__py3-none-any.whl

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