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

Sign up to get free protection for your applications and to get access to all the features.
redis_dict/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ """__init__ module for redis dict."""
2
+ from importlib.metadata import version, PackageNotFoundError
3
+
4
+ from .core import RedisDict
5
+ from .type_management import decoding_registry, encoding_registry, RedisDictJSONEncoder, RedisDictJSONDecoder
6
+
7
+ __all__ = [
8
+ 'RedisDict',
9
+ 'decoding_registry',
10
+ 'encoding_registry',
11
+ 'RedisDictJSONEncoder',
12
+ 'RedisDictJSONDecoder',
13
+ ]
14
+ try:
15
+ __version__ = version("redis-dict")
16
+ except PackageNotFoundError:
17
+ __version__ = "0.0.0"
@@ -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
@@ -194,70 +43,35 @@ class RedisDict:
194
43
  expire (Union[int, None]): An optional expiration time for keys, in seconds.
195
44
 
196
45
  """
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
- }
46
+
47
+ encoding_registry: EncodeType = enc_reg
48
+ decoding_registry: DecodeType = dec_reg
241
49
 
242
50
  def __init__(self,
243
51
  namespace: str = 'main',
244
52
  expire: Union[int, timedelta, None] = None,
245
53
  preserve_expiration: Optional[bool] = False,
54
+ redis: "Optional[StrictRedis[Any]]" = None,
246
55
  **redis_kwargs: Any) -> None:
247
- """
248
- Initialize a RedisDict instance.
56
+ """Initialize a RedisDict instance.
57
+
58
+ Init the RedisDict instance.
249
59
 
250
60
  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.
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.
255
66
  """
256
67
 
257
68
  self.namespace: str = namespace
258
69
  self.expire: Union[int, timedelta, None] = expire
259
70
  self.preserve_expiration: Optional[bool] = preserve_expiration
260
- 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)
261
75
  self.get_redis: StrictRedis[Any] = self.redis
262
76
 
263
77
  self.custom_encode_method = "encode"
@@ -288,7 +102,7 @@ class RedisDict:
288
102
  length does not exceed the maximum allowed size (500 MB).
289
103
 
290
104
  Args:
291
- val (Union[str, int, float, bool]): The input value to be validated.
105
+ val (Any): The input value to be validated.
292
106
  val_type (str): The type of the input value ("str", "int", "float", or "bool").
293
107
 
294
108
  Returns:
@@ -298,7 +112,19 @@ class RedisDict:
298
112
  return len(val) < self._max_string_size
299
113
  return True
300
114
 
301
- def _format_value(self, key: str, value: Any) -> str:
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
+ """
302
128
  store_type, key = type(value).__name__, str(key)
303
129
  if not self._valid_input(value, store_type) or not self._valid_input(key, "str"):
304
130
  raise ValueError("Invalid input value or key size exceeded the maximum limit.")
@@ -355,7 +181,7 @@ class RedisDict:
355
181
  Any: The transformed Python object.
356
182
  """
357
183
  type_, value = result.split(':', 1)
358
- return self.decoding_registry.get(type_, lambda x: x)(value)
184
+ return self.decoding_registry.get(type_, _default_decoder)(value)
359
185
 
360
186
  def new_type_compliance(
361
187
  self,
@@ -363,8 +189,7 @@ class RedisDict:
363
189
  encode_method_name: Optional[str] = None,
364
190
  decode_method_name: Optional[str] = None,
365
191
  ) -> None:
366
- """
367
- 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.
368
193
 
369
194
  Args:
370
195
  class_type (type): The class to check for compliance.
@@ -386,6 +211,7 @@ class RedisDict:
386
211
  raise NotImplementedError(
387
212
  f"Class {class_type.__name__} does not implement the required {decode_method_name} class method.")
388
213
 
214
+ # pylint: disable=too-many-arguments
389
215
  def extends_type(
390
216
  self,
391
217
  class_type: type,
@@ -394,31 +220,21 @@ class RedisDict:
394
220
  encoding_method_name: Optional[str] = None,
395
221
  decoding_method_name: Optional[str] = None,
396
222
  ) -> None:
397
- """
398
- 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.
399
224
 
400
225
  This method enables serialization of instances based on their type,
401
226
  allowing for custom types, specialized storage formats, and more.
402
227
  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]
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]
410
235
 
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.
236
+ `custom_encode_method`
237
+ `custom_decode_method`
422
238
 
423
239
  If no encoding or decoding function is provided, default to use the `encode` and `decode` methods of the class.
424
240
 
@@ -445,11 +261,21 @@ class RedisDict:
445
261
 
446
262
  redis_dict.extends_type(Person)
447
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
+
448
274
  Note:
449
- 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:
450
276
 
451
- This method raises a NotImplementedError if either `encode` or `decode` is `None`
452
- 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.
453
279
  """
454
280
 
455
281
  if encode is None or decode is None:
@@ -460,7 +286,7 @@ class RedisDict:
460
286
 
461
287
  if decode is None:
462
288
  decode_method_name = decoding_method_name or self.custom_decode_method
463
- 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)
464
290
  decode = _create_default_decode(class_type, decode_method_name)
465
291
 
466
292
  type_name = class_type.__name__
@@ -479,7 +305,7 @@ class RedisDict:
479
305
  """
480
306
  if len(self) != len(other):
481
307
  return False
482
- for key, value in self.iteritems():
308
+ for key, value in self.items():
483
309
  if value != other.get(key, SENTINEL):
484
310
  return False
485
311
  return True
@@ -561,7 +387,7 @@ class RedisDict:
561
387
  Returns:
562
388
  Iterator[str]: An iterator over the keys of the RedisDict.
563
389
  """
564
- self._iter = self.iterkeys()
390
+ self._iter = self.keys()
565
391
  return self
566
392
 
567
393
  def __repr__(self) -> str:
@@ -582,15 +408,102 @@ class RedisDict:
582
408
  """
583
409
  return str(self.to_dict())
584
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
+
585
501
  def __next__(self) -> str:
586
502
  """
587
503
  Get the next item in the iterator.
588
504
 
589
505
  Returns:
590
506
  str: The next item in the iterator.
591
-
592
- Raises:
593
- StopIteration: If there are no more items.
594
507
  """
595
508
  return next(self._iter)
596
509
 
@@ -601,8 +514,6 @@ class RedisDict:
601
514
  Returns:
602
515
  str: The next item in the iterator.
603
516
 
604
- Raises:
605
- StopIteration: If there are no more items.
606
517
  """
607
518
  return next(self)
608
519
 
@@ -633,7 +544,7 @@ class RedisDict:
633
544
  Scan for Redis keys matching the given search term.
634
545
 
635
546
  Args:
636
- search_term (str, optional): A search term to filter keys. Defaults to ''.
547
+ search_term (str): A search term to filter keys. Defaults to ''.
637
548
 
638
549
  Returns:
639
550
  Iterator[str]: An iterator of matching Redis keys.
@@ -642,8 +553,8 @@ class RedisDict:
642
553
  return self.get_redis.scan_iter(match=search_query)
643
554
 
644
555
  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.
556
+ """Return the value for the given key if it exists, otherwise return the default value.
557
+
647
558
  Analogous to a dictionary's get method.
648
559
 
649
560
  Args:
@@ -651,23 +562,30 @@ class RedisDict:
651
562
  default (Optional[Any], optional): The value to return if the key is not found.
652
563
 
653
564
  Returns:
654
- Optional[Any]: The value associated with the key or the default value.
565
+ Any: The value associated with the key or the default value.
655
566
  """
656
567
  found, item = self._load(key)
657
568
  if not found:
658
569
  return default
659
570
  return item
660
571
 
661
- def iterkeys(self) -> Iterator[str]:
662
- """
663
- 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.
664
577
  """
665
578
  to_rm = len(self.namespace) + 1
666
579
  return (str(item[to_rm:]) for item in self._scan_keys())
667
580
 
668
581
  def key(self, search_term: str = '') -> Optional[str]:
669
- """
670
- 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.
671
589
  """
672
590
  to_rm = len(self.namespace) + 1
673
591
  search_query = self._create_iter_query(search_term)
@@ -677,18 +595,11 @@ class RedisDict:
677
595
 
678
596
  return None
679
597
 
680
- def keys(self) -> List[str]:
681
- """
682
- 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.
683
600
 
684
- Returns:
685
- List[str]: A list of keys in the RedisDict.
686
- """
687
- return list(self.iterkeys())
688
-
689
- def iteritems(self) -> Iterator[Tuple[str, Any]]:
690
- """
691
- Note: for python2 str is needed
601
+ Yields:
602
+ Iterator[Tuple[str, Any]]: A list of key-value pairs in the RedisDict.
692
603
  """
693
604
  to_rm = len(self.namespace) + 1
694
605
  for item in self._scan_keys():
@@ -697,31 +608,14 @@ class RedisDict:
697
608
  except KeyError:
698
609
  pass
699
610
 
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.
611
+ def values(self) -> Iterator[Any]:
612
+ """Analogous to a dictionary's values method.
703
613
 
704
- Returns:
705
- List[Tuple[str, Any]]: A list of key-value pairs in the RedisDict.
706
- """
707
- return list(self.iteritems())
614
+ Return a list of values in the RedisDict,
708
615
 
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:
616
+ Yields:
714
617
  List[Any]: A list of values in the RedisDict.
715
618
  """
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
619
  to_rm = len(self.namespace) + 1
726
620
  for item in self._scan_keys():
727
621
  try:
@@ -730,8 +624,7 @@ class RedisDict:
730
624
  pass
731
625
 
732
626
  def to_dict(self) -> Dict[str, Any]:
733
- """
734
- Convert the RedisDict to a Python dictionary.
627
+ """Convert the RedisDict to a Python dictionary.
735
628
 
736
629
  Returns:
737
630
  Dict[str, Any]: A dictionary with the same key-value pairs as the RedisDict.
@@ -739,8 +632,7 @@ class RedisDict:
739
632
  return dict(self.items())
740
633
 
741
634
  def clear(self) -> None:
742
- """
743
- 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.
744
636
 
745
637
  This method mimics the behavior of the `clear` method from a standard Python dictionary.
746
638
  Redis pipelining is employed to group multiple commands into a single request, minimizing
@@ -754,33 +646,33 @@ class RedisDict:
754
646
  del self[key]
755
647
 
756
648
  def pop(self, key: str, default: Union[Any, object] = SENTINEL) -> Any:
757
- """
649
+ """Analogous to a dictionary's pop method.
650
+
758
651
  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.
652
+ if the key is not found.
760
653
 
761
654
  Args:
762
655
  key (str): The key to remove the value.
763
656
  default (Optional[Any], optional): The value to return if the key is not found.
764
657
 
765
658
  Returns:
766
- Optional[Any]: The value associated with the key or the default value.
659
+ Any: The value associated with the key or the default value.
767
660
 
768
661
  Raises:
769
662
  KeyError: If the key is not found and no default value is provided.
770
663
  """
771
- try:
772
- value = self[key]
773
- except KeyError:
664
+ formatted_key = self._format_key(key)
665
+ value = self.get_redis.execute_command("GETDEL", formatted_key)
666
+ if value is None:
774
667
  if default is not SENTINEL:
775
668
  return default
776
- raise
669
+ raise KeyError(formatted_key)
777
670
 
778
- del self[key]
779
- return value
671
+ return self._transform(value)
780
672
 
781
673
  def popitem(self) -> Tuple[str, Any]:
782
- """
783
- 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
+
784
676
  This method is analogous to the `popitem` method of a standard Python dictionary.
785
677
 
786
678
  Returns:
@@ -799,7 +691,8 @@ class RedisDict:
799
691
  continue
800
692
 
801
693
  def setdefault(self, key: str, default_value: Optional[Any] = None) -> Any:
802
- """
694
+ """Get value under key, and if not present set default value.
695
+
803
696
  Return the value associated with the given key if it exists, otherwise set the value to the
804
697
  default value and return it. Analogous to a dictionary's setdefault method.
805
698
 
@@ -810,15 +703,28 @@ class RedisDict:
810
703
  Returns:
811
704
  Any: The value associated with the key or the default value.
812
705
  """
813
- found, value = self._load(key)
814
- if not found:
815
- 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:
816
721
  return default_value
817
- return value
722
+
723
+ return self._transform(result)
818
724
 
819
725
  def copy(self) -> Dict[str, Any]:
820
- """
821
- 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
+
822
728
  This method is analogous to the `copy` method of a standard Python dictionary
823
729
 
824
730
  Returns:
@@ -841,7 +747,8 @@ class RedisDict:
841
747
  self[key] = value
842
748
 
843
749
  def fromkeys(self, iterable: List[str], value: Optional[Any] = None) -> 'RedisDict':
844
- """
750
+ """Create a new RedisDict from an iterable of key-value pairs.
751
+
845
752
  Create a new RedisDict with keys from the provided iterable and values set to the given value.
846
753
  This method is analogous to the `fromkeys` method of a standard Python dictionary, populating
847
754
  the RedisDict with the keys from the iterable and setting their corresponding values to the
@@ -861,10 +768,11 @@ class RedisDict:
861
768
  return self
862
769
 
863
770
  def __sizeof__(self) -> int:
864
- """
865
- Return the approximate size of the RedisDict in memory, in bytes.
771
+ """Return the approximate size of the RedisDict in memory, in bytes.
772
+
866
773
  This method is analogous to the `__sizeof__` method of a standard Python dictionary, estimating
867
774
  the memory consumption of the RedisDict based on the serialized in-memory representation.
775
+ Should be changed to redis view of the size.
868
776
 
869
777
  Returns:
870
778
  int: The approximate size of the RedisDict in memory, in bytes.
@@ -906,13 +814,12 @@ class RedisDict:
906
814
  # compatibility with Python 3.9 typing
907
815
  @contextmanager
908
816
  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.
817
+ """Context manager to set the expiration time for keys in the RedisDict.
911
818
 
912
819
  Args:
913
820
  sec_epoch (int, timedelta): The expiration duration is set using either an integer or a timedelta.
914
821
 
915
- Returns:
822
+ Yields:
916
823
  ContextManager: A context manager during which the expiration time is the time set.
917
824
  """
918
825
  self.expire, temp = sec_epoch, self.expire
@@ -924,7 +831,7 @@ class RedisDict:
924
831
  """
925
832
  Context manager to create a Redis pipeline for batch operations.
926
833
 
927
- Returns:
834
+ Yields:
928
835
  ContextManager: A context manager to create a Redis pipeline batching all operations within the context.
929
836
  """
930
837
  top_level = False
@@ -1006,7 +913,8 @@ class RedisDict:
1006
913
  return dict(self.redis.info())
1007
914
 
1008
915
  def get_ttl(self, key: str) -> Optional[int]:
1009
- """
916
+ """Get the Time To Live from Redis.
917
+
1010
918
  Get the Time To Live (TTL) in seconds for a given key. If the key does not exist or does not have an
1011
919
  associated `expire`, return None.
1012
920
 
redis_dict/py.typed ADDED
File without changes
@@ -0,0 +1,273 @@
1
+ """Type management module."""
2
+
3
+ import json
4
+ import base64
5
+ from collections import OrderedDict, defaultdict
6
+ from datetime import datetime, date, time, timedelta
7
+
8
+ from typing import Callable, Any, Dict, Tuple, Set
9
+
10
+ from uuid import UUID
11
+ from decimal import Decimal
12
+
13
+
14
+ SENTINEL = object()
15
+
16
+ EncodeFuncType = Callable[[Any], str]
17
+ DecodeFuncType = Callable[[str], Any]
18
+ EncodeType = Dict[str, EncodeFuncType]
19
+ DecodeType = Dict[str, DecodeFuncType]
20
+
21
+
22
+ def _create_default_encode(custom_encode_method: str) -> EncodeFuncType:
23
+ def default_encode(obj: Any) -> str:
24
+ return getattr(obj, custom_encode_method)() # type: ignore[no-any-return]
25
+ return default_encode
26
+
27
+
28
+ def _create_default_decode(cls: type, custom_decode_method: str) -> DecodeFuncType:
29
+ def default_decode(encoded_str: str) -> Any:
30
+ return getattr(cls, custom_decode_method)(encoded_str)
31
+ return default_decode
32
+
33
+
34
+ def _decode_tuple(val: str) -> Tuple[Any, ...]:
35
+ """
36
+ Deserialize a JSON-formatted string to a tuple.
37
+
38
+ This function takes a JSON-formatted string, deserializes it to a list, and
39
+ then converts the list to a tuple.
40
+
41
+ Args:
42
+ val (str): A JSON-formatted string representing a list.
43
+
44
+ Returns:
45
+ Tuple[Any, ...]: A tuple with the deserialized values from the input string.
46
+ """
47
+ return tuple(json.loads(val))
48
+
49
+
50
+ def _encode_tuple(val: Tuple[Any, ...]) -> str:
51
+ """
52
+ Serialize a tuple to a JSON-formatted string.
53
+
54
+ This function takes a tuple, converts it to a list, and then serializes
55
+ the list to a JSON-formatted string.
56
+
57
+ Args:
58
+ val (Tuple[Any, ...]): A tuple with values to be serialized.
59
+
60
+ Returns:
61
+ str: A JSON-formatted string representing the input tuple.
62
+ """
63
+ return json.dumps(list(val))
64
+
65
+
66
+ def _decode_set(val: str) -> Set[Any]:
67
+ """
68
+ Deserialize a JSON-formatted string to a set.
69
+
70
+ This function takes a JSON-formatted string, deserializes it to a list, and
71
+ then converts the list to a set.
72
+
73
+ Args:
74
+ val (str): A JSON-formatted string representing a list.
75
+
76
+ Returns:
77
+ set[Any]: A set with the deserialized values from the input string.
78
+ """
79
+ return set(json.loads(val))
80
+
81
+
82
+ def _encode_set(val: Set[Any]) -> str:
83
+ """
84
+ Serialize a set to a JSON-formatted string.
85
+
86
+ This function takes a set, converts it to a list, and then serializes the
87
+ list to a JSON-formatted string.
88
+
89
+ Args:
90
+ val (set[Any]): A set with values to be serialized.
91
+
92
+ Returns:
93
+ str: A JSON-formatted string representing the input set.
94
+ """
95
+ return json.dumps(list(val))
96
+
97
+
98
+ decoding_registry: DecodeType = {
99
+ type('').__name__: str,
100
+ type(1).__name__: int,
101
+ type(0.1).__name__: float,
102
+ type(True).__name__: lambda x: x == "True",
103
+ type(None).__name__: lambda x: None,
104
+
105
+ "list": json.loads,
106
+ "dict": json.loads,
107
+ "tuple": _decode_tuple,
108
+ type(set()).__name__: _decode_set,
109
+
110
+ datetime.__name__: datetime.fromisoformat,
111
+ date.__name__: date.fromisoformat,
112
+ time.__name__: time.fromisoformat,
113
+ timedelta.__name__: lambda x: timedelta(seconds=float(x)),
114
+
115
+ Decimal.__name__: Decimal,
116
+ complex.__name__: lambda x: complex(*map(float, x.split(','))),
117
+ bytes.__name__: base64.b64decode,
118
+
119
+ UUID.__name__: UUID,
120
+ OrderedDict.__name__: lambda x: OrderedDict(json.loads(x)),
121
+ defaultdict.__name__: lambda x: defaultdict(type(None), json.loads(x)),
122
+ frozenset.__name__: lambda x: frozenset(json.loads(x)),
123
+ }
124
+
125
+
126
+ encoding_registry: EncodeType = {
127
+ "list": json.dumps,
128
+ "dict": json.dumps,
129
+ "tuple": _encode_tuple,
130
+ type(set()).__name__: _encode_set,
131
+
132
+ datetime.__name__: datetime.isoformat,
133
+ date.__name__: date.isoformat,
134
+ time.__name__: time.isoformat,
135
+ timedelta.__name__: lambda x: str(x.total_seconds()),
136
+
137
+ complex.__name__: lambda x: f"{x.real},{x.imag}",
138
+ bytes.__name__: lambda x: base64.b64encode(x).decode('ascii'),
139
+ OrderedDict.__name__: lambda x: json.dumps(list(x.items())),
140
+ defaultdict.__name__: lambda x: json.dumps(dict(x)),
141
+ frozenset.__name__: lambda x: json.dumps(list(x)),
142
+ }
143
+
144
+
145
+ class RedisDictJSONEncoder(json.JSONEncoder):
146
+ """Extends JSON encoding capabilities by reusing RedisDict type conversion.
147
+
148
+ Uses existing decoding_registry to know which types to handle specially and
149
+ encoding_registry (falls back to str) for converting to JSON-compatible formats.
150
+
151
+ Example:
152
+ The encoded format looks like::
153
+
154
+ {
155
+ "__type__": "TypeName",
156
+ "value": <encoded value>
157
+ }
158
+
159
+ Notes:
160
+
161
+ Uses decoding_registry (containing all supported types) to check if type
162
+ needs special handling. For encoding, defaults to str() if no encoder exists
163
+ in encoding_registry.
164
+ """
165
+ def default(self, o: Any) -> Any:
166
+ """Overwrite default from json encoder.
167
+
168
+ Args:
169
+ o (Any): Object to be serialized.
170
+
171
+ Raises:
172
+ TypeError: If the object `o` cannot be serialized.
173
+
174
+ Returns:
175
+ Any: Serialized value.
176
+ """
177
+ type_name = type(o).__name__
178
+ if type_name in decoding_registry:
179
+ return {
180
+ "__type__": type_name,
181
+ "value": encoding_registry.get(type_name, _default_encoder)(o)
182
+ }
183
+ try:
184
+ return json.JSONEncoder.default(self, o)
185
+ except TypeError as e:
186
+ raise TypeError(f"Object of type {type_name} is not JSON serializable") from e
187
+
188
+
189
+ class RedisDictJSONDecoder(json.JSONDecoder):
190
+ """JSON decoder leveraging RedisDict existing type conversion system.
191
+
192
+ Works with RedisDictJSONEncoder to reconstruct Python objects from JSON using
193
+ RedisDict decoding_registry.
194
+
195
+ Still needs work but allows for more types than without.
196
+ """
197
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
198
+ """
199
+ Overwrite the __init__ method from JSON decoder.
200
+
201
+ Args:
202
+ *args (Any): Positional arguments for initialization.
203
+ **kwargs (Any): Keyword arguments for initialization.
204
+
205
+ """
206
+ def _object_hook(obj: Dict[Any, Any]) -> Any:
207
+ if "__type__" in obj and "value" in obj:
208
+ type_name = obj["__type__"]
209
+ if type_name in decoding_registry:
210
+ return decoding_registry[type_name](obj["value"])
211
+ return obj
212
+
213
+ super().__init__(object_hook=_object_hook, *args, **kwargs)
214
+
215
+
216
+ def encode_json(obj: Any) -> str:
217
+ """
218
+ Encode a Python object to a JSON string using the existing encoding registry.
219
+
220
+ Args:
221
+ obj (Any): The Python object to be encoded.
222
+
223
+ Returns:
224
+ str: The JSON-encoded string representation of the object.
225
+ """
226
+ return json.dumps(obj, cls=RedisDictJSONEncoder)
227
+
228
+
229
+ def decode_json(s: str) -> Any:
230
+ """
231
+ Decode a JSON string to a Python object using the existing decoding registry.
232
+
233
+ Args:
234
+ s (str): The JSON string to be decoded.
235
+
236
+ Returns:
237
+ Any: The decoded Python object.
238
+ """
239
+ return json.loads(s, cls=RedisDictJSONDecoder)
240
+
241
+
242
+ def _default_decoder(x: str) -> str:
243
+ """
244
+ Pass-through decoder that returns the input string unchanged.
245
+
246
+ Args:
247
+ x (str): The input string.
248
+
249
+ Returns:
250
+ str: The same input string.
251
+ """
252
+ return x
253
+
254
+
255
+ def _default_encoder(x: Any) -> str:
256
+ """
257
+ Takes x and returns the result str of the object.
258
+
259
+ Args:
260
+ x (Any): The input object
261
+
262
+ Returns:
263
+ str: output of str of the object
264
+ """
265
+ return str(x)
266
+
267
+
268
+ encoding_registry["dict"] = encode_json
269
+ decoding_registry["dict"] = decode_json
270
+
271
+
272
+ encoding_registry["list"] = encode_json
273
+ decoding_registry["list"] = decode_json
@@ -1,13 +1,14 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: redis-dict
3
- Version: 2.7.0
3
+ Version: 3.0.0
4
4
  Summary: Dictionary with Redis as storage backend
5
- Home-page: https://github.com/Attumm/redisdict
6
- Author: Melvin Bijman
7
- Author-email: bijman.m.m@gmail.com
5
+ Author-email: Melvin Bijman <bijman.m.m@gmail.com>
8
6
  License: MIT
9
- Keywords: redis python dictionary dict key-value key:value database caching distributed-computing dictionary-interface large-datasets scientific-computing data-persistence high-performance scalable pipelining batching big-data data-types distributed-algorithms encryption data-management
10
- Platform: any
7
+ Project-URL: Homepage, https://github.com/Attumm/redisdict
8
+ Project-URL: Documentation, https://github.com/Attumm/redisdict#readme
9
+ Project-URL: Repository, https://github.com/Attumm/redisdict.git
10
+ Project-URL: Changelog, https://github.com/Attumm/redisdict/releases
11
+ Keywords: redis,python,dictionary,dict,key-value,database,caching,distributed-computing,dictionary-interface,large-datasets,scientific-computing,data-persistence,high-performance,scalable,pipelining,batching,big-data,data-types,distributed-algorithms,encryption,data-management
11
12
  Classifier: Development Status :: 5 - Production/Stable
12
13
  Classifier: Intended Audience :: Developers
13
14
  Classifier: Intended Audience :: Information Technology
@@ -21,18 +22,51 @@ Classifier: Topic :: Software Development :: Object Brokering
21
22
  Classifier: Topic :: Database :: Database Engines/Servers
22
23
  Classifier: License :: OSI Approved :: MIT License
23
24
  Classifier: Programming Language :: Python :: 3
24
- Classifier: Programming Language :: Python :: 3.6
25
- Classifier: Programming Language :: Python :: 3.7
26
25
  Classifier: Programming Language :: Python :: 3.8
27
26
  Classifier: Programming Language :: Python :: 3.9
28
27
  Classifier: Programming Language :: Python :: 3.10
29
28
  Classifier: Programming Language :: Python :: 3.11
30
29
  Classifier: Programming Language :: Python :: 3.12
30
+ Classifier: Typing :: Typed
31
+ Requires-Python: >=3.8
31
32
  Description-Content-Type: text/markdown
32
33
  License-File: LICENSE
33
- Requires-Dist: redis
34
+ Requires-Dist: redis>=4.0.0
35
+ Provides-Extra: dev
36
+ Requires-Dist: coverage==5.5; extra == "dev"
37
+ Requires-Dist: hypothesis==6.70.1; extra == "dev"
38
+ Requires-Dist: mypy>=1.8.0; extra == "dev"
39
+ Requires-Dist: mypy-extensions>=1.0.0; extra == "dev"
40
+ Requires-Dist: types-pyOpenSSL>=24.0.0.0; extra == "dev"
41
+ Requires-Dist: types-redis>=4.6.0; extra == "dev"
42
+ Requires-Dist: typing-extensions>=4.5.0; extra == "dev"
43
+ Requires-Dist: pylama>=8.4.1; extra == "dev"
44
+ Requires-Dist: pycodestyle==2.10.0; extra == "dev"
45
+ Requires-Dist: pydocstyle==6.3.0; extra == "dev"
46
+ Requires-Dist: pyflakes==3.0.1; extra == "dev"
47
+ Requires-Dist: pylint==3.2.7; extra == "dev"
48
+ Requires-Dist: mccabe==0.7.0; extra == "dev"
49
+ Requires-Dist: attrs==22.2.0; extra == "dev"
50
+ Requires-Dist: cffi==1.15.1; extra == "dev"
51
+ Requires-Dist: cryptography==43.0.1; extra == "dev"
52
+ Requires-Dist: exceptiongroup==1.1.1; extra == "dev"
53
+ Requires-Dist: future==0.18.3; extra == "dev"
54
+ Requires-Dist: pycparser==2.21; extra == "dev"
55
+ Requires-Dist: snowballstemmer==2.2.0; extra == "dev"
56
+ Requires-Dist: sortedcontainers==2.4.0; extra == "dev"
57
+ Requires-Dist: tomli==2.0.1; extra == "dev"
58
+ Requires-Dist: setuptools>=68.0.0; extra == "dev"
59
+ Requires-Dist: darglint; extra == "dev"
60
+ Requires-Dist: pydocstyle; extra == "dev"
61
+ Provides-Extra: docs
62
+ Requires-Dist: sphinx; extra == "docs"
63
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
64
+ Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
65
+ Requires-Dist: tomli; extra == "docs"
66
+ Requires-Dist: myst-parser; extra == "docs"
34
67
 
35
68
  # Redis-dict
69
+ [![PyPI](https://img.shields.io/pypi/v/redis-dict.svg)](https://pypi.org/project/redis-dict/)
36
70
  [![CI](https://github.com/Attumm/redis-dict/actions/workflows/ci.yml/badge.svg)](https://github.com/Attumm/redis-dict/actions/workflows/ci.yml)
37
71
  [![codecov](https://codecov.io/gh/Attumm/redis-dict/graph/badge.svg?token=Lqs7McQGEs)](https://codecov.io/gh/Attumm/redis-dict)
38
72
  [![Downloads](https://static.pepy.tech/badge/redis-dict/month)](https://pepy.tech/project/redis-dict)
@@ -86,7 +120,6 @@ In Redis our example looks like this.
86
120
 
87
121
  ### Namespaces
88
122
  Acting as an identifier for your dictionary across different systems, RedisDict employs namespaces for organized data management. When a namespace isn't specified, "main" becomes the default. Thus allowing for data organization across systems and projects with the same redis instance.
89
-
90
123
  This approach also minimizes the risk of key collisions between different applications, preventing hard-to-debug issues. By leveraging namespaces, RedisDict ensures a cleaner and more maintainable data management experience for developers working on multiple projects.
91
124
 
92
125
  ## Advanced Features
@@ -135,7 +168,6 @@ dic['gone'] = 'gone in 5 seconds'
135
168
  Efficiently batch your requests using the Pipeline feature, which can be easily utilized with a context manager.
136
169
 
137
170
  ```python
138
- from redis_dict import RedisDict
139
171
  dic = RedisDict(namespace="example")
140
172
 
141
173
  # one round trip to redis
@@ -263,14 +295,11 @@ This approach optimizes Redis database performance and efficiency by ensuring th
263
295
  Following types are supported:
264
296
  `str, int, float, bool, NoneType, list, dict, tuple, set, datetime, date, time, timedelta, Decimal, complex, bytes, UUID, OrderedDict, defaultdict, frozenset`
265
297
  ```python
266
- from redis_dict import RedisDict
267
-
268
298
  from uuid import UUID
269
299
  from decimal import Decimal
270
300
  from collections import OrderedDict, defaultdict
271
301
  from datetime import datetime, date, time, timedelta
272
302
 
273
-
274
303
  dic = RedisDict()
275
304
 
276
305
  dic["string"] = "Hello World"
@@ -299,6 +328,32 @@ dic["default"] = defaultdict(int, {'a': 1, 'b': 2})
299
328
  dic["frozen"] = frozenset([1, 2, 3])
300
329
  ```
301
330
 
331
+
332
+
333
+ ### Nested types
334
+ Nested Types
335
+ RedisDict supports nested structures with mixed types through JSON serialization. The feature works by utilizing JSON encoding and decoding under the hood. While this represents an upgrade in functionality, the feature is not fully implemented and should be used with caution. For optimal performance, using shallow dictionaries is recommended.
336
+ ```python
337
+ from datetime import datetime, timedelta
338
+
339
+ dic["mixed"] = [1, "foobar", 3.14, [1, 2, 3], datetime.now()]
340
+
341
+ dic['dic'] = {"elapsed_time": timedelta(hours=60)}
342
+ ```
343
+
344
+ ### JSON Encoding - Decoding
345
+ The nested type support in RedisDict is implemented using custom JSON encoders and decoders. These JSON encoders and decoders are built on top of RedisDict's own encoding and decoding functionality, extending it for JSON compatibility. Since JSON serialization was a frequently requested feature, these enhanced encoders and decoders are available for use in other projects:
346
+ ```python
347
+ import json
348
+ from datetime import datetime
349
+ from redis_dict import RedisDictJSONDecoder, RedisDictJSONEncoder
350
+
351
+ data = [1, "foobar", 3.14, [1, 2, 3], datetime.now()]
352
+ encoded = json.dumps(data, cls=RedisDictJSONEncoder)
353
+ result = json.loads(encoded, cls=RedisDictJSONDecoder)
354
+ ```
355
+
356
+
302
357
  ### Extending RedisDict with Custom Types
303
358
 
304
359
  RedisDict supports custom type serialization. Here's how to add a new type:
@@ -306,7 +361,6 @@ RedisDict supports custom type serialization. Here's how to add a new type:
306
361
 
307
362
  ```python
308
363
  import json
309
- from redis_dict import RedisDict
310
364
 
311
365
  class Person:
312
366
  def __init__(self, name, age):
@@ -335,23 +389,13 @@ assert result.name == person.name
335
389
  assert result.age == person.age
336
390
  ```
337
391
 
338
- ```python
339
- >>> from datetime import datetime
340
- >>> redis_dict.extends_type(datetime, datetime.isoformat, datetime.fromisoformat)
341
- >>> redis_dict["now"] = datetime.now()
342
- >>> redis_dict
343
- {'now': datetime.datetime(2024, 10, 14, 18, 41, 53, 493775)}
344
- >>> redis_dict["now"]
345
- datetime.datetime(2024, 10, 14, 18, 41, 53, 493775)
346
- ```
347
-
348
- For more information on [extending types](https://github.com/Attumm/redis-dict/blob/main/extend_types_tests.py).
392
+ For more information on [extending types](https://github.com/Attumm/redis-dict/blob/main/tests/unit/extend_types_tests.py).
349
393
  ### Redis Encryption
350
394
  Setup guide for configuring and utilizing encrypted Redis TLS for redis-dict.
351
- [Setup guide](https://github.com/Attumm/redis-dict/blob/main/encrypted_redis.MD)
395
+ [Setup guide](https://github.com/Attumm/redis-dict/blob/main/docs/tutorials/encrypted_redis.MD)
352
396
 
353
397
  ### Redis Storage Encryption
354
- For storing encrypted data values, it's possible to use extended types. Take a look at this [encrypted test](https://github.com/Attumm/redis-dict/blob/main/encrypt_tests.py).
398
+ For storing encrypted data values, it's possible to use extended types. Take a look at this [encrypted test](https://github.com/Attumm/redis-dict/blob/main/tests/unit/encrypt_tests.py).
355
399
 
356
400
  ### Tests
357
401
  The RedisDict library includes a comprehensive suite of tests that ensure its correctness and resilience. The test suite covers various data types, edge cases, and error handling scenarios. It also employs the Hypothesis library for property-based testing, which provides fuzz testing to evaluate the implementation
@@ -359,19 +403,16 @@ The RedisDict library includes a comprehensive suite of tests that ensure its co
359
403
  ### Redis config
360
404
  To configure RedisDict using your Redis config.
361
405
 
362
- Configure both the host and port.
406
+ Configure both the host and port. Or configuration with a setting dictionary.
363
407
  ```python
364
408
  dic = RedisDict(host='127.0.0.1', port=6380)
365
- ```
366
409
 
367
- Configuration with a dictionary.
368
- ```python
369
410
  redis_config = {
370
411
  'host': '127.0.0.1',
371
412
  'port': 6380,
372
413
  }
373
414
 
374
- dic = RedisDict(**redis_config)
415
+ confid_dic = RedisDict(**redis_config)
375
416
  ```
376
417
 
377
418
  ## Installation
@@ -382,4 +423,3 @@ pip install redis-dict
382
423
  ### Note
383
424
  * Please be aware that this project is currently being utilized by various organizations in their production environments. If you have any questions or concerns, feel free to raise issues
384
425
  * This project only uses redis as dependency
385
-
@@ -0,0 +1,9 @@
1
+ redis_dict/__init__.py,sha256=fksonUr5DetzwFDEkT7lpmAaV3Jhmp2IQ12t62LwFb4,476
2
+ redis_dict/core.py,sha256=iLVTzpR4HmMPqcgZQWMgdAgwRepLEhTbdxP-tfA13ts,34698
3
+ redis_dict/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ redis_dict/type_management.py,sha256=U3aP_EtHByApRdHvpr-zSOjok6r9BVZ0g3YnpVCVt8Y,7690
5
+ redis_dict-3.0.0.dist-info/LICENSE,sha256=-QiLwYznh_vNUSz337k0faP9Jl0dgtCIHVZ39Uyl6cA,1070
6
+ redis_dict-3.0.0.dist-info/METADATA,sha256=8Zn6a75THLjxiCGfRdFuz675RwSsLrBf0JQE8fH-Kfo,16873
7
+ redis_dict-3.0.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
8
+ redis_dict-3.0.0.dist-info/top_level.txt,sha256=Wyp5Xvq_imoxvu-c-Le1rbTZ3pYM5BF440H9YAcgBZ8,11
9
+ redis_dict-3.0.0.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- redis_dict.py,sha256=LmP5C1lIf3KCG_3pfcnw3CZE5KuUUym2U_UqlbFoiVg,38214
2
- redis_dict-2.7.0.dist-info/LICENSE,sha256=-QiLwYznh_vNUSz337k0faP9Jl0dgtCIHVZ39Uyl6cA,1070
3
- redis_dict-2.7.0.dist-info/METADATA,sha256=8SDlje1sUiquUlKvk27LHRhLTsdwr9b5KTsKBa5XhGk,14300
4
- redis_dict-2.7.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
5
- redis_dict-2.7.0.dist-info/top_level.txt,sha256=Wyp5Xvq_imoxvu-c-Le1rbTZ3pYM5BF440H9YAcgBZ8,11
6
- redis_dict-2.7.0.dist-info/RECORD,,