redis-dict 1.6.0__py3-none-any.whl → 2.0.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,163 @@
1
+ Metadata-Version: 2.1
2
+ Name: redis-dict
3
+ Version: 2.0.0
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
8
+ License: MIT
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Topic :: Database
12
+ Classifier: Topic :: System :: Distributed Computing
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.3
16
+ Classifier: Programming Language :: Python :: 3.4
17
+ Classifier: Programming Language :: Python :: 3.5
18
+ Classifier: Programming Language :: Python :: 3.6
19
+ Classifier: Programming Language :: Python :: 3.7
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: redis
23
+
24
+ # Redis-dict
25
+ [![Build Status](https://travis-ci.com/Attumm/redis-dict.svg?branch=main)](https://travis-ci.com/Attumm/redis-dict)
26
+ [![Downloads](https://pepy.tech/badge/redis-dict)](https://pepy.tech/project/redis-dict)
27
+
28
+ RedisDict is a Python library that allows you to interact with Redis as if it were a Python dictionary. It provides a simple, convenient, and user-friendly interface for managing key-value pairs in Redis. The library is designed to work seamlessly with different data types such as strings, integers, floats, booleans, None, lists, and dictionaries. It also offers additional utility functions and features for more complex use cases.
29
+
30
+ RedisDict was developed to address the challenges associated with handling extremely large datasets, which often exceed the capacity of local memory. This package offers a powerful and efficient solution for managing vast amounts of data by leveraging the capabilities of Redis and providing a familiar Python dictionary-like interface for seamless integration into your projects.
31
+
32
+
33
+ RedisDict stores data in Redis using key-value pairs, adhering to [Redis best practices](https://redislabs.com/redis-best-practices/data-storage-patterns/) for data storage patterns. This design not only ensures optimal performance and reliability but also enables interoperability with non-Python programs, granting them seamless access to the data stored in Redis.
34
+
35
+ ## Example
36
+ Redis is an exceptionally fast database when used appropriately. RedisDict leverages Redis for efficient key-value storage, enabling high-performance data management.
37
+
38
+ ```python
39
+ >>> from redis_dict import RedisDict
40
+ >>> dic = RedisDict(namespace='bar')
41
+ >>> 'foo' in dic
42
+ False
43
+ >>> dic['foo'] = 42
44
+ >>> dic['foo']
45
+ 42
46
+ >>> 'foo' in dic
47
+ True
48
+ >>> dic["baz"] = "a string"
49
+ >>> print(dic)
50
+ {'foo': 42, 'baz': 'a string'}
51
+
52
+ ```
53
+ In Redis our example looks like this.
54
+ ```
55
+ 127.0.0.1:6379> KEYS "*"
56
+ 1) "bar:foo"
57
+ 2) "bar:baz"
58
+ ```
59
+
60
+ ## Features
61
+
62
+ * Dictionary-like interface: Use familiar Python dictionary syntax to interact with Redis.
63
+ * Data Type Support: Comprehensive support for various data types, including strings, integers, floats, booleans, lists, dictionaries, sets, and tuples.
64
+ * Pipelining support: Use pipelines for batch operations to improve performance.
65
+ * Expiration support: Set expiration times for keys using context managers.
66
+ * Efficiency and Scalability: RedisDict is designed for use with large datasets and is optimized for efficiency. It retrieves only the data needed for a particular operation, ensuring efficient memory usage and fast performance.
67
+ * Namespace Management: Provides simple and efficient namespace handling to help organize and manage data in Redis, streamlining data access and manipulation.
68
+ * Distributed Computing: With its ability to seamlessly connect to other instances or servers with access to the same Redis instance, RedisDict enables easy distributed computing.
69
+ * Multi-get and multi-delete: Perform batch operations for getting and deleting multiple keys at once.
70
+ * Custom data types: Add custom types and transformations to suit your specific needs.
71
+
72
+ #### Caveats and Experimental Support
73
+
74
+ Please note that the following data types have experimental support in RedisDict:
75
+ * List
76
+ * Tuple
77
+ * Set
78
+ * Dictionary
79
+
80
+ These data types are supported through JSON serialization, which means that if your lists or dictionaries can be serialized using JSON, this feature should work as expected. However, this may not be the optimal solution for all use cases. As such, use these features at your discretion and consider potential limitations.
81
+
82
+ If you encounter a need for additional support or improvements for these or other reference types, please feel free to open an issue on the GitHub repository. Your feedback and contributions are greatly appreciated.
83
+
84
+ ### Distributed computing
85
+ You can use RedisDict for distributed computing by starting multiple RedisDict instances on different servers or instances that have access to the same Redis instance:
86
+ ```python
87
+ # On server 1
88
+ r = RedisDict(namespace="example", **redis_config)
89
+ r["foo"] = "bar"
90
+
91
+
92
+ # On server 2
93
+ r1 = RedisDict(namespace="example", **redis_config)
94
+ print(r1["foo"])
95
+ "bar"
96
+
97
+ ```
98
+
99
+ ## Advance features examples
100
+
101
+ #### Expiration
102
+
103
+ Redis provides a valuable feature that enables keys to expire. RedisDict supports this feature in the following ways:
104
+ 1. Set a default expiration time when creating a RedisDict instance:
105
+ ```python
106
+ r_dic = RedisDict(namespace='app_name', expire=10)
107
+ ```
108
+ In this example, the keys will have a default expiration time of 10 seconds.
109
+
110
+ 2. Temporarily set the default expiration time within the scope using a context manager:
111
+ ```python
112
+ seconds = 60
113
+ with r_dic.expire_at(seconds):
114
+ r_dic['gone_in_sixty_seconds'] = 'foo'
115
+ ```
116
+ In this example, the key 'gone_in_sixty_seconds' will expire after 60 seconds. The default expiration time for other keys outside the context manager remains unchanged.
117
+
118
+ Please note that the default expiration time is set to None, which indicates that the keys will not expire unless explicitly specified.
119
+
120
+ #### Batching
121
+ Efficiently batch your requests using the Pipeline feature, which can be easily utilized with a context manager.
122
+
123
+ For example, let's store the first ten items of the Fibonacci sequence using a single round trip to Redis:
124
+ [example](https://github.com/Attumm/redis-dict/blob/main/assert_test.py#L1) larger script using pipelining with batching
125
+
126
+ ```python
127
+ def fib(n):
128
+ a, b = 0, 1
129
+ for _ in range(n):
130
+ yield a
131
+ a, b = (a+b), a
132
+
133
+ with r_dic.pipeline():
134
+ for index, item in enumerate(fib(10)):
135
+ r_dic[str(index)] = item
136
+ ```
137
+
138
+ By employing the pipeline context manager, you can reduce network overhead and improve the performance of your application when executing multiple Redis operations in a single batch.
139
+
140
+ ### Namespaces
141
+ RedisDict employs namespaces by default, providing an organized and efficient way to manage data across multiple projects. By using a dedicated RedisDict instance for each project, you can easily identify which data belongs to which application when inspecting Redis directly.
142
+
143
+ 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.
144
+
145
+ ### Additional Examples
146
+ For more advanced examples of RedisDict, please refer to the test files in the repository. All features and functionalities are thoroughly tested in either[ `assert_test.py` (here)](https://github.com/Attumm/redis-dict/blob/main/assert_test.py#L1) or in the [unit tests (here)](https://github.com/Attumm/redis-dict/blob/main/tests.py#L1).
147
+ The test can be used as starting point for new projects
148
+
149
+ ### Tests
150
+
151
+ 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
152
+
153
+ ## Installation
154
+ ```sh
155
+ pip install redis-dict
156
+ ```
157
+
158
+ ### Note
159
+ 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
160
+
161
+
162
+
163
+ ### Note This project only uses redis as dependency
@@ -0,0 +1,6 @@
1
+ redis_dict.py,sha256=q_bTwNil5veFqPRd5cAFi6nle-yZxpZLKcyxJi-p72o,24054
2
+ redis_dict-2.0.0.dist-info/LICENSE,sha256=-QiLwYznh_vNUSz337k0faP9Jl0dgtCIHVZ39Uyl6cA,1070
3
+ redis_dict-2.0.0.dist-info/METADATA,sha256=QWsNGQIsonqIKh_FCKa3i-U6vMjcf_WS7IwDlvQ0hHY,8302
4
+ redis_dict-2.0.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
5
+ redis_dict-2.0.0.dist-info/top_level.txt,sha256=Wyp5Xvq_imoxvu-c-Le1rbTZ3pYM5BF440H9YAcgBZ8,11
6
+ redis_dict-2.0.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.37.0)
2
+ Generator: bdist_wheel (0.40.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
redis_dict.py CHANGED
@@ -1,139 +1,426 @@
1
1
  import json
2
+ from typing import Any, Callable, Dict, Iterator, List, Tuple, Union, Optional
2
3
 
3
4
  from redis import StrictRedis
4
5
 
5
6
  from contextlib import contextmanager
6
- from future.utils import python_2_unicode_compatible
7
7
 
8
8
  SENTINEL = object()
9
9
 
10
10
 
11
- @python_2_unicode_compatible
11
+ transform_type = Dict[str, Callable[[str], Any]]
12
+ pre_transform_type = Dict[str, Callable[[Any], str]]
13
+
14
+
15
+ def _transform_tuple(val: str) -> Tuple[Any, ...]:
16
+ """
17
+ Deserialize a JSON-formatted string to a tuple.
18
+
19
+ This function takes a JSON-formatted string, deserializes it to a list, and
20
+ then converts the list to a tuple.
21
+
22
+ Args:
23
+ val (str): A JSON-formatted string representing a list.
24
+
25
+ Returns:
26
+ Tuple[Any, ...]: A tuple with the deserialized values from the input string.
27
+ """
28
+ return tuple(json.loads(val))
29
+
30
+
31
+ def _pre_transform_tuple(val: Tuple[Any, ...]) -> str:
32
+ """
33
+ Serialize a tuple to a JSON-formatted string.
34
+
35
+ This function takes a tuple, converts it to a list, and then serializes
36
+ the list to a JSON-formatted string.
37
+
38
+ Args:
39
+ val (Tuple[Any, ...]): A tuple with values to be serialized.
40
+
41
+ Returns:
42
+ str: A JSON-formatted string representing the input tuple.
43
+ """
44
+ return json.dumps(list(val))
45
+
46
+
47
+ def _transform_set(val: str) -> set[Any]:
48
+ """
49
+ Deserialize a JSON-formatted string to a set.
50
+
51
+ This function takes a JSON-formatted string, deserializes it to a list, and
52
+ then converts the list to a set.
53
+
54
+ Args:
55
+ val (str): A JSON-formatted string representing a list.
56
+
57
+ Returns:
58
+ set[Any]: A set with the deserialized values from the input string.
59
+ """
60
+ return set(json.loads(val))
61
+
62
+
63
+ def _pre_transform_set(val: set[Any]) -> str:
64
+ """
65
+ Serialize a set to a JSON-formatted string.
66
+
67
+ This function takes a set, converts it to a list, and then serializes the
68
+ list to a JSON-formatted string.
69
+
70
+ Args:
71
+ val (set[Any]): A set with values to be serialized.
72
+
73
+ Returns:
74
+ str: A JSON-formatted string representing the input set.
75
+ """
76
+ return json.dumps(list(val))
77
+
78
+
12
79
  class RedisDict:
13
- transform = {
80
+ """
81
+ A Redis-backed dictionary-like data structure with support for advanced features, such as
82
+ custom data types, pipelining, and key expiration.
83
+
84
+ This class provides a dictionary-like interface that interacts with a Redis database, allowing
85
+ for efficient storage and retrieval of key-value pairs. It supports various data types, including
86
+ strings, integers, floats, lists, dictionaries, tuples, sets, and user-defined types. The class
87
+ leverages the power of Redis pipelining to minimize network round-trip time, latency, and I/O load,
88
+ thereby optimizing performance for batch operations. Additionally, it allows for the management of
89
+ key expiration through the use of context managers.
90
+
91
+ The RedisDict class is designed to be analogous to a standard Python dictionary while providing
92
+ enhanced functionality, such as support for a wider range of data types and efficient batch operations.
93
+ It aims to offer a seamless and familiar interface for developers familiar with Python dictionaries,
94
+ enabling a smooth transition to a Redis-backed data store.
95
+
96
+ Attributes:
97
+ transform (Dict[str, Callable[[str], Any]]): A dictionary of data type transformation functions for loading data.
98
+ pre_transform (Dict[str, Callable[[Any], str]]): A dictionary of data type transformation functions for storing data.
99
+ namespace (str): A string used as a prefix for Redis keys to separate data in different namespaces.
100
+ expire (Union[int, None]): An optional expiration time for keys, in seconds.
101
+
102
+ """
103
+
104
+ transform: transform_type = {
14
105
  type('').__name__: str,
15
106
  type(1).__name__: int,
16
107
  type(0.1).__name__: float,
17
108
  type(True).__name__: lambda x: x == "True",
18
109
  type(None).__name__: lambda x: None,
19
- type(None).__name__: lambda x: None,
20
110
 
21
111
  "list": json.loads,
22
112
  "dict": json.loads,
113
+ "tuple": _transform_tuple,
114
+ type(set()).__name__: _transform_set,
23
115
  }
24
116
 
25
- pre_transform = {
117
+ pre_transform: pre_transform_type = {
26
118
  "list": json.dumps,
27
119
  "dict": json.dumps,
120
+ "tuple": _pre_transform_tuple,
121
+ type(set()).__name__: _pre_transform_set,
28
122
  }
29
123
 
30
- def __init__(self, **kwargs):
31
- self.temp_redis = None
32
- # Todo validate namespace
33
- self.namespace = kwargs.pop('namespace', '')
34
- self.expire = kwargs.pop('expire', None)
124
+ def __init__(self, **kwargs: Any):
125
+ """
126
+ Initialize a RedisDict instance.
35
127
 
36
- self.redis = StrictRedis(decode_responses=True, **kwargs)
37
- self.get_redis = self.redis
38
- self.iter = self.iterkeys()
128
+ Args:
129
+ namespace (str, optional): A prefix for keys stored in Redis.
130
+ expire (int, optional): Expiration time for keys in seconds.
131
+ **kwargs: Additional keyword arguments passed to StrictRedis.
132
+ """
133
+ self.temp_redis: Optional[StrictRedis[Any]] = None
134
+
135
+ self.namespace: str = kwargs.pop('namespace', '')
136
+ self.expire: Union[int, None] = kwargs.pop('expire', None)
39
137
 
40
- def _format_key(self, key):
138
+ self.redis: StrictRedis[Any] = StrictRedis(decode_responses=True, **kwargs)
139
+ self.get_redis: StrictRedis[Any] = self.redis
140
+ self.iter: Iterator[str] = self.iterkeys()
141
+
142
+ def _format_key(self, key: str) -> str:
143
+ """
144
+ Format a key with the namespace prefix.
145
+
146
+ Args:
147
+ key (str): The key to be formatted.
148
+
149
+ Returns:
150
+ str: The formatted key with the namespace prefix.
151
+ """
41
152
  return '{}:{}'.format(self.namespace, str(key))
42
153
 
43
- def _store(self, key, value):
154
+ def _valid_input(self, val: Any, val_type: str) -> bool:
155
+ """
156
+ Check if the input value is valid based on the specified value type.
157
+
158
+ This method ensures that the input value is within the acceptable constraints for the given
159
+ value type. For example, when the value type is "str", the method checks that the string
160
+ length does not exceed the maximum allowed size (500 MB).
161
+
162
+ Args:
163
+ val (Union[str, int, float, bool]): The input value to be validated.
164
+ val_type (str): The type of the input value ("str", "int", "float", or "bool").
165
+
166
+ Returns:
167
+ bool: True if the input value is valid, False otherwise.
168
+ """
169
+ if val_type == "str":
170
+ return len(val) < (500 * 1024 * 1024)
171
+ return True
172
+
173
+ def _store(self, key: str, value: Any) -> None:
174
+ """
175
+ Store a value in Redis with the given key.
176
+
177
+ Args:
178
+ key (str): The key to store the value.
179
+ value (Any): The value to be stored.
180
+ """
44
181
  store_type = type(value).__name__
182
+ if not self._valid_input(value, store_type) or not self._valid_input(key, "str"):
183
+ # TODO When needed, make valid_input, pass the reason, or throw a exception.
184
+ raise ValueError("Invalid input value or key size exceeded the maximum limit.")
185
+ value = self.pre_transform.get(store_type, lambda x: x)(value) # type: ignore
45
186
 
46
- value = self.pre_transform.get(store_type, lambda x: x)(value)
47
187
  store_value = '{}:{}'.format(store_type, value)
48
188
  self.redis.set(self._format_key(key), store_value, ex=self.expire)
49
189
 
50
- def _load(self, key):
190
+ def _load(self, key: str) -> Tuple[bool, Any]:
191
+ """
192
+ Load a value from Redis with the given key.
193
+
194
+ Args:
195
+ key (str): The key to retrieve the value.
196
+
197
+ Returns:
198
+ tuple: A tuple containing a boolean indicating whether the value was found and the value itself.
199
+ """
51
200
  result = self.get_redis.get(self._format_key(key))
52
201
  if result is None:
53
202
  return False, None
54
203
  t, value = result.split(':', 1)
55
204
  return True, self.transform.get(t, lambda x: x)(value)
56
205
 
57
- def _transform(self, result):
206
+ def _transform(self, result: str) -> Any:
207
+ """
208
+ Transform the result string from Redis into the appropriate Python object.
209
+
210
+ Args:
211
+ result (str): The result string from Redis.
212
+
213
+ Returns:
214
+ Any: The transformed Python object.
215
+ """
58
216
  t, value = result.split(':', 1)
59
217
  return self.transform.get(t, lambda x: x)(value)
60
218
 
61
- def add_type(self, k, v):
62
- self.trans[k] = v
63
-
64
- def __cmp__(self, other):
219
+ def add_type(self, k: str, v: Callable[[str], Any]) -> None:
220
+ """
221
+ Add a custom type to the transform mapping.
222
+
223
+ Args:
224
+ k (str): The key representing the type.
225
+ v (Callable): The transformation function for the type.
226
+ """
227
+ self.transform[k] = v
228
+
229
+ def __cmp__(self, other: Any) -> int:
230
+ """
231
+ Compare the current RedisDict with another object.
232
+
233
+ Args:
234
+ other (Any): The object to compare with.
235
+
236
+ Returns:
237
+ int: 1 if equal, -1 otherwise.
238
+ Note:
239
+ TODO add the following methods
240
+ __lt__(self, other)
241
+ __le__(self, other)
242
+ __eq__(self, other)
243
+ __ne__(self, other)
244
+ __gt__(self, other)
245
+ __ge__(self, other)
246
+ """
65
247
  if len(self) != len(other):
66
- return False
67
- for key in self:
68
- if self[key] != other[key]:
69
- return False
70
- return True
248
+ return -1
249
+ for key, value in self.iteritems():
250
+ if value != other.get(key, SENTINEL):
251
+ return -1
252
+ return 1
253
+
254
+ def __getitem__(self, item: str) -> Any:
255
+ """
256
+ Get the value associated with the given key, analogous to a dictionary.
257
+
258
+ Args:
259
+ item (str): The key to retrieve the value.
260
+
261
+ Returns:
262
+ Any: The value associated with the key.
71
263
 
72
- def __getitem__(self, item):
264
+ Raises:
265
+ KeyError: If the key is not found.
266
+ """
73
267
  found, value = self._load(item)
74
268
  if not found:
75
269
  raise KeyError(item)
76
270
  return value
77
271
 
78
- def __setitem__(self, key, value):
272
+ def __setitem__(self, key: str, value: Any) -> None:
273
+ """
274
+ Set the value associated with the given key, analogous to a dictionary.
275
+
276
+ Args:
277
+ key (str): The key to store the value.
278
+ value (Any): The value to be stored.
279
+ """
79
280
  self._store(key, value)
80
281
 
81
- def __delitem__(self, key):
282
+ def __delitem__(self, key: str) -> None:
283
+ """
284
+ Delete the value associated with the given key, analogous to a dictionary.
285
+
286
+ Args:
287
+ key (str): The key to delete the value.
288
+ """
82
289
  self.redis.delete(self._format_key(key))
83
290
 
84
- def __contains__(self, key):
85
- return self._load(key)[1]
291
+ def __contains__(self, key: str) -> bool:
292
+ """
293
+ Check if the given key exists in the RedisDict, analogous to a dictionary.
294
+
295
+ Args:
296
+ key (str): The key to check for existence.
86
297
 
87
- def __len__(self):
298
+ Returns:
299
+ bool: True if the key exists, False otherwise.
300
+ """
301
+ return self._load(key)[0]
302
+
303
+ def __len__(self) -> int:
304
+ """
305
+ Get the number of items in the RedisDict, analogous to a dictionary.
306
+
307
+ Returns:
308
+ int: The number of items in the RedisDict.
309
+ """
88
310
  return len(list(self._scan_keys()))
89
311
 
90
- def __iter__(self):
312
+ def __iter__(self) -> Iterator[str]:
313
+ """
314
+ Return an iterator over the keys of the RedisDict, analogous to a dictionary.
315
+
316
+ Returns:
317
+ Iterator[str]: An iterator over the keys of the RedisDict.
318
+ """
91
319
  self.iter = self.iterkeys()
92
320
  return self
93
321
 
94
- def __repr__(self):
322
+ def __repr__(self) -> str:
323
+ """
324
+ Create a string representation of the RedisDict.
325
+
326
+ Returns:
327
+ str: A string representation of the RedisDict.
328
+ """
95
329
  return str(self)
96
330
 
97
- def __str__(self):
331
+ def __str__(self) -> str:
332
+ """
333
+ Create a string representation of the RedisDict.
334
+
335
+ Returns:
336
+ str: A string representation of the RedisDict.
337
+ """
98
338
  return str(self.to_dict())
99
339
 
100
- def __next__(self):
340
+ def __next__(self) -> str:
341
+ """
342
+ Get the next item in the iterator.
343
+
344
+ Returns:
345
+ str: The next item in the iterator.
346
+
347
+ Raises:
348
+ StopIteration: If there are no more items.
349
+ """
101
350
  return next(self.iter)
102
351
 
103
- def next(self):
352
+ def next(self) -> str:
353
+ """
354
+ Get the next item in the iterator (alias for __next__).
355
+
356
+ Returns:
357
+ str: The next item in the iterator.
358
+
359
+ Raises:
360
+ StopIteration: If there are no more items.
361
+ """
104
362
  return self.__next__()
105
363
 
106
- def _scan_keys(self, search_term=''):
364
+ def _scan_keys(self, search_term: str = '') -> Iterator[str]:
365
+ """
366
+ Scan for Redis keys matching the given search term.
367
+
368
+ Args:
369
+ search_term (str, optional): A search term to filter keys. Defaults to ''.
370
+
371
+ Returns:
372
+ Iterator[str]: An iterator of matching Redis keys.
373
+ """
107
374
  return self.get_redis.scan_iter(match='{}:{}{}'.format(self.namespace, search_term, '*'))
108
375
 
109
- def get(self, key, default=None):
376
+ def get(self, key: str, default: Optional[Any] = None) -> Any:
377
+ """
378
+ Return the value for the given key if it exists, otherwise return the default value.
379
+ Analogous to a dictionary's get method.
380
+
381
+ Args:
382
+ key (str): The key to retrieve the value.
383
+ default (Optional[Any], optional): The value to return if the key is not found.
384
+
385
+ Returns:
386
+ Optional[Any]: The value associated with the key or the default value.
387
+ """
110
388
  found, item = self._load(key)
111
389
  if not found:
112
390
  return default
113
391
  return item
114
392
 
115
- def iterkeys(self):
116
- """Note: for pythone2 str is needed"""
393
+ def iterkeys(self) -> Iterator[str]:
394
+ """
395
+ Note: for pythone2 str is needed
396
+ """
117
397
  to_rm = len(self.namespace) + 1
118
398
  return (str(item[to_rm:]) for item in self._scan_keys())
119
399
 
120
- def iter_keys(self):
121
- """Deprecated: should be removed after major version change"""
122
- print("Warning: deprecated method. use iterkeys instead")
123
- return self.iterkeys()
124
-
125
- def key(self, search_term=''):
126
- """Note: for pythone2 str is needed"""
400
+ def key(self, search_term: str = '') -> Optional[str]:
401
+ """
402
+ Note: for pythone2 str is needed
403
+ """
127
404
  to_rm = len(self.namespace) + 1
128
405
  cursor, data = self.get_redis.scan(match='{}:{}{}'.format(self.namespace, search_term, '*'), count=1)
129
406
  for item in data:
130
407
  return str(item[to_rm:])
131
408
 
132
- def keys(self):
409
+ return None
410
+
411
+ def keys(self) -> List[str]:
412
+ """
413
+ Return a list of keys in the RedisDict, analogous to a dictionary's keys method.
414
+
415
+ Returns:
416
+ List[str]: A list of keys in the RedisDict.
417
+ """
133
418
  return list(self.iterkeys())
134
419
 
135
- def iteritems(self):
136
- """Note: for pythone2 str is needed"""
420
+ def iteritems(self) -> Iterator[Tuple[str, Any]]:
421
+ """
422
+ Note: for pythone2 str is needed
423
+ """
137
424
  to_rm = len(self.namespace) + 1
138
425
  for item in self._scan_keys():
139
426
  try:
@@ -141,13 +428,31 @@ class RedisDict:
141
428
  except KeyError:
142
429
  pass
143
430
 
144
- def items(self):
431
+ def items(self) -> List[Tuple[str, Any]]:
432
+ """
433
+ Return a list of key-value pairs (tuples) in the RedisDict, analogous to a dictionary's items method.
434
+
435
+ Returns:
436
+ List[Tuple[str, Any]]: A list of key-value pairs in the RedisDict.
437
+ """
145
438
  return list(self.iteritems())
146
439
 
147
- def values(self):
440
+ def values(self) -> List[Any]:
441
+ """
442
+ Return a list of values in the RedisDict, analogous to a dictionary's values method.
443
+
444
+ Returns:
445
+ List[Any]: A list of values in the RedisDict.
446
+ """
148
447
  return list(self.itervalues())
149
448
 
150
- def itervalues(self):
449
+ def itervalues(self) -> Iterator[Any]:
450
+ """
451
+ Iterate over the values in the RedisDict.
452
+
453
+ Returns:
454
+ Iterator[Any]: An iterator of values in the RedisDict.
455
+ """
151
456
  to_rm = len(self.namespace) + 1
152
457
  for item in self._scan_keys():
153
458
  try:
@@ -155,15 +460,44 @@ class RedisDict:
155
460
  except KeyError:
156
461
  pass
157
462
 
158
- def to_dict(self):
463
+ def to_dict(self) -> Dict[str, Any]:
464
+ """
465
+ Convert the RedisDict to a Python dictionary.
466
+
467
+ Returns:
468
+ Dict[str, Any]: A dictionary with the same key-value pairs as the RedisDict.
469
+ """
159
470
  return dict(self.items())
160
471
 
161
- def clear(self):
472
+ def clear(self) -> None:
473
+ """
474
+ Remove all key-value pairs from the RedisDict in one batch operation using pipelining.
475
+
476
+ This method mimics the behavior of the `clear` method from a standard Python dictionary.
477
+ Redis pipelining is employed to group multiple commands into a single request, minimizing
478
+ network round-trip time, latency, and I/O load, thereby enhancing the overall performance.
479
+
480
+ It is important to highlight that the clear method can be safely executed within the context of an initiated pipeline operation
481
+ """
162
482
  with self.pipeline():
163
483
  for key in self:
164
- del(self[key])
484
+ del (self[key])
165
485
 
166
- def pop(self, key, default=SENTINEL):
486
+ def pop(self, key: str, default: Union[Any, object] = SENTINEL) -> Any:
487
+ """
488
+ Remove the value associated with the given key and return it, or return the default value
489
+ if the key is not found. Analogous to a dictionary's pop method.
490
+
491
+ Args:
492
+ key (str): The key to remove the value.
493
+ default (Optional[Any], optional): The value to return if the key is not found.
494
+
495
+ Returns:
496
+ Optional[Any]: The value associated with the key or the default value.
497
+
498
+ Raises:
499
+ KeyError: If the key is not found and no default value is provided.
500
+ """
167
501
  try:
168
502
  value = self[key]
169
503
  except KeyError:
@@ -171,10 +505,20 @@ class RedisDict:
171
505
  return default
172
506
  raise
173
507
 
174
- del(self[key])
508
+ del (self[key])
175
509
  return value
176
510
 
177
- def popitem(self):
511
+ def popitem(self) -> Tuple[str, Any]:
512
+ """
513
+ Remove and return a random (key, value) pair from the RedisDict as a tuple.
514
+ This method is analogous to the `popitem` method of a standard Python dictionary.
515
+
516
+ Returns:
517
+ tuple: A tuple containing a randomly chosen (key, value) pair.
518
+
519
+ Raises:
520
+ KeyError: If RedisDict is empty.
521
+ """
178
522
  while True:
179
523
  key = self.key()
180
524
  if key is None:
@@ -184,44 +528,132 @@ class RedisDict:
184
528
  except KeyError:
185
529
  continue
186
530
 
187
- def setdefault(self, key, default_value=None):
531
+ def setdefault(self, key: str, default_value: Optional[Any] = None) -> Any:
532
+ """
533
+ Return the value associated with the given key if it exists, otherwise set the value to the
534
+ default value and return it. Analogous to a dictionary's setdefault method.
535
+
536
+ Args:
537
+ key (str): The key to retrieve the value.
538
+ default (Optional[Any], optional): The value to set if the key is not found.
539
+
540
+ Returns:
541
+ Any: The value associated with the key or the default value.
542
+ """
188
543
  found, value = self._load(key)
189
544
  if not found:
190
545
  self[key] = default_value
191
546
  return default_value
192
547
  return value
193
548
 
194
- def copy(self):
549
+ def copy(self) -> Dict[str, Any]:
550
+ """
551
+ Create a shallow copy of the RedisDict and return it as a standard Python dictionary.
552
+ This method is analogous to the `copy` method of a standard Python dictionary
553
+
554
+ Returns:
555
+ dict: A shallow copy of the RedisDict as a standard Python dictionary.
556
+
557
+ Note:
558
+ does not create a new RedisDict instance.
559
+ """
195
560
  return self.to_dict()
196
561
 
197
- def update(self, dic):
562
+ def update(self, dic: Dict[str, Any]) -> None:
563
+ """
564
+ Update the RedisDict with key-value pairs from the given mapping, analogous to a dictionary's update method.
565
+
566
+ Args:
567
+ other (Mapping[str, Any]): A mapping containing key-value pairs to update the RedisDict.
568
+ """
198
569
  with self.pipeline():
199
570
  for key, value in dic.items():
200
571
  self[key] = value
201
572
 
202
- def fromkeys(self, iterable, value=None):
203
- return {}.fromkeys(iterable, value)
573
+ def fromkeys(self, iterable: List[str], value: Optional[Any] = None) -> 'RedisDict':
574
+ """
575
+ Create a new RedisDict with keys from the provided iterable and values set to the given value.
576
+ This method is analogous to the `fromkeys` method of a standard Python dictionary, populating
577
+ the RedisDict with the keys from the iterable and setting their corresponding values to the
578
+ specified value.
579
+
580
+
581
+ Args:
582
+ iterable (List[str]): An iterable containing the keys to be added to the RedisDict.
583
+ value (Optional[Any], optional): The value to be assigned to each key in the RedisDict. Defaults to None.
204
584
 
205
- def __sizeof__(self):
585
+ Returns:
586
+ RedisDict: The current RedisDict instance, now populated with the keys from the iterable and their corresponding values.
587
+ """
588
+ for key in iterable:
589
+ self[key] = value
590
+ return self
591
+
592
+ def __sizeof__(self) -> int:
593
+ """
594
+ Return the approximate size of the RedisDict in memory, in bytes.
595
+ This method is analogous to the `__sizeof__` method of a standard Python dictionary, estimating
596
+ the memory consumption of the RedisDict based on the serialized in-memory representation.
597
+
598
+ Returns:
599
+ int: The approximate size of the RedisDict in memory, in bytes.
600
+ """
206
601
  return self.to_dict().__sizeof__()
207
602
 
208
- def chain_set(self, iterable, v):
603
+ def chain_set(self, iterable: List[str], v: Any) -> None:
604
+ """
605
+ Set a value in the RedisDict using a chain of keys.
606
+
607
+ Args:
608
+ iterable (List[str]): A list of keys representing the chain.
609
+ v (Any): The value to be set.
610
+ """
209
611
  self[':'.join(iterable)] = v
210
612
 
211
- def chain_get(self, iterable):
613
+ def chain_get(self, iterable: List[str]) -> Any:
614
+ """
615
+ Get a value from the RedisDict using a chain of keys.
616
+
617
+ Args:
618
+ iterable (List[str]): A list of keys representing the chain.
619
+
620
+ Returns:
621
+ Any: The value associated with the chain of keys.
622
+ """
212
623
  return self[':'.join(iterable)]
213
624
 
214
- def chain_del(self, iterable):
625
+ def chain_del(self, iterable: List[str]) -> None:
626
+ """
627
+ Delete a value from the RedisDict using a chain of keys.
628
+
629
+ Args:
630
+ iterable (List[str]): A list of keys representing the chain.
631
+ """
215
632
  return self.__delitem__(':'.join(iterable))
216
633
 
217
634
  @contextmanager
218
- def expire_at(self, sec_epoch):
635
+ def expire_at(self, sec_epoch: int) -> Iterator[None]:
636
+ """
637
+ Context manager to set the expiration time for keys in the RedisDict.
638
+
639
+ Args:
640
+ sec_epoch (int): The expiration time in Unix timestamp format.
641
+
642
+ Returns:
643
+ ContextManager: A context manager during which the expiration time is the time set.
644
+ """
219
645
  self.expire, temp = sec_epoch, self.expire
220
646
  yield
221
647
  self.expire = temp
222
648
 
223
649
  @contextmanager
224
- def pipeline(self):
650
+ def pipeline(self) -> Iterator[None]:
651
+ """
652
+ Context manager to create a Redis pipeline for batch operations.
653
+
654
+ Returns:
655
+ ContextManager: A context manager to create a Redis pipeline batching all operations within the context.
656
+ """
225
657
  top_level = False
226
658
  if self.temp_redis is None:
227
659
  self.redis, self.temp_redis, top_level = self.redis.pipeline(), self.redis, True
@@ -229,25 +661,61 @@ class RedisDict:
229
661
  yield
230
662
  finally:
231
663
  if top_level:
232
- _, self.temp_redis, self.redis = self.redis.execute(), None, self.temp_redis
664
+ _, self.temp_redis, self.redis = self.redis.execute(), None, self.temp_redis # type: ignore
665
+
666
+ def multi_get(self, key: str) -> List[Any]:
667
+ """
668
+ Get multiple values from the RedisDict using a shared key prefix.
669
+
670
+ Args:
671
+ key (str): The shared key prefix.
233
672
 
234
- def multi_get(self, key):
673
+ Returns:
674
+ List[Any]: A list of values associated with the key prefix.
675
+ """
235
676
  found_keys = list(self._scan_keys(key))
236
677
  if len(found_keys) == 0:
237
678
  return []
238
679
  return [self._transform(i) for i in self.redis.mget(found_keys) if i is not None]
239
680
 
240
- def multi_chain_get(self, keys):
681
+ def multi_chain_get(self, keys: List[str]) -> List[Any]:
682
+ """
683
+ Get multiple values from the RedisDict using a chain of keys.
684
+
685
+ Args:
686
+ keys (List[str]): A list of keys representing the chain.
687
+
688
+ Returns:
689
+ List[Any]: A list of values associated with the chain of keys.
690
+ """
241
691
  return self.multi_get(':'.join(keys))
242
692
 
243
- def multi_dict(self, key):
693
+ def multi_dict(self, key: str) -> Dict[str, Any]:
694
+ """
695
+ Get a dictionary of key-value pairs from the RedisDict using a shared key prefix.
696
+
697
+ Args:
698
+ key (str): The shared key prefix.
699
+
700
+ Returns:
701
+ Dict[str, Any]: A dictionary of key-value pairs associated with the key prefix.
702
+ """
244
703
  keys = list(self._scan_keys(key))
245
704
  if len(keys) == 0:
246
705
  return {}
247
706
  to_rm = keys[0].rfind(':') + 1
248
707
  return dict(zip([i[to_rm:] for i in keys], (self._transform(i) for i in self.redis.mget(keys) if i is not None)))
249
708
 
250
- def multi_del(self, key):
709
+ def multi_del(self, key: str) -> int:
710
+ """
711
+ Delete multiple values from the RedisDict using a shared key prefix.
712
+
713
+ Args:
714
+ key (str): The shared key prefix.
715
+
716
+ Returns:
717
+ int: The number of keys deleted.
718
+ """
251
719
  keys = list(self._scan_keys(key))
252
720
  if len(keys) == 0:
253
721
  return 0
@@ -1,142 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: redis-dict
3
- Version: 1.6.0
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
8
- License: MIT
9
- Platform: UNKNOWN
10
- Classifier: Development Status :: 5 - Production/Stable
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Topic :: Database
13
- Classifier: Topic :: System :: Distributed Computing
14
- Classifier: License :: OSI Approved :: MIT License
15
- Classifier: Programming Language :: Python :: 2
16
- Classifier: Programming Language :: Python :: 2.7
17
- Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.3
19
- Classifier: Programming Language :: Python :: 3.4
20
- Classifier: Programming Language :: Python :: 3.5
21
- Classifier: Programming Language :: Python :: 3.6
22
- Classifier: Programming Language :: Python :: 3.7
23
- Description-Content-Type: text/markdown
24
- License-File: LICENSE
25
- Requires-Dist: redis
26
- Requires-Dist: future
27
-
28
- # redis-dict
29
- [![Build Status](https://travis-ci.com/Attumm/redis-dict.svg?branch=main)](https://travis-ci.com/Attumm/redis-dict)
30
- [![Downloads](https://pepy.tech/badge/redis-dict/month)](https://pepy.tech/project/redis-dict)
31
-
32
- A Python dictionary with Redis as the storage back-end.
33
- Redis is a great database for all kinds of environments; from simple to complex.
34
- redis-dict tries to make using Redis as simple as using a dictionary.
35
- redis-dict stores data in Redis with key-values, this is according to [Redis best practices](https://redislabs.com/redis-best-practices/data-storage-patterns/).
36
- This also allows other non-Python programs to access the data stored in Redis.
37
-
38
- redis-dict was built out of the necessity of working with incredibly large data sets.
39
- It had to be possible to only send or receive the required data over the wire and into memory.
40
- With redis-dict it's as simple as a dictionary.
41
-
42
- ## Example
43
- Redis is a really fast database if used right.
44
- redis-dict uses Redis for key-value storage.
45
- ```python
46
- >>> from redis_dict import RedisDict
47
- >>> dic = RedisDict(namespace='bar')
48
- >>> 'foo' in dic
49
- False
50
- >>> dic['foo'] = 42
51
- >>> dic['foo']
52
- 42
53
- >>> 'foo' in dic
54
- True
55
- >>> dic["baz"] = "a string"
56
- >>> print(dic)
57
- {'foo': 42, 'baz': 'a string'}
58
-
59
- ```
60
- In Redis our example looks like this.
61
- ```
62
- 127.0.0.1:6379> KEYS "*"
63
- 1) "bar:foo"
64
- 2) "bar:baz"
65
- ```
66
-
67
- ## Features
68
-
69
- #### Dictionary
70
- redis-dict can be used as a drop-in replacement for a normal dictionary as long as no datastructures are used by reference.
71
- i.e. no nested layout
72
- e.g. values such list, instance and other dictionaries.
73
- When used with supported types, it can be used a drop-in for a normal dictionary.
74
-
75
- redis-dict has all the methods and behavior of a normal dictionary.
76
-
77
- #### Types
78
- Several Python types can be saved and retrieved as the same type.
79
- As of writing, redis-dict supports the following types.
80
- * String
81
- * Integer
82
- * Float
83
- * Boolean
84
- * None
85
-
86
- #### Other Types not fully supported
87
- Experimental support for the following types.
88
- List, Dictionary supported provided with json serialization.
89
- If your list or Dictionary can be serializate by json this feature will work.
90
-
91
- Although is not the best solution, it could work for many usecases. So use at your discretion.
92
- If there is need for other referenced types open issue on github.
93
- * List
94
- * Dictionary
95
-
96
- #### Expire
97
- Redis has the great feature of expiring keys. This feature is supported.
98
- 1. You can set the default expiration when creating a redis-dict instance.
99
- ```python
100
- r_dic = RedisDict(namespace='app_name', expire=10)
101
- ```
102
- 2. With a context manager you can temporarily set the default expiration time.
103
- Defaults to None (does not expire)
104
- ```python
105
- seconds = 60
106
- with r_dic.expire_at(seconds):
107
- r_dic['gone_in_sixty_seconds'] = 'foo'
108
- ```
109
-
110
- #### Batching
111
- Batch your requests by using Pipeline, as easy as using a context manager
112
-
113
- Example storing the first ten items of Fibonacci, with one round trip to Redis.
114
- ```python
115
- def fib(n):
116
- a, b = 0, 1
117
- for _ in range(n):
118
- yield a
119
- a, b = (a+b), a
120
-
121
- with r_dic.pipeline():
122
- for index, item in enumerate(fib(10)):
123
- r_dic[str(index)] = item
124
- ```
125
-
126
- #### Namespaces
127
- redis-dict uses namespaces by default. This allows you to have an instance of redis-dict per project.
128
- When looking directly at the data in Redis, this gives you the advantage of directly seeing which data belongs to which app.
129
- This also has the advantage that it is less likely for apps to collide with keys, which is a difficult problem to debug.
130
-
131
- ### More Examples
132
- More complex examples of redis-dict can be found in the tests. All functionality is tested in either[ `assert_test.py` (here)](https://github.com/Attumm/redis-dict/blob/master/assert_test.py#L1) or in the [unit tests (here)](https://github.com/Attumm/redis-dict/blob/master/tests.py#L1).
133
-
134
- ## Installation
135
- ```sh
136
- pip install redis-dict
137
- ```
138
-
139
- ### Note
140
- This project is used by different companies in production.
141
-
142
-
@@ -1,6 +0,0 @@
1
- redis_dict.py,sha256=6l6ZnFUBDKxYgNpyZXww1RiEB-uDgMV1JwocxqSFfjs,7084
2
- redis_dict-1.6.0.dist-info/LICENSE,sha256=-QiLwYznh_vNUSz337k0faP9Jl0dgtCIHVZ39Uyl6cA,1070
3
- redis_dict-1.6.0.dist-info/METADATA,sha256=CmY5CmO2B5sq4t7hS4NSnMdX9DxvEFBjVzyiK-R3arM,4950
4
- redis_dict-1.6.0.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
5
- redis_dict-1.6.0.dist-info/top_level.txt,sha256=Wyp5Xvq_imoxvu-c-Le1rbTZ3pYM5BF440H9YAcgBZ8,11
6
- redis_dict-1.6.0.dist-info/RECORD,,