redis-dict 3.1.2__tar.gz → 3.2.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: redis-dict
3
- Version: 3.1.2
3
+ Version: 3.2.0
4
4
  Summary: Dictionary with Redis as storage backend
5
5
  Author-email: Melvin Bijman <bijman.m.m@gmail.com>
6
6
  License: MIT
@@ -122,6 +122,48 @@ In Redis our example looks like this.
122
122
  "str:hello world"
123
123
  ```
124
124
 
125
+ ## Types
126
+
127
+ ### standard types
128
+ RedisDict supports a range of Python data types, from basic types to nested structures.
129
+ Basic types are handled natively, while complex data types like lists and dictionaries, RedisDict uses JSON serialization, specifically avoiding [pickle](https://docs.python.org/3/library/pickle.html) due to its security vulnerabilities within distributed computing contexts.
130
+ Although the library supports nested structures, the recommended best practice is to use RedisDict as a shallow dictionary.
131
+ This approach optimizes Redis database performance and efficiency by ensuring that each set and get operation efficiently maps to Redis's key-value storage capabilities, while still preserving the library's Pythonic interface.
132
+ Following types are supported:
133
+ `str, int, float, bool, NoneType, list, dict, tuple, set, datetime, date, time, timedelta, Decimal, complex, bytes, UUID, OrderedDict, defaultdict, frozenset`
134
+ ```python
135
+ from uuid import UUID
136
+ from decimal import Decimal
137
+ from collections import OrderedDict, defaultdict
138
+ from datetime import datetime, date, time, timedelta
139
+
140
+ dic = RedisDict()
141
+
142
+ dic["string"] = "Hello World"
143
+ dic["number"] = 42
144
+ dic["float"] = 3.14
145
+ dic["bool"] = True
146
+ dic["None"] = None
147
+
148
+ dic["list"] = [1, 2, 3]
149
+ dic["dict"] = {"a": 1, "b": 2}
150
+ dic["tuple"] = (1, 2, 3)
151
+ dic["set"] = {1, 2, 3}
152
+
153
+ dic["datetime"] = datetime.date(2024, 1, 1, 12, 30, 45)
154
+ dic["date"] = date(2024, 1, 1)
155
+ dic["time"] = time(12, 30, 45)
156
+ dic["delta"] = timedelta(days=1, hours=2)
157
+
158
+ dic["decimal"] = Decimal("3.14159")
159
+ dic["complex"] = complex(1, 2)
160
+ dic["bytes"] = bytes([72, 101, 108, 108, 111])
161
+ dic["uuid"] = UUID('12345678-1234-5678-1234-567812345678')
162
+
163
+ dic["ordered"] = OrderedDict([('a', 1), ('b', 2)])
164
+ dic["default"] = defaultdict(int, {'a': 1, 'b': 2})
165
+ dic["frozen"] = frozenset([1, 2, 3])
166
+ ```
125
167
 
126
168
  ### Namespaces
127
169
  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.
@@ -290,51 +332,6 @@ print(dic["d"]) # Output: 4
290
332
  For more advanced examples of RedisDict, please refer to the unit-test files in the repository. All features and functionalities are thoroughly tested in [unit tests (here)](https://github.com/Attumm/redis-dict/blob/main/tests/unit/tests.py#L1) Or take a look at load test for batching [load test](https://github.com/Attumm/redis-dict/blob/main/tests/load/load_test.py#L1).
291
333
  The unit-tests can be as used as a starting point.
292
334
 
293
- ## Types
294
-
295
- ### standard types
296
- RedisDict supports a range of Python data types, from basic types to nested structures.
297
- Basic types are handled natively, while complex data types like lists and dictionaries, RedisDict uses JSON serialization, specifically avoiding `pickle` due to its [security vulnerabilities](https://docs.python.org/3/library/pickle.html) in distributed computing contexts.
298
- Although the library supports nested structures, the recommended best practice is to use RedisDict as a shallow dictionary.
299
- This approach optimizes Redis database performance and efficiency by ensuring that each set and get operation efficiently maps to Redis's key-value storage capabilities, while still preserving the library's Pythonic interface.
300
- Following types are supported:
301
- `str, int, float, bool, NoneType, list, dict, tuple, set, datetime, date, time, timedelta, Decimal, complex, bytes, UUID, OrderedDict, defaultdict, frozenset`
302
- ```python
303
- from uuid import UUID
304
- from decimal import Decimal
305
- from collections import OrderedDict, defaultdict
306
- from datetime import datetime, date, time, timedelta
307
-
308
- dic = RedisDict()
309
-
310
- dic["string"] = "Hello World"
311
- dic["number"] = 42
312
- dic["float"] = 3.14
313
- dic["bool"] = True
314
- dic["None"] = None
315
-
316
- dic["list"] = [1, 2, 3]
317
- dic["dict"] = {"a": 1, "b": 2}
318
- dic["tuple"] = (1, 2, 3)
319
- dic["set"] = {1, 2, 3}
320
-
321
- dic["datetime"] = datetime.date(2024, 1, 1, 12, 30, 45)
322
- dic["date"] = date(2024, 1, 1)
323
- dic["time"] = time(12, 30, 45)
324
- dic["delta"] = timedelta(days=1, hours=2)
325
-
326
- dic["decimal"] = Decimal("3.14159")
327
- dic["complex"] = complex(1, 2)
328
- dic["bytes"] = bytes([72, 101, 108, 108, 111])
329
- dic["uuid"] = UUID('12345678-1234-5678-1234-567812345678')
330
-
331
- dic["ordered"] = OrderedDict([('a', 1), ('b', 2)])
332
- dic["default"] = defaultdict(int, {'a': 1, 'b': 2})
333
- dic["frozen"] = frozenset([1, 2, 3])
334
- ```
335
-
336
-
337
-
338
335
  ### Nested types
339
336
  Nested Types
340
337
  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.
@@ -394,6 +391,21 @@ assert result.name == person.name
394
391
  assert result.age == person.age
395
392
  ```
396
393
 
394
+ ### Insertion Order
395
+ For insertion order, use the PythonRedisDict. This class is focused on Python dictionary behavior one-to-one.
396
+ It will eventually become a drop-in replacement for dictionary. Currently, nested types and typed keys are not yet supported but will be added in the future.
397
+
398
+ ```python
399
+ from redis_dict import PythonRedisDict
400
+
401
+ dic = PythonRedisDict()
402
+ dic["1"] = "one"
403
+ dic["2"] = "two"
404
+ dic["3"] = "three"
405
+
406
+ assert list(dic.keys()) == ["1", "2", "3"]
407
+ ```
408
+
397
409
  For more information on [extending types](https://github.com/Attumm/redis-dict/blob/main/tests/unit/extend_types_tests.py).
398
410
  ### Redis Encryption
399
411
  Setup guide for configuring and utilizing encrypted Redis TLS for redis-dict.
@@ -417,7 +429,7 @@ redis_config = {
417
429
  'port': 6380,
418
430
  }
419
431
 
420
- confid_dic = RedisDict(**redis_config)
432
+ config_dic = RedisDict(**redis_config)
421
433
  ```
422
434
 
423
435
  ## Installation
@@ -50,6 +50,48 @@ In Redis our example looks like this.
50
50
  "str:hello world"
51
51
  ```
52
52
 
53
+ ## Types
54
+
55
+ ### standard types
56
+ RedisDict supports a range of Python data types, from basic types to nested structures.
57
+ Basic types are handled natively, while complex data types like lists and dictionaries, RedisDict uses JSON serialization, specifically avoiding [pickle](https://docs.python.org/3/library/pickle.html) due to its security vulnerabilities within distributed computing contexts.
58
+ Although the library supports nested structures, the recommended best practice is to use RedisDict as a shallow dictionary.
59
+ This approach optimizes Redis database performance and efficiency by ensuring that each set and get operation efficiently maps to Redis's key-value storage capabilities, while still preserving the library's Pythonic interface.
60
+ Following types are supported:
61
+ `str, int, float, bool, NoneType, list, dict, tuple, set, datetime, date, time, timedelta, Decimal, complex, bytes, UUID, OrderedDict, defaultdict, frozenset`
62
+ ```python
63
+ from uuid import UUID
64
+ from decimal import Decimal
65
+ from collections import OrderedDict, defaultdict
66
+ from datetime import datetime, date, time, timedelta
67
+
68
+ dic = RedisDict()
69
+
70
+ dic["string"] = "Hello World"
71
+ dic["number"] = 42
72
+ dic["float"] = 3.14
73
+ dic["bool"] = True
74
+ dic["None"] = None
75
+
76
+ dic["list"] = [1, 2, 3]
77
+ dic["dict"] = {"a": 1, "b": 2}
78
+ dic["tuple"] = (1, 2, 3)
79
+ dic["set"] = {1, 2, 3}
80
+
81
+ dic["datetime"] = datetime.date(2024, 1, 1, 12, 30, 45)
82
+ dic["date"] = date(2024, 1, 1)
83
+ dic["time"] = time(12, 30, 45)
84
+ dic["delta"] = timedelta(days=1, hours=2)
85
+
86
+ dic["decimal"] = Decimal("3.14159")
87
+ dic["complex"] = complex(1, 2)
88
+ dic["bytes"] = bytes([72, 101, 108, 108, 111])
89
+ dic["uuid"] = UUID('12345678-1234-5678-1234-567812345678')
90
+
91
+ dic["ordered"] = OrderedDict([('a', 1), ('b', 2)])
92
+ dic["default"] = defaultdict(int, {'a': 1, 'b': 2})
93
+ dic["frozen"] = frozenset([1, 2, 3])
94
+ ```
53
95
 
54
96
  ### Namespaces
55
97
  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.
@@ -218,51 +260,6 @@ print(dic["d"]) # Output: 4
218
260
  For more advanced examples of RedisDict, please refer to the unit-test files in the repository. All features and functionalities are thoroughly tested in [unit tests (here)](https://github.com/Attumm/redis-dict/blob/main/tests/unit/tests.py#L1) Or take a look at load test for batching [load test](https://github.com/Attumm/redis-dict/blob/main/tests/load/load_test.py#L1).
219
261
  The unit-tests can be as used as a starting point.
220
262
 
221
- ## Types
222
-
223
- ### standard types
224
- RedisDict supports a range of Python data types, from basic types to nested structures.
225
- Basic types are handled natively, while complex data types like lists and dictionaries, RedisDict uses JSON serialization, specifically avoiding `pickle` due to its [security vulnerabilities](https://docs.python.org/3/library/pickle.html) in distributed computing contexts.
226
- Although the library supports nested structures, the recommended best practice is to use RedisDict as a shallow dictionary.
227
- This approach optimizes Redis database performance and efficiency by ensuring that each set and get operation efficiently maps to Redis's key-value storage capabilities, while still preserving the library's Pythonic interface.
228
- Following types are supported:
229
- `str, int, float, bool, NoneType, list, dict, tuple, set, datetime, date, time, timedelta, Decimal, complex, bytes, UUID, OrderedDict, defaultdict, frozenset`
230
- ```python
231
- from uuid import UUID
232
- from decimal import Decimal
233
- from collections import OrderedDict, defaultdict
234
- from datetime import datetime, date, time, timedelta
235
-
236
- dic = RedisDict()
237
-
238
- dic["string"] = "Hello World"
239
- dic["number"] = 42
240
- dic["float"] = 3.14
241
- dic["bool"] = True
242
- dic["None"] = None
243
-
244
- dic["list"] = [1, 2, 3]
245
- dic["dict"] = {"a": 1, "b": 2}
246
- dic["tuple"] = (1, 2, 3)
247
- dic["set"] = {1, 2, 3}
248
-
249
- dic["datetime"] = datetime.date(2024, 1, 1, 12, 30, 45)
250
- dic["date"] = date(2024, 1, 1)
251
- dic["time"] = time(12, 30, 45)
252
- dic["delta"] = timedelta(days=1, hours=2)
253
-
254
- dic["decimal"] = Decimal("3.14159")
255
- dic["complex"] = complex(1, 2)
256
- dic["bytes"] = bytes([72, 101, 108, 108, 111])
257
- dic["uuid"] = UUID('12345678-1234-5678-1234-567812345678')
258
-
259
- dic["ordered"] = OrderedDict([('a', 1), ('b', 2)])
260
- dic["default"] = defaultdict(int, {'a': 1, 'b': 2})
261
- dic["frozen"] = frozenset([1, 2, 3])
262
- ```
263
-
264
-
265
-
266
263
  ### Nested types
267
264
  Nested Types
268
265
  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.
@@ -322,6 +319,21 @@ assert result.name == person.name
322
319
  assert result.age == person.age
323
320
  ```
324
321
 
322
+ ### Insertion Order
323
+ For insertion order, use the PythonRedisDict. This class is focused on Python dictionary behavior one-to-one.
324
+ It will eventually become a drop-in replacement for dictionary. Currently, nested types and typed keys are not yet supported but will be added in the future.
325
+
326
+ ```python
327
+ from redis_dict import PythonRedisDict
328
+
329
+ dic = PythonRedisDict()
330
+ dic["1"] = "one"
331
+ dic["2"] = "two"
332
+ dic["3"] = "three"
333
+
334
+ assert list(dic.keys()) == ["1", "2", "3"]
335
+ ```
336
+
325
337
  For more information on [extending types](https://github.com/Attumm/redis-dict/blob/main/tests/unit/extend_types_tests.py).
326
338
  ### Redis Encryption
327
339
  Setup guide for configuring and utilizing encrypted Redis TLS for redis-dict.
@@ -345,7 +357,7 @@ redis_config = {
345
357
  'port': 6380,
346
358
  }
347
359
 
348
- confid_dic = RedisDict(**redis_config)
360
+ config_dic = RedisDict(**redis_config)
349
361
  ```
350
362
 
351
363
  ## Installation
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "redis-dict"
7
- version = "3.1.2"
7
+ version = "3.2.0"
8
8
  description = "Dictionary with Redis as storage backend"
9
9
  authors = [
10
10
  {name = "Melvin Bijman", email = "bijman.m.m@gmail.com"},
@@ -2,10 +2,12 @@
2
2
  from importlib.metadata import version, PackageNotFoundError
3
3
 
4
4
  from .core import RedisDict
5
+ from .python_dict import PythonRedisDict
5
6
  from .type_management import decoding_registry, encoding_registry, RedisDictJSONEncoder, RedisDictJSONDecoder
6
7
 
7
8
  __all__ = [
8
9
  'RedisDict',
10
+ 'PythonRedisDict',
9
11
  'decoding_registry',
10
12
  'encoding_registry',
11
13
  'RedisDictJSONEncoder',
@@ -7,10 +7,10 @@ from collections.abc import Mapping
7
7
 
8
8
  from redis import StrictRedis
9
9
 
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
10
+ from .type_management import SENTINEL, EncodeFuncType, DecodeFuncType, EncodeType, DecodeType
11
+ from .type_management import _create_default_encode, _create_default_decode, _default_decoder
12
+ from .type_management import encoding_registry as enc_reg
13
+ from .type_management import decoding_registry as dec_reg
14
14
 
15
15
 
16
16
  # pylint: disable=R0902, R0904
@@ -40,12 +40,14 @@ class RedisDict:
40
40
  encoding_registry: EncodeType = enc_reg
41
41
  decoding_registry: DecodeType = dec_reg
42
42
 
43
+ # pylint: disable=R0913
43
44
  def __init__(self,
44
- namespace: str = 'main',
45
- expire: Union[int, timedelta, None] = None,
46
- preserve_expiration: Optional[bool] = False,
47
- redis: "Optional[StrictRedis[Any]]" = None,
48
- **redis_kwargs: Any) -> None:
45
+ namespace: str = 'main',
46
+ expire: Union[int, timedelta, None] = None,
47
+ preserve_expiration: Optional[bool] = False,
48
+ redis: "Optional[StrictRedis[Any]]" = None,
49
+ raise_key_error_delete: bool = False,
50
+ **redis_kwargs: Any) -> None: # noqa: D202:R0913 pydocstyle clashes with Sphinx
49
51
  """
50
52
  Initialize a RedisDict instance.
51
53
 
@@ -56,12 +58,14 @@ class RedisDict:
56
58
  expire (Union[int, timedelta, None], optional): Expiration time for keys.
57
59
  preserve_expiration (Optional[bool], optional): Preserve expiration on key updates.
58
60
  redis (Optional[StrictRedis[Any]], optional): A Redis connection instance.
61
+ raise_key_error_delete (bool): Enable strict Python dict behavior raise if key not found when deleting.
59
62
  **redis_kwargs (Any): Additional kwargs for Redis connection if not provided.
60
63
  """
61
64
 
62
65
  self.namespace: str = namespace
63
66
  self.expire: Union[int, timedelta, None] = expire
64
67
  self.preserve_expiration: Optional[bool] = preserve_expiration
68
+ self.raise_key_error_delete: bool = raise_key_error_delete
65
69
  if redis:
66
70
  redis.connection_pool.connection_kwargs["decode_responses"] = True
67
71
 
@@ -74,6 +78,8 @@ class RedisDict:
74
78
  self._iter: Iterator[str] = iter([])
75
79
  self._max_string_size: int = 500 * 1024 * 1024 # 500mb
76
80
  self._temp_redis: Optional[StrictRedis[Any]] = None
81
+ self._insertion_order_key = f"redis-dict-insertion-order-{namespace}"
82
+ self._batch_size: int = 200
77
83
 
78
84
  def _format_key(self, key: str) -> str:
79
85
  """
@@ -85,9 +91,21 @@ class RedisDict:
85
91
  Returns:
86
92
  str: The formatted key with the namespace prefix.
87
93
  """
88
- return f'{self.namespace}:{str(key)}'
94
+ return f'{self.namespace}:{key}'
89
95
 
90
- def _valid_input(self, val: Any, val_type: str) -> bool:
96
+ def _parse_key(self, key: str) -> str:
97
+ """
98
+ Parse a formatted key with the namespace prefix and type.
99
+
100
+ Args:
101
+ key (str): The key to be parsed to type.
102
+
103
+ Returns:
104
+ str: The parsed key
105
+ """
106
+ return key[len(self.namespace) + 1:]
107
+
108
+ def _valid_input(self, value: Any) -> bool:
91
109
  """
92
110
  Check if the input value is valid based on the specified value type.
93
111
 
@@ -96,36 +114,35 @@ class RedisDict:
96
114
  length does not exceed the maximum allowed size (500 MB).
97
115
 
98
116
  Args:
99
- val (Any): The input value to be validated.
100
- val_type (str): The type of the input value ("str", "int", "float", or "bool").
117
+ value (Any): The input value to be validated.
101
118
 
102
119
  Returns:
103
120
  bool: True if the input value is valid, False otherwise.
104
121
  """
105
- if val_type == "str":
106
- return len(val) < self._max_string_size
122
+ store_type = type(value).__name__
123
+ if store_type == "str":
124
+ return len(value) < self._max_string_size
107
125
  return True
108
126
 
109
- def _format_value(self, key: str, value: Any) -> str:
127
+ def _format_value(self, value: Any) -> str:
110
128
  """Format a valid value with the type and encoded representation of the value.
111
129
 
112
130
  Args:
113
- key (str): The key of the value to be formatted.
114
131
  value (Any): The value to be encoded and formatted.
115
132
 
116
- Raises:
117
- ValueError: If the value or key fail validation.
118
-
119
133
  Returns:
120
134
  str: The formatted value with the type and encoded representation of the value.
121
135
  """
122
- store_type, key = type(value).__name__, str(key)
123
- if not self._valid_input(value, store_type) or not self._valid_input(key, "str"):
124
- raise ValueError("Invalid input value or key size exceeded the maximum limit.")
136
+ store_type = type(value).__name__
125
137
  encoded_value = self.encoding_registry.get(store_type, lambda x: x)(value) # type: ignore
126
-
127
138
  return f'{store_type}:{encoded_value}'
128
139
 
140
+ def _store_set(self, formatted_key: str, formatted_value: str) -> None:
141
+ if self.preserve_expiration and self.get_redis.exists(formatted_key):
142
+ self.redis.set(formatted_key, formatted_value, keepttl=True)
143
+ else:
144
+ self.redis.set(formatted_key, formatted_value, ex=self.expire)
145
+
129
146
  def _store(self, key: str, value: Any) -> None:
130
147
  """
131
148
  Store a value in Redis with the given key.
@@ -134,6 +151,9 @@ class RedisDict:
134
151
  key (str): The key to store the value.
135
152
  value (Any): The value to be stored.
136
153
 
154
+ Raises:
155
+ ValueError: If the value or key fail validation.
156
+
137
157
  Note: Validity checks could be refactored to allow for custom exceptions that inherit from ValueError,
138
158
  providing detailed information about why a specific validation failed.
139
159
  This would enable users to specify which validity checks should be executed, add custom validity functions,
@@ -142,12 +162,13 @@ class RedisDict:
142
162
  Allowing for simple dict set operation, but only cache data that makes sense.
143
163
 
144
164
  """
165
+ if not self._valid_input(value) or not self._valid_input(key):
166
+ raise ValueError("Invalid input value or key size exceeded the maximum limit.")
167
+
145
168
  formatted_key = self._format_key(key)
146
- formatted_value = self._format_value(key, value)
147
- if self.preserve_expiration and self.redis.exists(formatted_key):
148
- self.redis.set(formatted_key, formatted_value, keepttl=True)
149
- else:
150
- self.redis.set(formatted_key, formatted_value, ex=self.expire)
169
+ formatted_value = self._format_value(value)
170
+
171
+ self._store_set(formatted_key, formatted_value)
151
172
 
152
173
  def _load(self, key: str) -> Tuple[bool, Any]:
153
174
  """
@@ -213,7 +234,7 @@ class RedisDict:
213
234
  decode: Optional[DecodeFuncType] = None,
214
235
  encoding_method_name: Optional[str] = None,
215
236
  decoding_method_name: Optional[str] = None,
216
- ) -> None:
237
+ ) -> None: # noqa: D202 pydocstyle clashes with Sphinx
217
238
  """
218
239
  Extend RedisDict to support a custom type in the encode/decode mapping.
219
240
 
@@ -349,10 +370,25 @@ class RedisDict:
349
370
  """
350
371
  Delete the value associated with the given key, analogous to a dictionary.
351
372
 
373
+ For distributed systems, we intentionally don't raise KeyError when the key doesn't exist.
374
+ This ensures identical code running across different systems won't randomly fail
375
+ when another system already achieved the deletion goal (key not existing).
376
+
377
+ Warning:
378
+ Setting dict_compliant=True will raise KeyError when key doesn't exist.
379
+ This is not recommended for distributed systems as it can cause KeyErrors
380
+ that are hard to debug when multiple systems interact with the same keys.
381
+
352
382
  Args:
353
- key (str): The key to delete the value.
383
+ key (str): The key to delete
384
+
385
+ Raises:
386
+ KeyError: Only if dict_compliant=True and key doesn't exist
354
387
  """
355
- self.redis.delete(self._format_key(key))
388
+ formatted_key = self._format_key(key)
389
+ result = self.redis.delete(formatted_key)
390
+ if self.raise_key_error_delete and not result:
391
+ raise KeyError(key)
356
392
 
357
393
  def __contains__(self, key: str) -> bool:
358
394
  """
@@ -373,7 +409,7 @@ class RedisDict:
373
409
  Returns:
374
410
  int: The number of items in the RedisDict.
375
411
  """
376
- return len(list(self._scan_keys()))
412
+ return sum(1 for _ in self._scan_keys(full_scan=True))
377
413
 
378
414
  def __iter__(self) -> Iterator[str]:
379
415
  """
@@ -470,12 +506,12 @@ class RedisDict:
470
506
  return self
471
507
 
472
508
  @classmethod
473
- def __class_getitem__(cls: Type['RedisDict'], key: Any) -> Type['RedisDict']:
509
+ def __class_getitem__(cls: Type['RedisDict'], _key: Any) -> Type['RedisDict']:
474
510
  """
475
511
  Enable type hinting support like RedisDict[str, Any].
476
512
 
477
513
  Args:
478
- key (Any): The type parameter(s) used in the type hint.
514
+ _key (Any): The type parameter(s) used in the type hint.
479
515
 
480
516
  Returns:
481
517
  Type[RedisDict]: The class itself, enabling type hint usage.
@@ -537,18 +573,19 @@ class RedisDict:
537
573
  """
538
574
  return f'{self.namespace}:{search_term}*'
539
575
 
540
- def _scan_keys(self, search_term: str = '') -> Iterator[str]:
541
- """
542
- Scan for Redis keys matching the given search term.
576
+ def _scan_keys(self, search_term: str = '', full_scan: bool = False) -> Iterator[str]:
577
+ """Scan for Redis keys matching the given search term.
543
578
 
544
579
  Args:
545
580
  search_term (str): A search term to filter keys. Defaults to ''.
581
+ full_scan (bool): During full scan uses batches of self._batch_size by default 200
546
582
 
547
583
  Returns:
548
584
  Iterator[str]: An iterator of matching Redis keys.
549
585
  """
550
586
  search_query = self._create_iter_query(search_term)
551
- return self.get_redis.scan_iter(match=search_query)
587
+ count = None if full_scan else self._batch_size
588
+ return self.get_redis.scan_iter(match=search_query, count=count)
552
589
 
553
590
  def get(self, key: str, default: Optional[Any] = None) -> Any:
554
591
  """Return the value for the given key if it exists, otherwise return the default value.
@@ -636,12 +673,24 @@ class RedisDict:
636
673
  Redis pipelining is employed to group multiple commands into a single request, minimizing
637
674
  network round-trip time, latency, and I/O load, thereby enhancing the overall performance.
638
675
 
639
- It is important to highlight that the clear method can be safely executed within
640
- the context of an initiated pipeline operation.
641
676
  """
642
677
  with self.pipeline():
643
- for key in self:
644
- del self[key]
678
+ for key in self._scan_keys(full_scan=True):
679
+ self.redis.delete(key)
680
+
681
+ def _pop(self, formatted_key: str) -> Any:
682
+ """
683
+ Remove the value associated with the given key and return it.
684
+
685
+ Or return the default value if the key is not found.
686
+
687
+ Args:
688
+ formatted_key (str): The formatted key to remove the value.
689
+
690
+ Returns:
691
+ Any: The value associated with the key or the default value.
692
+ """
693
+ return self.get_redis.execute_command("GETDEL", formatted_key)
645
694
 
646
695
  def pop(self, key: str, default: Union[Any, object] = SENTINEL) -> Any:
647
696
  """Analogous to a dictionary's pop method.
@@ -660,12 +709,11 @@ class RedisDict:
660
709
  KeyError: If the key is not found and no default value is provided.
661
710
  """
662
711
  formatted_key = self._format_key(key)
663
- value = self.get_redis.execute_command("GETDEL", formatted_key)
712
+ value = self._pop(formatted_key)
664
713
  if value is None:
665
714
  if default is not SENTINEL:
666
715
  return default
667
716
  raise KeyError(formatted_key)
668
-
669
717
  return self._transform(value)
670
718
 
671
719
  def popitem(self) -> Tuple[str, Any]:
@@ -673,6 +721,8 @@ class RedisDict:
673
721
 
674
722
  This method is analogous to the `popitem` method of a standard Python dictionary.
675
723
 
724
+ if dict_compliant set true stays true to In Python 3.7+, removes the last inserted item (LIFO order)
725
+
676
726
  Returns:
677
727
  tuple: A tuple containing a randomly chosen (key, value) pair.
678
728
 
@@ -688,22 +738,16 @@ class RedisDict:
688
738
  except KeyError:
689
739
  continue
690
740
 
691
- def setdefault(self, key: str, default_value: Optional[Any] = None) -> Any:
692
- """Get value under key, and if not present set default value.
693
-
694
- Return the value associated with the given key if it exists, otherwise set the value to the
695
- default value and return it. Analogous to a dictionary's setdefault method.
741
+ def _create_set_get_command(self, formatted_key: str, formatted_value: str) -> Tuple[List[str], Dict[str, bool]]:
742
+ """Create SET command arguments and options for Redis. For setdefault operation.
696
743
 
697
744
  Args:
698
- key (str): The key to retrieve the value.
699
- default_value (Optional[Any], optional): The value to set if the key is not found.
745
+ formatted_key (str): The formatted Redis key.
746
+ formatted_value (str): The formatted value to be set.
700
747
 
701
748
  Returns:
702
- Any: The value associated with the key or the default value.
749
+ Tuple[List[str], Dict[str, bool]]: A tuple containing the command arguments and options.
703
750
  """
704
- formatted_key = self._format_key(key)
705
- formatted_value = self._format_value(key, default_value)
706
-
707
751
  # Setting {"get": True} enables parsing of the redis result as "GET", instead of "SET" command
708
752
  options = {"get": True}
709
753
  args = ["SET", formatted_key, formatted_value, "NX", "GET"]
@@ -713,8 +757,27 @@ class RedisDict:
713
757
  expire_val = int(self.expire.total_seconds()) if isinstance(self.expire, timedelta) else self.expire
714
758
  expire_str = str(1) if expire_val <= 1 else str(expire_val)
715
759
  args.extend(["EX", expire_str])
760
+ return args, options
716
761
 
762
+ def setdefault(self, key: str, default_value: Optional[Any] = None) -> Any:
763
+ """Get value under key, and if not present set default value.
764
+
765
+ Return the value associated with the given key if it exists, otherwise set the value to the
766
+ default value and return it. Analogous to a dictionary's setdefault method.
767
+
768
+ Args:
769
+ key (str): The key to retrieve the value.
770
+ default_value (Optional[Any], optional): The value to set if the key is not found.
771
+
772
+ Returns:
773
+ Any: The value associated with the key or the default value.
774
+ """
775
+ formatted_key = self._format_key(key)
776
+ formatted_value = self._format_value(default_value)
777
+
778
+ args, options = self._create_set_get_command(formatted_key, formatted_value)
717
779
  result = self.get_redis.execute_command(*args, **options)
780
+
718
781
  if result is None:
719
782
  return default_value
720
783
 
@@ -0,0 +1,331 @@
1
+ """Python Redis Dict module."""
2
+ from typing import Any, Iterator, Tuple, Union, Optional, List, Dict
3
+
4
+ import time
5
+ from datetime import timedelta
6
+
7
+ from redis import StrictRedis
8
+
9
+ from .core import RedisDict
10
+
11
+
12
+ class PythonRedisDict(RedisDict):
13
+ """Python dictionary with Redis as backend.
14
+
15
+ With support for advanced features, such as custom data types, pipelining, and key expiration.
16
+
17
+ This class focuses on having one-to-on behavior of a dictionary while using Redis as storage layer, allowing
18
+ for efficient storage and retrieval of key-value pairs. It supports various data types, including
19
+ strings, integers, floats, lists, dictionaries, tuples, sets, and user-defined types. The class
20
+ leverages the power of Redis pipelining to minimize network round-trip time, latency, and I/O load,
21
+ thereby optimizing performance for batch operations. Additionally, it allows for the management of
22
+ key expiration through the use of context managers.
23
+
24
+ The RedisDict class is designed to be analogous to a standard Python dictionary while providing
25
+ enhanced functionality, such as support for a wider range of data types and efficient batch operations.
26
+ It aims to offer a seamless and familiar interface for developers familiar with Python dictionaries,
27
+ enabling a smooth transition to a Redis-backed data store.
28
+
29
+ Extendable Types: You can extend RedisDict by adding or overriding encoding and decoding functions.
30
+ This functionality enables various use cases, such as managing encrypted data in Redis,
31
+ To implement this, simply create and register your custom encoding and decoding functions.
32
+ By delegating serialization to redis-dict, reduce complexity and have simple code in the codebase.
33
+ """
34
+
35
+ def __init__(self,
36
+ namespace: str = 'main',
37
+ expire: Union[int, timedelta, None] = None,
38
+ preserve_expiration: Optional[bool] = False,
39
+ redis: "Optional[StrictRedis[Any]]" = None,
40
+ **redis_kwargs: Any) -> None: # noqa: D202 pydocstyle clashes with Sphinx
41
+ """
42
+ Initialize a RedisDict instance.
43
+
44
+ Init the RedisDict instance.
45
+
46
+ Args:
47
+ namespace (str): A prefix for keys stored in Redis.
48
+ expire (Union[int, timedelta, None], optional): Expiration time for keys.
49
+ preserve_expiration (Optional[bool], optional): Preserve expiration on key updates.
50
+ redis (Optional[StrictRedis[Any]], optional): A Redis connection instance.
51
+ **redis_kwargs (Any): Additional kwargs for Redis connection if not provided.
52
+ """
53
+ super().__init__(
54
+ namespace=namespace,
55
+ expire=expire,
56
+ preserve_expiration=preserve_expiration,
57
+ redis=redis,
58
+ raise_key_error_delete=True,
59
+ **redis_kwargs
60
+ )
61
+ self._insertion_order_key = f"redis-dict-insertion-order-{namespace}"
62
+
63
+ def __delitem__(self, key: str) -> None:
64
+ """
65
+ Delete the value associated with the given key, analogous to a dictionary.
66
+
67
+ For distributed systems, we intentionally don't raise KeyError when the key doesn't exist.
68
+ This ensures identical code running across different systems won't randomly fail
69
+ when another system already achieved the deletion goal (key not existing).
70
+
71
+ Warning:
72
+ Setting dict_compliant=True will raise KeyError when key doesn't exist.
73
+ This is not recommended for distributed systems as it can cause KeyErrors
74
+ that are hard to debug when multiple systems interact with the same keys.
75
+
76
+ Args:
77
+ key (str): The key to delete
78
+
79
+ Raises:
80
+ KeyError: Only if dict_compliant=True and key doesn't exist
81
+ """
82
+ formatted_key = self._format_key(key)
83
+
84
+ result = self.redis.delete(formatted_key)
85
+ self._insertion_order_delete(formatted_key)
86
+ if not result:
87
+ raise KeyError(key)
88
+
89
+ def _store(self, key: str, value: Any) -> None:
90
+ """
91
+ Store a value in Redis with the given key.
92
+
93
+ Args:
94
+ key (str): The key to store the value.
95
+ value (Any): The value to be stored.
96
+
97
+ Raises:
98
+ ValueError: If the value or key fail validation.
99
+
100
+ Note: Validity checks could be refactored to allow for custom exceptions that inherit from ValueError,
101
+ providing detailed information about why a specific validation failed.
102
+ This would enable users to specify which validity checks should be executed, add custom validity functions,
103
+ and choose whether to fail on validation errors, or drop the data and only issue a warning and continue.
104
+ Example use case is caching, to cache data only when it's between min and max sizes.
105
+ Allowing for simple dict set operation, but only cache data that makes sense.
106
+
107
+ """
108
+ if not self._valid_input(value) or not self._valid_input(key):
109
+ raise ValueError("Invalid input value or key size exceeded the maximum limit.")
110
+
111
+ formatted_key = self._format_key(key)
112
+ formatted_value = self._format_value(value)
113
+
114
+ with self.pipeline():
115
+ self._insertion_order_add(formatted_key)
116
+ self._store_set(formatted_key, formatted_value)
117
+
118
+ def setdefault(self, key: str, default_value: Optional[Any] = None) -> Any:
119
+ """Get value under key, and if not present set default value.
120
+
121
+ Return the value associated with the given key if it exists, otherwise set the value to the
122
+ default value and return it. Analogous to a dictionary's setdefault method.
123
+
124
+ Args:
125
+ key (str): The key to retrieve the value.
126
+ default_value (Optional[Any], optional): The value to set if the key is not found.
127
+
128
+ Returns:
129
+ Any: The value associated with the key or the default value.
130
+ """
131
+ formatted_key = self._format_key(key)
132
+ formatted_value = self._format_value(default_value)
133
+
134
+ # Todo bind both commands
135
+ args, options = self._create_set_get_command(formatted_key, formatted_value)
136
+ result = self.get_redis.execute_command(*args, **options)
137
+ self._insertion_order_add(formatted_key)
138
+
139
+ if result is None:
140
+ return default_value
141
+
142
+ return self._transform(result)
143
+
144
+ def __len__(self) -> int:
145
+ """
146
+ Get the number of items in the RedisDict, analogous to a dictionary.
147
+
148
+ Returns:
149
+ int: The number of items in the RedisDict.
150
+ """
151
+ return self._insertion_order_len()
152
+
153
+ def _scan_keys(self, search_term: str = '', full_scan: bool = False) -> Iterator[str]:
154
+ return self._insertion_order_iter()
155
+
156
+ def clear(self) -> None:
157
+ """Remove all key-value pairs from the RedisDict in one batch operation using pipelining.
158
+
159
+ This method mimics the behavior of the `clear` method from a standard Python dictionary.
160
+ Redis pipelining is employed to group multiple commands into a single request, minimizing
161
+ network round-trip time, latency, and I/O load, thereby enhancing the overall performance.
162
+
163
+ """
164
+ with self.pipeline():
165
+ self._insertion_order_clear()
166
+ for key in self._scan_keys(full_scan=True):
167
+ self.redis.delete(key)
168
+
169
+ def popitem(self) -> Tuple[str, Any]:
170
+ """Remove and return a random (key, value) pair from the RedisDict as a tuple.
171
+
172
+ This method is analogous to the `popitem` method of a standard Python dictionary.
173
+
174
+ if dict_compliant set true stays true to In Python 3.7+, removes the last inserted item (LIFO order)
175
+
176
+ Returns:
177
+ tuple: A tuple containing a randomly chosen (key, value) pair.
178
+
179
+ Raises:
180
+ KeyError: If RedisDict is empty.
181
+ """
182
+ key = self._insertion_order_latest()
183
+ if key is None:
184
+ raise KeyError("popitem(): dictionary is empty")
185
+ return self._parse_key(key), self._transform(self._pop(key))
186
+
187
+ def _pop(self, formatted_key: str) -> Any:
188
+ """
189
+ Remove the value associated with the given key and return it.
190
+
191
+ Or return the default value if the key is not found.
192
+
193
+ Args:
194
+ formatted_key (str): The formatted key to remove the value.
195
+
196
+ Returns:
197
+ Any: The value associated with the key or the default value.
198
+ """
199
+ # TODO bind both commands
200
+ self._insertion_order_delete(formatted_key)
201
+ return self.get_redis.execute_command("GETDEL", formatted_key)
202
+
203
+ def multi_get(self, _key: str) -> List[Any]:
204
+ """
205
+ Not part of Python Redis Dict.
206
+
207
+ Args:
208
+ _key (str): Not used.
209
+
210
+ Raises:
211
+ NotImplementedError: Not part of Python Redis Dict.
212
+ """
213
+ raise NotImplementedError("Not part of PythonRedisDict")
214
+
215
+ def multi_chain_get(self, _keys: List[str]) -> List[Any]:
216
+ """
217
+ Not part of Python Redis Dict.
218
+
219
+ Args:
220
+ _keys (List[str]): Not used.
221
+
222
+ Raises:
223
+ NotImplementedError: Not part of Python Redis Dict.
224
+ """
225
+ raise NotImplementedError("Not part of PythonRedisDict")
226
+
227
+ def multi_dict(self, _key: str) -> Dict[str, Any]:
228
+ """
229
+ Not part of Python Redis Dict.
230
+
231
+ Args:
232
+ _key (str): Not used.
233
+
234
+ Raises:
235
+ NotImplementedError: Not part of Python Redis Dict.
236
+ """
237
+ raise NotImplementedError("Not part of PythonRedisDict")
238
+
239
+ def multi_del(self, _key: str) -> int:
240
+ """
241
+ Not part of Python Redis Dict.
242
+
243
+ Args:
244
+ _key (str): Not used.
245
+
246
+ Raises:
247
+ NotImplementedError: Not part of Python Redis Dict.
248
+ """
249
+ raise NotImplementedError("Not part of PythonRedisDict")
250
+
251
+ def _insertion_order_add(self, formatted_key: str) -> bool:
252
+ """Record a key's insertion into the dictionary.
253
+
254
+ This private method updates the insertion order tracking when a new key is added
255
+ to the dictionary.
256
+
257
+ Args:
258
+ formatted_key (str): The key being added to the dictionary.
259
+
260
+ Returns:
261
+ bool: True if the insertion order was updated, False otherwise.
262
+ """
263
+ return bool(self.redis.zadd(self._insertion_order_key, {formatted_key: time.time()}))
264
+
265
+ def _insertion_order_delete(self, formatted_key: str) -> bool:
266
+ """Remove a key from the insertion order tracking.
267
+
268
+ This private method updates the insertion order tracking when a key is removed
269
+ from the dictionary.
270
+
271
+ Args:
272
+ formatted_key (str): The key being removed from the dictionary.
273
+
274
+ Returns:
275
+ bool: True if the insertion order was updated, False otherwise.
276
+ """
277
+ return bool(self.redis.zrem(self._insertion_order_key, formatted_key))
278
+
279
+ def _insertion_order_iter(self) -> Iterator[str]:
280
+ """Create an iterator for dictionary keys in their insertion order.
281
+
282
+ This private method allows for iterating over the dictionary's keys in the order
283
+ they were inserted.
284
+
285
+ Yields:
286
+ str: Keys in their insertion order.
287
+ """
288
+ # TODO add full_scan boolean and search terms.
289
+ first = True
290
+ cursor = -1
291
+ while cursor != 0:
292
+ if first:
293
+ cursor = 0
294
+ first = False
295
+ cursor, data = self.get_redis.zscan(
296
+ name=self._insertion_order_key,
297
+ cursor=cursor,
298
+ count=1
299
+ )
300
+ yield from (item[0] for item in data)
301
+
302
+ def _insertion_order_clear(self) -> bool:
303
+ """Clear all insertion order information.
304
+
305
+ This private method resets the insertion order tracking for the dictionary.
306
+
307
+ Returns:
308
+ bool: True if the insertion order was successfully cleared, False otherwise.
309
+ """
310
+ return bool(self.redis.delete(self._insertion_order_key))
311
+
312
+ def _insertion_order_len(self) -> int:
313
+ """Get the number of keys in the insertion order tracking.
314
+
315
+ This private method returns the count of keys being tracked for insertion order.
316
+
317
+ Returns:
318
+ int: The number of keys in the insertion order tracking.
319
+ """
320
+ return self.get_redis.zcard(self._insertion_order_key)
321
+
322
+ def _insertion_order_latest(self) -> Union[str, None]:
323
+ """Get the most recently inserted key in the dictionary.
324
+
325
+ This private method retrieves the key that was most recently added to the dictionary.
326
+
327
+ Returns:
328
+ Union[str, None]: The most recently inserted key, or None if the dictionary is empty.
329
+ """
330
+ result = self.redis.zrange(self._insertion_order_key, -1, -1)
331
+ return result[0] if result else None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: redis-dict
3
- Version: 3.1.2
3
+ Version: 3.2.0
4
4
  Summary: Dictionary with Redis as storage backend
5
5
  Author-email: Melvin Bijman <bijman.m.m@gmail.com>
6
6
  License: MIT
@@ -122,6 +122,48 @@ In Redis our example looks like this.
122
122
  "str:hello world"
123
123
  ```
124
124
 
125
+ ## Types
126
+
127
+ ### standard types
128
+ RedisDict supports a range of Python data types, from basic types to nested structures.
129
+ Basic types are handled natively, while complex data types like lists and dictionaries, RedisDict uses JSON serialization, specifically avoiding [pickle](https://docs.python.org/3/library/pickle.html) due to its security vulnerabilities within distributed computing contexts.
130
+ Although the library supports nested structures, the recommended best practice is to use RedisDict as a shallow dictionary.
131
+ This approach optimizes Redis database performance and efficiency by ensuring that each set and get operation efficiently maps to Redis's key-value storage capabilities, while still preserving the library's Pythonic interface.
132
+ Following types are supported:
133
+ `str, int, float, bool, NoneType, list, dict, tuple, set, datetime, date, time, timedelta, Decimal, complex, bytes, UUID, OrderedDict, defaultdict, frozenset`
134
+ ```python
135
+ from uuid import UUID
136
+ from decimal import Decimal
137
+ from collections import OrderedDict, defaultdict
138
+ from datetime import datetime, date, time, timedelta
139
+
140
+ dic = RedisDict()
141
+
142
+ dic["string"] = "Hello World"
143
+ dic["number"] = 42
144
+ dic["float"] = 3.14
145
+ dic["bool"] = True
146
+ dic["None"] = None
147
+
148
+ dic["list"] = [1, 2, 3]
149
+ dic["dict"] = {"a": 1, "b": 2}
150
+ dic["tuple"] = (1, 2, 3)
151
+ dic["set"] = {1, 2, 3}
152
+
153
+ dic["datetime"] = datetime.date(2024, 1, 1, 12, 30, 45)
154
+ dic["date"] = date(2024, 1, 1)
155
+ dic["time"] = time(12, 30, 45)
156
+ dic["delta"] = timedelta(days=1, hours=2)
157
+
158
+ dic["decimal"] = Decimal("3.14159")
159
+ dic["complex"] = complex(1, 2)
160
+ dic["bytes"] = bytes([72, 101, 108, 108, 111])
161
+ dic["uuid"] = UUID('12345678-1234-5678-1234-567812345678')
162
+
163
+ dic["ordered"] = OrderedDict([('a', 1), ('b', 2)])
164
+ dic["default"] = defaultdict(int, {'a': 1, 'b': 2})
165
+ dic["frozen"] = frozenset([1, 2, 3])
166
+ ```
125
167
 
126
168
  ### Namespaces
127
169
  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.
@@ -290,51 +332,6 @@ print(dic["d"]) # Output: 4
290
332
  For more advanced examples of RedisDict, please refer to the unit-test files in the repository. All features and functionalities are thoroughly tested in [unit tests (here)](https://github.com/Attumm/redis-dict/blob/main/tests/unit/tests.py#L1) Or take a look at load test for batching [load test](https://github.com/Attumm/redis-dict/blob/main/tests/load/load_test.py#L1).
291
333
  The unit-tests can be as used as a starting point.
292
334
 
293
- ## Types
294
-
295
- ### standard types
296
- RedisDict supports a range of Python data types, from basic types to nested structures.
297
- Basic types are handled natively, while complex data types like lists and dictionaries, RedisDict uses JSON serialization, specifically avoiding `pickle` due to its [security vulnerabilities](https://docs.python.org/3/library/pickle.html) in distributed computing contexts.
298
- Although the library supports nested structures, the recommended best practice is to use RedisDict as a shallow dictionary.
299
- This approach optimizes Redis database performance and efficiency by ensuring that each set and get operation efficiently maps to Redis's key-value storage capabilities, while still preserving the library's Pythonic interface.
300
- Following types are supported:
301
- `str, int, float, bool, NoneType, list, dict, tuple, set, datetime, date, time, timedelta, Decimal, complex, bytes, UUID, OrderedDict, defaultdict, frozenset`
302
- ```python
303
- from uuid import UUID
304
- from decimal import Decimal
305
- from collections import OrderedDict, defaultdict
306
- from datetime import datetime, date, time, timedelta
307
-
308
- dic = RedisDict()
309
-
310
- dic["string"] = "Hello World"
311
- dic["number"] = 42
312
- dic["float"] = 3.14
313
- dic["bool"] = True
314
- dic["None"] = None
315
-
316
- dic["list"] = [1, 2, 3]
317
- dic["dict"] = {"a": 1, "b": 2}
318
- dic["tuple"] = (1, 2, 3)
319
- dic["set"] = {1, 2, 3}
320
-
321
- dic["datetime"] = datetime.date(2024, 1, 1, 12, 30, 45)
322
- dic["date"] = date(2024, 1, 1)
323
- dic["time"] = time(12, 30, 45)
324
- dic["delta"] = timedelta(days=1, hours=2)
325
-
326
- dic["decimal"] = Decimal("3.14159")
327
- dic["complex"] = complex(1, 2)
328
- dic["bytes"] = bytes([72, 101, 108, 108, 111])
329
- dic["uuid"] = UUID('12345678-1234-5678-1234-567812345678')
330
-
331
- dic["ordered"] = OrderedDict([('a', 1), ('b', 2)])
332
- dic["default"] = defaultdict(int, {'a': 1, 'b': 2})
333
- dic["frozen"] = frozenset([1, 2, 3])
334
- ```
335
-
336
-
337
-
338
335
  ### Nested types
339
336
  Nested Types
340
337
  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.
@@ -394,6 +391,21 @@ assert result.name == person.name
394
391
  assert result.age == person.age
395
392
  ```
396
393
 
394
+ ### Insertion Order
395
+ For insertion order, use the PythonRedisDict. This class is focused on Python dictionary behavior one-to-one.
396
+ It will eventually become a drop-in replacement for dictionary. Currently, nested types and typed keys are not yet supported but will be added in the future.
397
+
398
+ ```python
399
+ from redis_dict import PythonRedisDict
400
+
401
+ dic = PythonRedisDict()
402
+ dic["1"] = "one"
403
+ dic["2"] = "two"
404
+ dic["3"] = "three"
405
+
406
+ assert list(dic.keys()) == ["1", "2", "3"]
407
+ ```
408
+
397
409
  For more information on [extending types](https://github.com/Attumm/redis-dict/blob/main/tests/unit/extend_types_tests.py).
398
410
  ### Redis Encryption
399
411
  Setup guide for configuring and utilizing encrypted Redis TLS for redis-dict.
@@ -417,7 +429,7 @@ redis_config = {
417
429
  'port': 6380,
418
430
  }
419
431
 
420
- confid_dic = RedisDict(**redis_config)
432
+ config_dic = RedisDict(**redis_config)
421
433
  ```
422
434
 
423
435
  ## Installation
@@ -4,6 +4,7 @@ pyproject.toml
4
4
  src/redis_dict/__init__.py
5
5
  src/redis_dict/core.py
6
6
  src/redis_dict/py.typed
7
+ src/redis_dict/python_dict.py
7
8
  src/redis_dict/type_management.py
8
9
  src/redis_dict.egg-info/PKG-INFO
9
10
  src/redis_dict.egg-info/SOURCES.txt
File without changes
File without changes