persidict 0.34.2__py3-none-any.whl → 0.35.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of persidict might be problematic. Click here for more details.
- persidict/file_dir_dict.py +259 -53
- persidict/jokers.py +52 -12
- persidict/overlapping_multi_dict.py +99 -36
- persidict/persi_dict.py +293 -105
- persidict/s3_dict.py +167 -38
- persidict/safe_chars.py +22 -3
- persidict/safe_str_tuple.py +142 -34
- persidict/safe_str_tuple_signing.py +140 -36
- persidict/write_once_dict.py +180 -38
- {persidict-0.34.2.dist-info → persidict-0.35.0.dist-info}/METADATA +4 -5
- persidict-0.35.0.dist-info/RECORD +13 -0
- {persidict-0.34.2.dist-info → persidict-0.35.0.dist-info}/WHEEL +1 -1
- persidict/.DS_Store +0 -0
- persidict-0.34.2.dist-info/RECORD +0 -14
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
"""Sign and unsign SafeStrTuple elements with deterministic suffixes.
|
|
2
|
+
|
|
3
|
+
This module provides helpers to add or remove short, deterministic hash
|
|
4
|
+
suffixes to every string element of a SafeStrTuple. These suffixes are used
|
|
5
|
+
to avoid collisions on case-insensitive filesystems (e.g., macOS HFS) while
|
|
6
|
+
keeping keys stable and portable.
|
|
7
|
+
|
|
8
|
+
Functions:
|
|
9
|
+
sign_safe_str_tuple(str_seq, digest_len):
|
|
10
|
+
Return a new SafeStrTuple where each element is suffixed with an
|
|
11
|
+
underscore and a base32-encoded MD5 digest fragment of length
|
|
12
|
+
``digest_len``. If a correct suffix is already present, it is not
|
|
13
|
+
duplicated.
|
|
14
|
+
unsign_safe_str_tuple(str_seq, digest_len):
|
|
15
|
+
Return a new SafeStrTuple where a previously added suffix of length
|
|
16
|
+
``digest_len`` is removed from each element, when detected.
|
|
10
17
|
"""
|
|
11
18
|
|
|
12
19
|
import base64
|
|
@@ -14,12 +21,31 @@ import hashlib
|
|
|
14
21
|
from .safe_str_tuple import SafeStrTuple
|
|
15
22
|
|
|
16
23
|
|
|
17
|
-
def _create_signature_suffix(input_str:str, digest_len:int) -> str:
|
|
18
|
-
"""
|
|
24
|
+
def _create_signature_suffix(input_str: str, digest_len: int) -> str:
|
|
25
|
+
"""Create a short, deterministic hash suffix for a string.
|
|
26
|
+
|
|
27
|
+
The suffix format is ``_<b32(md5(input))[:digest_len].lower()>``. For
|
|
28
|
+
``digest_len == 0`` an empty string is returned.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
input_str: Input string to sign.
|
|
32
|
+
digest_len: Number of base32 characters from the MD5 digest to include.
|
|
33
|
+
Must be non-negative. A value of 0 disables suffixing.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
str: The computed suffix to append (may be an empty string).
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
TypeError: If input_str is not a str or digest_len is not an int.
|
|
40
|
+
ValueError: If digest_len is negative.
|
|
41
|
+
"""
|
|
19
42
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
43
|
+
if not isinstance(input_str, str):
|
|
44
|
+
raise TypeError(f"input_str must be str, got {type(input_str)!r}")
|
|
45
|
+
if not isinstance(digest_len, int):
|
|
46
|
+
raise TypeError(f"digest_len must be int, got {type(digest_len)!r}")
|
|
47
|
+
if digest_len < 0:
|
|
48
|
+
raise ValueError(f"digest_len must be >= 0, got {digest_len}")
|
|
23
49
|
|
|
24
50
|
if digest_len == 0:
|
|
25
51
|
return ""
|
|
@@ -32,12 +58,31 @@ def _create_signature_suffix(input_str:str, digest_len:int) -> str:
|
|
|
32
58
|
return suffix
|
|
33
59
|
|
|
34
60
|
|
|
35
|
-
def _add_signature_suffix_if_absent(input_str:str, digest_len:int) -> str:
|
|
36
|
-
"""
|
|
61
|
+
def _add_signature_suffix_if_absent(input_str: str, digest_len: int) -> str:
|
|
62
|
+
"""Add the hash signature suffix if it's not already present.
|
|
37
63
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
64
|
+
If the input already ends with the exact suffix calculated from its
|
|
65
|
+
unsuffixed part, it is returned unchanged.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
input_str: The string to sign.
|
|
69
|
+
digest_len: Length of the digest fragment to use; 0 leaves the string
|
|
70
|
+
unchanged.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
str: The original or suffixed string.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
TypeError: If input_str is not a str or digest_len is not an int.
|
|
77
|
+
ValueError: If digest_len is negative.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
if not isinstance(input_str, str):
|
|
81
|
+
raise TypeError(f"input_str must be str, got {type(input_str)!r}")
|
|
82
|
+
if not isinstance(digest_len, int):
|
|
83
|
+
raise TypeError(f"digest_len must be int, got {type(digest_len)!r}")
|
|
84
|
+
if digest_len < 0:
|
|
85
|
+
raise ValueError(f"digest_len must be >= 0, got {digest_len}")
|
|
41
86
|
|
|
42
87
|
if digest_len == 0:
|
|
43
88
|
return input_str
|
|
@@ -52,10 +97,18 @@ def _add_signature_suffix_if_absent(input_str:str, digest_len:int) -> str:
|
|
|
52
97
|
|
|
53
98
|
|
|
54
99
|
def _add_all_suffixes_if_absent(
|
|
55
|
-
str_seq:SafeStrTuple
|
|
56
|
-
|
|
100
|
+
str_seq: SafeStrTuple,
|
|
101
|
+
digest_len: int,
|
|
57
102
|
) -> SafeStrTuple:
|
|
58
|
-
"""
|
|
103
|
+
"""Return a new SafeStrTuple with suffixes added to each element.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
str_seq: Input sequence convertible to SafeStrTuple.
|
|
107
|
+
digest_len: Digest fragment length; 0 results in a no-op.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
SafeStrTuple: The suffixed sequence.
|
|
111
|
+
"""
|
|
59
112
|
|
|
60
113
|
str_seq = SafeStrTuple(str_seq)
|
|
61
114
|
|
|
@@ -68,12 +121,32 @@ def _add_all_suffixes_if_absent(
|
|
|
68
121
|
return new_seq
|
|
69
122
|
|
|
70
123
|
|
|
71
|
-
def _remove_signature_suffix_if_present(input_str:str, digest_len:int) -> str:
|
|
72
|
-
"""
|
|
124
|
+
def _remove_signature_suffix_if_present(input_str: str, digest_len: int) -> str:
|
|
125
|
+
"""Remove the hash signature suffix if it is detected.
|
|
73
126
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
127
|
+
Detection is performed by recomputing the expected suffix from the
|
|
128
|
+
unsuffixed portion and comparing it to the current ending.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
input_str: The possibly suffixed string.
|
|
132
|
+
digest_len: Digest fragment length used during signing; 0 leaves the
|
|
133
|
+
string unchanged.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
str: The original string without the suffix if detected; otherwise the
|
|
137
|
+
original string.
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
TypeError: If input_str is not a str or digest_len is not an int.
|
|
141
|
+
ValueError: If digest_len is negative.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
if not isinstance(input_str, str):
|
|
145
|
+
raise TypeError(f"input_str must be str, got {type(input_str)!r}")
|
|
146
|
+
if not isinstance(digest_len, int):
|
|
147
|
+
raise TypeError(f"digest_len must be int, got {type(digest_len)!r}")
|
|
148
|
+
if digest_len < 0:
|
|
149
|
+
raise ValueError(f"digest_len must be >= 0, got {digest_len}")
|
|
77
150
|
|
|
78
151
|
if digest_len == 0:
|
|
79
152
|
return input_str
|
|
@@ -88,10 +161,19 @@ def _remove_signature_suffix_if_present(input_str:str, digest_len:int) -> str:
|
|
|
88
161
|
|
|
89
162
|
|
|
90
163
|
def _remove_all_signature_suffixes_if_present(
|
|
91
|
-
str_seq:SafeStrTuple
|
|
92
|
-
|
|
164
|
+
str_seq: SafeStrTuple,
|
|
165
|
+
digest_len: int,
|
|
93
166
|
) -> SafeStrTuple:
|
|
94
|
-
"""
|
|
167
|
+
"""Return a new SafeStrTuple with detected suffixes removed from elements.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
str_seq: Input sequence convertible to SafeStrTuple.
|
|
171
|
+
digest_len: Digest fragment length used during signing; 0 results in a
|
|
172
|
+
no-op.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
SafeStrTuple: The unsigned sequence.
|
|
176
|
+
"""
|
|
95
177
|
|
|
96
178
|
str_seq = SafeStrTuple(str_seq)
|
|
97
179
|
|
|
@@ -108,10 +190,22 @@ def _remove_all_signature_suffixes_if_present(
|
|
|
108
190
|
return new_seq
|
|
109
191
|
|
|
110
192
|
|
|
111
|
-
def sign_safe_str_tuple(
|
|
112
|
-
,
|
|
193
|
+
def sign_safe_str_tuple(
|
|
194
|
+
str_seq: SafeStrTuple,
|
|
195
|
+
digest_len: int,
|
|
113
196
|
) -> SafeStrTuple:
|
|
114
|
-
"""
|
|
197
|
+
"""Return a SafeStrTuple with signature suffixes added to all elements.
|
|
198
|
+
|
|
199
|
+
This is the public function for signing keys used by persistent dicts.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
str_seq: Input sequence convertible to SafeStrTuple.
|
|
203
|
+
digest_len: Number of characters from the base32 digest to append. Use
|
|
204
|
+
0 to disable suffixing.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
SafeStrTuple: The suffixed sequence.
|
|
208
|
+
"""
|
|
115
209
|
|
|
116
210
|
str_seq = SafeStrTuple(str_seq)
|
|
117
211
|
|
|
@@ -120,10 +214,20 @@ def sign_safe_str_tuple(str_seq:SafeStrTuple
|
|
|
120
214
|
return str_seq
|
|
121
215
|
|
|
122
216
|
|
|
123
|
-
def unsign_safe_str_tuple(
|
|
124
|
-
,
|
|
217
|
+
def unsign_safe_str_tuple(
|
|
218
|
+
str_seq: SafeStrTuple,
|
|
219
|
+
digest_len: int,
|
|
125
220
|
) -> SafeStrTuple:
|
|
126
|
-
"""
|
|
221
|
+
"""Return a SafeStrTuple with detected signature suffixes removed.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
str_seq: Input sequence convertible to SafeStrTuple.
|
|
225
|
+
digest_len: Number of characters that were appended during signing. Use
|
|
226
|
+
0 for a no-op.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
SafeStrTuple: The unsigned sequence.
|
|
230
|
+
"""
|
|
127
231
|
|
|
128
232
|
str_seq = SafeStrTuple(str_seq)
|
|
129
233
|
|
persidict/write_once_dict.py
CHANGED
|
@@ -16,11 +16,20 @@ from typing import Any
|
|
|
16
16
|
import joblib.hashing
|
|
17
17
|
from .persi_dict import PersiDictKey
|
|
18
18
|
|
|
19
|
-
def _get_md5_signature(x:Any) -> str:
|
|
20
|
-
"""
|
|
19
|
+
def _get_md5_signature(x: Any) -> str:
|
|
20
|
+
"""Compute an MD5 signature for an arbitrary Python object.
|
|
21
21
|
|
|
22
|
-
Uses joblib's Hasher (or NumpyHasher
|
|
23
|
-
except for NumPy arrays
|
|
22
|
+
Uses joblib's Hasher (or NumpyHasher when NumPy is available). joblib
|
|
23
|
+
relies on Pickle for serialization, except for NumPy arrays which are
|
|
24
|
+
handled by optimized routines. The resulting digest is returned as a
|
|
25
|
+
lower-case base16 string.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
x: Any serializable Python object. NumPy arrays are supported with
|
|
29
|
+
specialized hashing.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
str: The base16 MD5 hash of the object.
|
|
24
33
|
"""
|
|
25
34
|
if 'numpy' in sys.modules:
|
|
26
35
|
hasher = joblib.hashing.NumpyHasher(hash_name='md5')
|
|
@@ -30,12 +39,26 @@ def _get_md5_signature(x:Any) -> str:
|
|
|
30
39
|
return str(hash_signature)
|
|
31
40
|
|
|
32
41
|
class WriteOnceDict(PersiDict):
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
"""Dictionary wrapper that preserves the first value written for each key.
|
|
43
|
+
|
|
44
|
+
Subsequent writes to an existing key are allowed but ignored as they are
|
|
45
|
+
expected to have exactly the same value. They are randomly checked
|
|
46
|
+
against the original value to ensure consistency. If a randomly triggered
|
|
47
|
+
check finds a difference, a ValueError is raised. The probability of
|
|
48
|
+
performing a check is controlled by ``p_consistency_checks``.
|
|
49
|
+
|
|
50
|
+
This is useful in concurrent or distributed settings where the same value
|
|
51
|
+
is assumed to be assigned repeatedly to the same key,
|
|
52
|
+
and you want to check this assumption (detect divergent values)
|
|
53
|
+
without paying the full cost of always comparing values.
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
p_consistency_checks (float): Probability in [0, 1] of performing a
|
|
57
|
+
consistency check for a key that has been previously set.
|
|
58
|
+
consistency_checks_attempted (int): Number of checks that were
|
|
59
|
+
attempted.
|
|
60
|
+
consistency_checks_passed (int): Number of checks that succeeded.
|
|
61
|
+
consistency_checks_failed (int): Derived as attempted - passed.
|
|
39
62
|
|
|
40
63
|
"""
|
|
41
64
|
_wrapped_dict: PersiDict
|
|
@@ -43,18 +66,34 @@ class WriteOnceDict(PersiDict):
|
|
|
43
66
|
_consistency_checks_attempted: int
|
|
44
67
|
_consistency_checks_passed: int
|
|
45
68
|
|
|
46
|
-
def __init__(self
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
def __init__(self,
|
|
70
|
+
wrapped_dict: PersiDict | None = None,
|
|
71
|
+
p_consistency_checks: float | None = None):
|
|
72
|
+
"""Initialize a WriteOnceDict.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
wrapped_dict: The underlying persistent dictionary to wrap. If not
|
|
76
|
+
provided, a FileDirDict with immutable_items=True is created.
|
|
77
|
+
p_consistency_checks: Probability in [0, 1] to perform a
|
|
78
|
+
consistency check when a key already exists. ``None`` means 0.0
|
|
79
|
+
(disabled).
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
TypeError: If ``wrapped_dict`` is not a PersiDict instance.
|
|
83
|
+
ValueError: If ``wrapped_dict`` does not enforce immutable items.
|
|
84
|
+
"""
|
|
49
85
|
if wrapped_dict is None:
|
|
50
|
-
wrapped_dict = FileDirDict(immutable_items
|
|
51
|
-
|
|
52
|
-
|
|
86
|
+
wrapped_dict = FileDirDict(immutable_items=True)
|
|
87
|
+
if not isinstance(wrapped_dict, PersiDict):
|
|
88
|
+
raise TypeError("wrapped_dict must be a PersiDict instance")
|
|
89
|
+
if wrapped_dict.immutable_items is not True:
|
|
90
|
+
raise ValueError("wrapped_dict must be append-only "
|
|
91
|
+
"(immutable_items==True)")
|
|
53
92
|
self.p_consistency_checks = p_consistency_checks
|
|
54
|
-
PersiDict.__init__(self
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
93
|
+
PersiDict.__init__(self,
|
|
94
|
+
base_class_for_values=wrapped_dict.base_class_for_values,
|
|
95
|
+
immutable_items=True,
|
|
96
|
+
digest_len=wrapped_dict.digest_len)
|
|
58
97
|
self._wrapped_dict = wrapped_dict
|
|
59
98
|
self._consistency_checks_passed = 0
|
|
60
99
|
self._consistency_checks_attempted = 0
|
|
@@ -62,12 +101,27 @@ class WriteOnceDict(PersiDict):
|
|
|
62
101
|
|
|
63
102
|
@property
|
|
64
103
|
def p_consistency_checks(self) -> float:
|
|
65
|
-
"""
|
|
104
|
+
"""Probability of checking a new value against the first value stored.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
float: Probability in [0, 1].
|
|
108
|
+
"""
|
|
66
109
|
return self._p_consistency_checks
|
|
67
110
|
|
|
68
111
|
|
|
69
112
|
@p_consistency_checks.setter
|
|
70
|
-
def p_consistency_checks(self, value: float|None|KeepCurrentFlag) -> None:
|
|
113
|
+
def p_consistency_checks(self, value: float | None | KeepCurrentFlag) -> None:
|
|
114
|
+
"""Set the probability of performing consistency checks.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
value: Probability in [0, 1]. ``None`` is treated as 0.0.
|
|
118
|
+
``KEEP_CURRENT`` can be used only after initialization to
|
|
119
|
+
preserve the current value.
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
ValueError: If used with ``KEEP_CURRENT`` during initialization or
|
|
123
|
+
if ``value`` is outside [0, 1].
|
|
124
|
+
"""
|
|
71
125
|
if value is KEEP_CURRENT:
|
|
72
126
|
if hasattr(self, '_p_consistency_checks'):
|
|
73
127
|
return
|
|
@@ -85,34 +139,65 @@ class WriteOnceDict(PersiDict):
|
|
|
85
139
|
|
|
86
140
|
@property
|
|
87
141
|
def consistency_checks_failed(self) -> int:
|
|
88
|
-
"""
|
|
89
|
-
|
|
142
|
+
"""Number of failed consistency checks.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
int: Failed checks (attempted - passed).
|
|
146
|
+
"""
|
|
147
|
+
return (self._consistency_checks_attempted
|
|
148
|
+
- self._consistency_checks_passed)
|
|
90
149
|
|
|
91
150
|
|
|
92
151
|
@property
|
|
93
152
|
def consistency_checks_attempted(self) -> int:
|
|
94
|
-
"""
|
|
153
|
+
"""Number of attempted consistency checks.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
int: Attempted checks counter.
|
|
157
|
+
"""
|
|
95
158
|
return self._consistency_checks_attempted
|
|
96
159
|
|
|
97
160
|
|
|
98
161
|
@property
|
|
99
162
|
def consistency_checks_passed(self) -> int:
|
|
100
|
-
"""
|
|
163
|
+
"""Number of successful consistency checks.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
int: Passed checks counter.
|
|
167
|
+
"""
|
|
101
168
|
return self._consistency_checks_passed
|
|
102
169
|
|
|
103
170
|
|
|
104
171
|
def get_params(self):
|
|
172
|
+
"""Return parameterization of this instance.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
dict: A dictionary with keys 'wrapped_dict' and
|
|
176
|
+
'p_consistency_checks', sorted by keys for deterministic
|
|
177
|
+
comparison/serialization.
|
|
178
|
+
"""
|
|
105
179
|
params = dict(
|
|
106
|
-
wrapped_dict
|
|
107
|
-
p_consistency_checks
|
|
180
|
+
wrapped_dict=self._wrapped_dict,
|
|
181
|
+
p_consistency_checks=self.p_consistency_checks)
|
|
108
182
|
sorted_params = sort_dict_by_keys(params)
|
|
109
183
|
return sorted_params
|
|
110
184
|
|
|
111
|
-
def __setitem__(self, key, value):
|
|
112
|
-
"""
|
|
185
|
+
def __setitem__(self, key:PersiDictKey, value):
|
|
186
|
+
"""Set a value for a key, preserving the first assignment.
|
|
187
|
+
|
|
188
|
+
If the key is new, the value is stored. If the key already exists,
|
|
189
|
+
a probabilistic consistency check may be performed to ensure the new
|
|
190
|
+
value matches the originally stored value. If a check is performed and
|
|
191
|
+
the values differ, a ValueError is raised.
|
|
113
192
|
|
|
114
|
-
|
|
115
|
-
|
|
193
|
+
Args:
|
|
194
|
+
key: key (string or sequence of strings or SafeStrTuple)
|
|
195
|
+
value: Value to store.
|
|
196
|
+
|
|
197
|
+
Raises:
|
|
198
|
+
KeyError: If the wrapped dict failed to set a new key unexpectedly.
|
|
199
|
+
ValueError: If a consistency check is triggered and the new value
|
|
200
|
+
differs from the original value for the key.
|
|
116
201
|
"""
|
|
117
202
|
check_needed = False
|
|
118
203
|
|
|
@@ -149,34 +234,91 @@ class WriteOnceDict(PersiDict):
|
|
|
149
234
|
+ f"which is not allowed. Details here: {diff_dict} ")
|
|
150
235
|
self._consistency_checks_passed += 1
|
|
151
236
|
|
|
152
|
-
def __contains__(self, item):
|
|
237
|
+
def __contains__(self, item:PersiDictKey):
|
|
238
|
+
"""Check if a key exists in the dictionary.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
item: Key to check.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
bool: True if the key exists, False otherwise.
|
|
245
|
+
"""
|
|
153
246
|
return item in self._wrapped_dict
|
|
154
247
|
|
|
155
|
-
def __getitem__(self, key):
|
|
248
|
+
def __getitem__(self, key:PersiDictKey):
|
|
249
|
+
"""Retrieve a value by key.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
key: Key to look up.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Any: Stored value.
|
|
256
|
+
"""
|
|
156
257
|
return self._wrapped_dict[key]
|
|
157
258
|
|
|
158
259
|
def __len__(self):
|
|
260
|
+
"""Return the number of items stored.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
int: Number of key-value pairs.
|
|
264
|
+
"""
|
|
159
265
|
return len(self._wrapped_dict)
|
|
160
266
|
|
|
161
|
-
def _generic_iter(self, iter_type: str):
|
|
267
|
+
def _generic_iter(self, iter_type: set[str]):
|
|
268
|
+
"""Delegate iteration to the wrapped dict.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
iter_type: tType of iterator: 'items' and/or 'keys' and/or 'timestamps'.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Any: Iterator from the wrapped dictionary.
|
|
275
|
+
"""
|
|
162
276
|
return self._wrapped_dict._generic_iter(iter_type)
|
|
163
277
|
|
|
164
|
-
def timestamp(self, key:PersiDictKey) -> float:
|
|
278
|
+
def timestamp(self, key: PersiDictKey) -> float:
|
|
279
|
+
"""Return the timestamp for a given key.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
key: Key for which to retrieve the timestamp.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
float: POSIX timestamp (seconds since epoch) of the item's last
|
|
286
|
+
modification as tracked by the wrapped dict.
|
|
287
|
+
"""
|
|
165
288
|
return self._wrapped_dict.timestamp(key)
|
|
166
289
|
|
|
167
290
|
def __getattr__(self, name):
|
|
168
|
-
|
|
291
|
+
"""Forward attribute access to the wrapped object.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
name: Attribute name.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Any: Attribute value from the wrapped dict.
|
|
298
|
+
"""
|
|
169
299
|
return getattr(self._wrapped_dict, name)
|
|
170
300
|
|
|
171
301
|
@property
|
|
172
302
|
def base_dir(self):
|
|
303
|
+
"""Base directory of the wrapped dict (if applicable)."""
|
|
173
304
|
return self._wrapped_dict.base_dir
|
|
174
305
|
|
|
175
306
|
@property
|
|
176
307
|
def base_url(self):
|
|
308
|
+
"""Base URL of the wrapped dict (if applicable)."""
|
|
177
309
|
return self._wrapped_dict.base_url
|
|
178
310
|
|
|
179
|
-
def get_subdict(self, prefix_key:PersiDictKey) -> WriteOnceDict:
|
|
311
|
+
def get_subdict(self, prefix_key: PersiDictKey) -> WriteOnceDict:
|
|
312
|
+
"""Return a WriteOnceDict view over a sub-keyspace.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
prefix_key: Prefix identifying the sub-dictionary.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
WriteOnceDict: A new WriteOnceDict wrapping the corresponding
|
|
319
|
+
sub-dictionary of the underlying store, sharing the same
|
|
320
|
+
p_consistency_checks probability.
|
|
321
|
+
"""
|
|
180
322
|
subdict = self._wrapped_dict.get_subdict(prefix_key)
|
|
181
323
|
result = WriteOnceDict(subdict, self.p_consistency_checks)
|
|
182
324
|
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: persidict
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.35.0
|
|
4
4
|
Summary: Simple persistent key-value store for Python. Values are stored as files on a disk or as S3 objects on AWS cloud.
|
|
5
5
|
Keywords: persistence,dicts,distributed,parallel
|
|
6
6
|
Author: Vlad (Volodymyr) Pavlov
|
|
@@ -158,10 +158,9 @@ that simultaneously work with the same instance of a dictionary.
|
|
|
158
158
|
* Insertion order is not preserved.
|
|
159
159
|
* You cannot assign initial key-value pairs to a dictionary in its constructor.
|
|
160
160
|
* `PersiDict` API has additional methods `delete_if_exists()`, `timestamp()`,
|
|
161
|
-
`get_subdict()`, `subdicts()`, `
|
|
162
|
-
`oldest_keys()`, `newest_values()`, `oldest_values()`,
|
|
163
|
-
`get_params()`,
|
|
164
|
-
which are not available in native Python dicts.
|
|
161
|
+
`get_subdict()`, `subdicts()`, `random_key()`, `newest_keys()`,
|
|
162
|
+
`oldest_keys()`, `newest_values()`, `oldest_values()`, and
|
|
163
|
+
`get_params()`, which are not available in native Python dicts.
|
|
165
164
|
* You can use KEEP_CURRENT constant as a fake new value
|
|
166
165
|
to avoid actually setting/updating a value. Or DELETE_CURRENT as
|
|
167
166
|
a fake new value to delete the previous value from a dictionary.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
persidict/__init__.py,sha256=CDOSJGgCnyRTkGUTzaeg3Cqsxwx0-0EFieOtldXwAls,1380
|
|
2
|
+
persidict/file_dir_dict.py,sha256=gvCyk_kp_3AC-zkHuSj-0lM4hf_fBK6iz3ffGQ7jtvU,25757
|
|
3
|
+
persidict/jokers.py,sha256=H2MzKllvgm7t2sjX3GaRNLngOiG2ohE-lv0B3g3J1SQ,2710
|
|
4
|
+
persidict/overlapping_multi_dict.py,sha256=v1c2kf3Bhm2Dh6SGEsRV58hQI11YX9ZPxyrDV_1d5s8,5360
|
|
5
|
+
persidict/persi_dict.py,sha256=DIMQaY4gE8NSYTlHlk9rfOJJEYUuLV8kmQ-gc474py4,20052
|
|
6
|
+
persidict/s3_dict.py,sha256=VKDqY9sASffeXtfbavVWk8-umrioIG5Xq57Qqg1wPH4,21522
|
|
7
|
+
persidict/safe_chars.py,sha256=9Qy24fu2dmiJOdmCF8mKZULfQaRp7H4oxfgDXeLgogI,1160
|
|
8
|
+
persidict/safe_str_tuple.py,sha256=YBTcYjUKIffznOawXb9xKjz4HaKdklrgyVtegJFmr5w,7202
|
|
9
|
+
persidict/safe_str_tuple_signing.py,sha256=RQAj4fnpRVaOe0KpwLler1UTaeNOgXCQpU3t80ixtxg,7493
|
|
10
|
+
persidict/write_once_dict.py,sha256=-lPQ_yuU62pczHT0BYO6SFbiZBKFq8Tj9ln3jCzNDzA,11443
|
|
11
|
+
persidict-0.35.0.dist-info/WHEEL,sha256=F3mArEuDT3LDFEqo9fCiUx6ISLN64aIhcGSiIwtu4r8,79
|
|
12
|
+
persidict-0.35.0.dist-info/METADATA,sha256=oo_YL1_W4ux3lTJZvREQPcin1ILJmvxtMDlWuITUwCw,9262
|
|
13
|
+
persidict-0.35.0.dist-info/RECORD,,
|
persidict/.DS_Store
DELETED
|
Binary file
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
persidict/.DS_Store,sha256=1lFlJ5EFymdzGAUAaI30vcaaLHt3F1LwpG7xILf9jsM,6148
|
|
2
|
-
persidict/__init__.py,sha256=CDOSJGgCnyRTkGUTzaeg3Cqsxwx0-0EFieOtldXwAls,1380
|
|
3
|
-
persidict/file_dir_dict.py,sha256=Dr4gdIC5uqykRgNca1pI_M_jEd_9FGjys0BvzAJR0JU,17804
|
|
4
|
-
persidict/jokers.py,sha256=kX4bE-jKWTM2ki7JOmm_2uJS8zm8u6InZ_V12xo2ImI,1436
|
|
5
|
-
persidict/overlapping_multi_dict.py,sha256=a-lUbmY15_HrDq6jSIt8F8tJboqbeYiuRQeW4elf_oU,2663
|
|
6
|
-
persidict/persi_dict.py,sha256=SF6aWs6kCeeW-bZ9HJwx0sPX7Xav_aURqeSZ-j5quv0,14266
|
|
7
|
-
persidict/s3_dict.py,sha256=j_Fb73wbkOrsneu3a48VUpDDXqVSTtEa0sRI-c_Kbjk,16104
|
|
8
|
-
persidict/safe_chars.py,sha256=HjK1MwROYy_U9ui-rhg1i3nGkj52K4OFWD-wCCcnJ7Y,536
|
|
9
|
-
persidict/safe_str_tuple.py,sha256=xyIzxlCKmvnNHkFFKWtcBREefxZ0-HLxoH_epYDt8qg,3719
|
|
10
|
-
persidict/safe_str_tuple_signing.py,sha256=5uCjAVZRqOou-KpDZw-Exboc3-3vuayJMqrrt8aZ0ck,3742
|
|
11
|
-
persidict/write_once_dict.py,sha256=-XHQhTEdvPHTKqLXK4WWW0k5cFitkzalVJC1n4BbKGo,6496
|
|
12
|
-
persidict-0.34.2.dist-info/WHEEL,sha256=Jb20R3Ili4n9P1fcwuLup21eQ5r9WXhs4_qy7VTrgPI,79
|
|
13
|
-
persidict-0.34.2.dist-info/METADATA,sha256=ihAySv6w1tA9-sHA6ONLvM0aDrRkn5RA4a1-G8dFxeE,9312
|
|
14
|
-
persidict-0.34.2.dist-info/RECORD,,
|