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
persidict/persi_dict.py
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Persistent, dict-like API for durable key-value stores.
|
|
2
2
|
|
|
3
|
-
PersiDict
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
(e.g. insertion order is not preserved) and additional methods.
|
|
3
|
+
PersiDict defines a unified interface for persistent dictionaries. The API is
|
|
4
|
+
similar to Python's built-in dict with some differences (e.g., insertion order
|
|
5
|
+
is not guaranteed) and several additional convenience methods.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
Keys are sequences of URL/filename-safe strings represented by SafeStrTuple.
|
|
8
|
+
Plain strings or sequences of strings are accepted and automatically coerced to
|
|
9
|
+
SafeStrTuple. Values can be arbitrary Python objects unless an implementation
|
|
10
|
+
restricts them via ``base_class_for_values``.
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
in this case they will be automatically converted to SafeStrTuple.
|
|
13
|
-
|
|
14
|
-
A value can be (virtually) any Python object.
|
|
15
|
-
|
|
16
|
-
'Persistently' means that key-value pairs are saved in a durable storage,
|
|
17
|
-
such as a local hard-drive or AWS S3 cloud, and can be retrieved
|
|
18
|
-
even after the Python process that created the dictionary has terminated.
|
|
12
|
+
Persistence means items are stored durably (e.g., in local files or cloud
|
|
13
|
+
objects) and remain accessible across process lifetimes.
|
|
19
14
|
"""
|
|
20
15
|
|
|
21
16
|
from __future__ import annotations
|
|
@@ -41,43 +36,24 @@ it will be automatically converted into SafeStrTuple.
|
|
|
41
36
|
"""
|
|
42
37
|
|
|
43
38
|
class PersiDict(MutableMapping, ParameterizableClass):
|
|
44
|
-
"""
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
It enables various distributed cache optimizations
|
|
63
|
-
for remote storage.
|
|
64
|
-
False means normal dict-like behaviour.
|
|
65
|
-
|
|
66
|
-
digest_len : int
|
|
67
|
-
Length of a hash signature suffix which PersiDict
|
|
68
|
-
automatically adds to each string in a key
|
|
69
|
-
while mapping the key to an address of a value
|
|
70
|
-
in a persistent storage backend (e.g. a filename
|
|
71
|
-
or an S3 objectname). We need it to ensure correct work
|
|
72
|
-
of persistent dictionaries with case-insensitive
|
|
73
|
-
(even if case-preserving) filesystems, such as MacOS HFS.
|
|
74
|
-
|
|
75
|
-
base_class_for_values: Optional[type]
|
|
76
|
-
A base class for values stored in the dictionary.
|
|
77
|
-
If specified, it will be used to check types of values
|
|
78
|
-
in the dictionary. If not specified, no type checking
|
|
79
|
-
will be performed and all types will be allowed.
|
|
80
|
-
|
|
39
|
+
"""Abstract dict-like interface for durable key-value stores.
|
|
40
|
+
|
|
41
|
+
Keys are URL/filename-safe sequences of strings (SafeStrTuple). Concrete
|
|
42
|
+
subclasses implement storage backends (e.g., filesystem, S3). The API is
|
|
43
|
+
similar to Python's dict but does not guarantee insertion order and adds
|
|
44
|
+
persistence-specific helpers (e.g., timestamp()).
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
immutable_items (bool):
|
|
48
|
+
If True, items are write-once: existing values cannot be modified or
|
|
49
|
+
deleted.
|
|
50
|
+
digest_len (int):
|
|
51
|
+
Length of a base32 MD5 digest fragment used to suffix each key
|
|
52
|
+
component to avoid collisions on case-insensitive filesystems. 0
|
|
53
|
+
disables suffixing.
|
|
54
|
+
base_class_for_values (Optional[type]):
|
|
55
|
+
Optional base class that all values must inherit from. If None, any
|
|
56
|
+
type is accepted.
|
|
81
57
|
"""
|
|
82
58
|
|
|
83
59
|
digest_len:int
|
|
@@ -89,6 +65,20 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
89
65
|
, digest_len:int = 8
|
|
90
66
|
, base_class_for_values:Optional[type] = None
|
|
91
67
|
, *args, **kwargs):
|
|
68
|
+
"""Initialize base parameters shared by all persistent dicts.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
immutable_items: If True, items cannot be modified or deleted.
|
|
72
|
+
digest_len: Number of hash characters to append to key components to
|
|
73
|
+
avoid case-insensitive collisions. Must be non-negative.
|
|
74
|
+
base_class_for_values: Optional base class that values must inherit
|
|
75
|
+
from; if None, values are not type-restricted.
|
|
76
|
+
*args: Ignored in the base class (reserved for subclasses).
|
|
77
|
+
**kwargs: Ignored in the base class (reserved for subclasses).
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
ValueError: If digest_len is negative.
|
|
81
|
+
"""
|
|
92
82
|
self.digest_len = int(digest_len)
|
|
93
83
|
if digest_len < 0:
|
|
94
84
|
raise ValueError("digest_len must be non-negative")
|
|
@@ -98,10 +88,12 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
98
88
|
|
|
99
89
|
|
|
100
90
|
def get_params(self):
|
|
101
|
-
"""Return
|
|
91
|
+
"""Return configuration parameters of this dictionary.
|
|
102
92
|
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
Returns:
|
|
94
|
+
dict: A sorted dict of parameters used to reconstruct the instance.
|
|
95
|
+
This supports the Parameterizable API and is absent in the
|
|
96
|
+
builtin dict.
|
|
105
97
|
"""
|
|
106
98
|
params = dict(
|
|
107
99
|
immutable_items=self.immutable_items
|
|
@@ -115,9 +107,13 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
115
107
|
@property
|
|
116
108
|
@abstractmethod
|
|
117
109
|
def base_url(self):
|
|
118
|
-
"""
|
|
110
|
+
"""Base URL identifying the storage location.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
str: A URL-like string (e.g., s3://bucket/prefix or file://...).
|
|
119
114
|
|
|
120
|
-
|
|
115
|
+
Raises:
|
|
116
|
+
NotImplementedError: Must be provided by subclasses.
|
|
121
117
|
"""
|
|
122
118
|
raise NotImplementedError
|
|
123
119
|
|
|
@@ -125,39 +121,79 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
125
121
|
@property
|
|
126
122
|
@abstractmethod
|
|
127
123
|
def base_dir(self):
|
|
128
|
-
"""
|
|
124
|
+
"""Base directory on the local filesystem, if applicable.
|
|
129
125
|
|
|
130
|
-
|
|
126
|
+
Returns:
|
|
127
|
+
str: Path to a local base directory used by the store.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
NotImplementedError: Must be provided by subclasses that use local
|
|
131
|
+
storage.
|
|
131
132
|
"""
|
|
132
133
|
raise NotImplementedError
|
|
133
134
|
|
|
134
135
|
|
|
135
136
|
def __repr__(self) -> str:
|
|
136
|
-
"""Return
|
|
137
|
+
"""Return a reproducible string representation.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
str: Representation including class name and constructor parameters.
|
|
141
|
+
"""
|
|
137
142
|
params = self.get_params()
|
|
138
143
|
params_str = ', '.join(f'{k}={v!r}' for k, v in params.items())
|
|
139
144
|
return f'{self.__class__.__name__}({params_str})'
|
|
140
145
|
|
|
141
146
|
|
|
142
147
|
def __str__(self) -> str:
|
|
143
|
-
"""Return
|
|
148
|
+
"""Return a user-friendly string with all items.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
str: Stringified dict of items.
|
|
152
|
+
"""
|
|
144
153
|
return str(dict(self.items()))
|
|
145
154
|
|
|
146
155
|
|
|
147
156
|
@abstractmethod
|
|
148
157
|
def __contains__(self, key:PersiDictKey) -> bool:
|
|
149
|
-
"""
|
|
158
|
+
"""Check whether a key exists in the store.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
key: Key (string or sequence of strings) or SafeStrTuple.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
bool: True if key exists, False otherwise.
|
|
165
|
+
"""
|
|
150
166
|
raise NotImplementedError
|
|
151
167
|
|
|
152
168
|
|
|
153
169
|
@abstractmethod
|
|
154
170
|
def __getitem__(self, key:PersiDictKey) -> Any:
|
|
155
|
-
"""
|
|
171
|
+
"""Retrieve the value for a key.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
key: Key (string or sequence of strings) or SafeStrTuple.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Any: The stored value.
|
|
178
|
+
"""
|
|
156
179
|
raise NotImplementedError
|
|
157
180
|
|
|
158
181
|
|
|
159
182
|
def __setitem__(self, key:PersiDictKey, value:Any):
|
|
160
|
-
"""Set
|
|
183
|
+
"""Set the value for a key.
|
|
184
|
+
|
|
185
|
+
Special values KEEP_CURRENT and DELETE_CURRENT are interpreted as
|
|
186
|
+
commands to keep or delete the current value respectively.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
key: Key (string or sequence of strings) or SafeStrTuple.
|
|
190
|
+
value: Value to store, or a Joker command.
|
|
191
|
+
|
|
192
|
+
Raises:
|
|
193
|
+
KeyError: If attempting to modify an existing key when
|
|
194
|
+
immutable_items is True.
|
|
195
|
+
NotImplementedError: Subclasses must implement actual writing.
|
|
196
|
+
"""
|
|
161
197
|
if value is KEEP_CURRENT:
|
|
162
198
|
return
|
|
163
199
|
elif value is DELETE_CURRENT:
|
|
@@ -169,71 +205,139 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
169
205
|
|
|
170
206
|
|
|
171
207
|
def __delitem__(self, key:PersiDictKey):
|
|
172
|
-
"""Delete
|
|
173
|
-
|
|
208
|
+
"""Delete a key and its value.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
key: Key (string or sequence of strings) or SafeStrTuple.
|
|
212
|
+
|
|
213
|
+
Raises:
|
|
214
|
+
KeyError: If immutable_items is True.
|
|
215
|
+
NotImplementedError: Subclasses must implement deletion.
|
|
216
|
+
"""
|
|
217
|
+
if self.immutable_items:
|
|
174
218
|
raise KeyError("Can't delete an immutable key-value pair")
|
|
175
219
|
raise NotImplementedError
|
|
176
220
|
|
|
177
221
|
|
|
178
222
|
@abstractmethod
|
|
179
223
|
def __len__(self) -> int:
|
|
180
|
-
"""Return
|
|
224
|
+
"""Return the number of stored items.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
int: Number of key-value pairs.
|
|
228
|
+
"""
|
|
181
229
|
raise NotImplementedError
|
|
182
230
|
|
|
183
231
|
|
|
184
232
|
@abstractmethod
|
|
185
233
|
def _generic_iter(self, result_type: set[str]) -> Any:
|
|
186
|
-
"""Underlying implementation for
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
234
|
+
"""Underlying implementation for iterator helpers.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
result_type: A set indicating desired fields among {'keys',
|
|
238
|
+
'values', 'timestamps'}.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Any: An iterator yielding keys, values, and/or timestamps based on
|
|
242
|
+
result_type.
|
|
243
|
+
|
|
244
|
+
Raises:
|
|
245
|
+
TypeError: If result_type is not a set.
|
|
246
|
+
ValueError: If result_type contains invalid entries or an invalid number of items.
|
|
247
|
+
NotImplementedError: Subclasses must implement the concrete iterator.
|
|
248
|
+
"""
|
|
249
|
+
if not isinstance(result_type, set):
|
|
250
|
+
raise TypeError("result_type must be a set of strings")
|
|
251
|
+
if not (1 <= len(result_type) <= 3):
|
|
252
|
+
raise ValueError("result_type must contain between 1 and 3 elements")
|
|
253
|
+
allowed = {"keys", "values", "timestamps"}
|
|
254
|
+
if (result_type | allowed) != allowed:
|
|
255
|
+
raise ValueError("result_type can only contain 'keys', 'values', 'timestamps'")
|
|
256
|
+
if not (1 <= len(result_type & allowed) <= 3):
|
|
257
|
+
raise ValueError("result_type must include at least one of 'keys', 'values', 'timestamps'")
|
|
191
258
|
raise NotImplementedError
|
|
192
259
|
|
|
193
260
|
|
|
194
261
|
def __iter__(self):
|
|
195
|
-
"""
|
|
262
|
+
"""Iterate over keys.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Iterator[SafeStrTuple]: Iterator of keys.
|
|
266
|
+
"""
|
|
196
267
|
return self._generic_iter({"keys"})
|
|
197
268
|
|
|
198
269
|
|
|
199
270
|
def keys(self):
|
|
200
|
-
"""
|
|
271
|
+
"""Return an iterator over keys.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Iterator[SafeStrTuple]: Keys iterator.
|
|
275
|
+
"""
|
|
201
276
|
return self._generic_iter({"keys"})
|
|
202
277
|
|
|
203
278
|
|
|
204
279
|
def keys_and_timestamps(self):
|
|
205
|
-
"""
|
|
280
|
+
"""Return an iterator over (key, timestamp) pairs.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Iterator[tuple[SafeStrTuple, float]]: Keys and POSIX timestamps.
|
|
284
|
+
"""
|
|
206
285
|
return self._generic_iter({"keys", "timestamps"})
|
|
207
286
|
|
|
208
287
|
|
|
209
288
|
def values(self):
|
|
210
|
-
"""
|
|
289
|
+
"""Return an iterator over values.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Iterator[Any]: Values iterator.
|
|
293
|
+
"""
|
|
211
294
|
return self._generic_iter({"values"})
|
|
212
295
|
|
|
213
296
|
|
|
214
297
|
def values_and_timestamps(self):
|
|
215
|
-
"""
|
|
298
|
+
"""Return an iterator over (value, timestamp) pairs.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Iterator[tuple[Any, float]]: Values and POSIX timestamps.
|
|
302
|
+
"""
|
|
216
303
|
return self._generic_iter({"values", "timestamps"})
|
|
217
304
|
|
|
218
305
|
|
|
219
306
|
def items(self):
|
|
220
|
-
"""
|
|
307
|
+
"""Return an iterator over (key, value) pairs.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Iterator[tuple[SafeStrTuple, Any]]: Items iterator.
|
|
311
|
+
"""
|
|
221
312
|
return self._generic_iter({"keys", "values"})
|
|
222
313
|
|
|
223
314
|
|
|
224
315
|
def items_and_timestamps(self):
|
|
225
|
-
"""
|
|
316
|
+
"""Return an iterator over (key, value, timestamp) triples.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Iterator[tuple[SafeStrTuple, Any, float]]: Items and timestamps.
|
|
320
|
+
"""
|
|
226
321
|
return self._generic_iter({"keys", "values", "timestamps"})
|
|
227
322
|
|
|
228
323
|
|
|
229
324
|
def setdefault(self, key:PersiDictKey, default:Any=None) -> Any:
|
|
230
|
-
"""Insert key with
|
|
325
|
+
"""Insert key with default if absent; return the value.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
key: Key (string or sequence of strings) or SafeStrTuple.
|
|
329
|
+
default: Value to insert if the key is not present.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Any: Existing value if present; otherwise the provided default.
|
|
231
333
|
|
|
232
|
-
|
|
334
|
+
Raises:
|
|
335
|
+
TypeError: If default is a Joker command (KEEP_CURRENT/DELETE_CURRENT).
|
|
233
336
|
"""
|
|
234
337
|
# TODO: check edge cases to ensure the same semantics as standard dicts
|
|
235
338
|
key = SafeStrTuple(key)
|
|
236
|
-
|
|
339
|
+
if isinstance(default, Joker):
|
|
340
|
+
raise TypeError("default must be a regular value, not a Joker command")
|
|
237
341
|
if key in self:
|
|
238
342
|
return self[key]
|
|
239
343
|
else:
|
|
@@ -241,8 +345,18 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
241
345
|
return default
|
|
242
346
|
|
|
243
347
|
|
|
244
|
-
def __eq__(self, other) -> bool:
|
|
245
|
-
"""
|
|
348
|
+
def __eq__(self, other:PersiDict) -> bool:
|
|
349
|
+
"""Compare dictionaries for equality.
|
|
350
|
+
|
|
351
|
+
If other is a PersiDict, compare portable params. Otherwise, attempt to
|
|
352
|
+
compare as mapping by keys and values.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
other: Another dictionary-like object.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
bool: True if considered equal, False otherwise.
|
|
359
|
+
"""
|
|
246
360
|
if isinstance(other, PersiDict):
|
|
247
361
|
return self.get_portable_params() == other.get_portable_params()
|
|
248
362
|
try:
|
|
@@ -257,16 +371,30 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
257
371
|
|
|
258
372
|
|
|
259
373
|
def __getstate__(self):
|
|
374
|
+
"""Prevent pickling of PersiDict instances.
|
|
375
|
+
|
|
376
|
+
Raises:
|
|
377
|
+
TypeError: Always raised; PersiDict instances are not pickleable.
|
|
378
|
+
"""
|
|
260
379
|
raise TypeError("PersiDict is not picklable.")
|
|
261
380
|
|
|
262
381
|
|
|
263
382
|
def __setstate__(self, state):
|
|
383
|
+
"""Prevent unpickling of PersiDict instances.
|
|
384
|
+
|
|
385
|
+
Raises:
|
|
386
|
+
TypeError: Always raised; PersiDict instances are not pickleable.
|
|
387
|
+
"""
|
|
264
388
|
raise TypeError("PersiDict is not picklable.")
|
|
265
389
|
|
|
266
390
|
|
|
267
391
|
def clear(self) -> None:
|
|
268
|
-
"""Remove all items from the dictionary.
|
|
269
|
-
|
|
392
|
+
"""Remove all items from the dictionary.
|
|
393
|
+
|
|
394
|
+
Raises:
|
|
395
|
+
KeyError: If items are immutable (immutable_items is True).
|
|
396
|
+
"""
|
|
397
|
+
if self.immutable_items:
|
|
270
398
|
raise KeyError("Can't delete an immutable key-value pair")
|
|
271
399
|
|
|
272
400
|
for k in self.keys():
|
|
@@ -277,14 +405,21 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
277
405
|
|
|
278
406
|
|
|
279
407
|
def delete_if_exists(self, key:PersiDictKey) -> bool:
|
|
280
|
-
"""
|
|
281
|
-
|
|
282
|
-
Returns True if the item existed and was deleted, False otherwise.
|
|
408
|
+
"""Delete an item without raising an exception if it doesn't exist.
|
|
283
409
|
|
|
284
410
|
This method is absent in the original dict API.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
key: Key (string or sequence of strings) or SafeStrTuple.
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
bool: True if the item existed and was deleted; False otherwise.
|
|
417
|
+
|
|
418
|
+
Raises:
|
|
419
|
+
KeyError: If items are immutable (immutable_items is True).
|
|
285
420
|
"""
|
|
286
421
|
|
|
287
|
-
if self.immutable_items:
|
|
422
|
+
if self.immutable_items:
|
|
288
423
|
raise KeyError("Can't delete an immutable key-value pair")
|
|
289
424
|
|
|
290
425
|
key = SafeStrTuple(key)
|
|
@@ -300,19 +435,37 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
300
435
|
|
|
301
436
|
|
|
302
437
|
def get_subdict(self, prefix_key:PersiDictKey) -> PersiDict:
|
|
303
|
-
"""Get a sub-dictionary containing items with the
|
|
438
|
+
"""Get a sub-dictionary containing items with the given prefix key.
|
|
304
439
|
|
|
305
|
-
|
|
440
|
+
Items whose keys start with the provided prefix are visible through the
|
|
441
|
+
returned sub-dictionary. If the prefix does not exist, an empty
|
|
442
|
+
sub-dictionary is returned.
|
|
306
443
|
|
|
307
444
|
This method is absent in the original Python dict API.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
prefix_key: Key prefix (string, sequence of strings, or SafeStrTuple)
|
|
448
|
+
identifying the sub-namespace to expose.
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
PersiDict: A dictionary-like view restricted to keys under the
|
|
452
|
+
provided prefix.
|
|
453
|
+
|
|
454
|
+
Raises:
|
|
455
|
+
NotImplementedError: Must be implemented by subclasses that support
|
|
456
|
+
hierarchical key spaces.
|
|
308
457
|
"""
|
|
309
458
|
raise NotImplementedError
|
|
310
459
|
|
|
311
460
|
|
|
312
461
|
def subdicts(self) -> dict[str, PersiDict]:
|
|
313
|
-
"""
|
|
462
|
+
"""Return a mapping of first-level keys to sub-dictionaries.
|
|
314
463
|
|
|
315
464
|
This method is absent in the original dict API.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
dict[str, PersiDict]: A mapping from a top-level key segment to a
|
|
468
|
+
sub-dictionary restricted to the corresponding keyspace.
|
|
316
469
|
"""
|
|
317
470
|
all_keys = {k[0] for k in self.keys()}
|
|
318
471
|
result_subdicts = {k: self.get_subdict(k) for k in all_keys}
|
|
@@ -322,13 +475,14 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
322
475
|
def random_key(self) -> PersiDictKey | None:
|
|
323
476
|
"""Return a random key from the dictionary.
|
|
324
477
|
|
|
325
|
-
Returns a single random key if the dictionary is not empty.
|
|
326
|
-
Returns None if the dictionary is empty.
|
|
327
|
-
|
|
328
478
|
This method is absent in the original Python dict API.
|
|
329
479
|
|
|
330
480
|
Implementation uses reservoir sampling to select a uniformly random key
|
|
331
481
|
in streaming time, without loading all keys into memory or using len().
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
SafeStrTuple | None: A random key if the dictionary is not empty;
|
|
485
|
+
None if the dictionary is empty.
|
|
332
486
|
"""
|
|
333
487
|
iterator = iter(self.keys())
|
|
334
488
|
try:
|
|
@@ -351,17 +505,33 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
351
505
|
|
|
352
506
|
@abstractmethod
|
|
353
507
|
def timestamp(self, key:PersiDictKey) -> float:
|
|
354
|
-
"""
|
|
508
|
+
"""Return the last modification time of a key.
|
|
355
509
|
|
|
356
510
|
This method is absent in the original dict API.
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
key: Key (string or sequence of strings) or SafeStrTuple.
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
float: POSIX timestamp (seconds since Unix epoch) of the last
|
|
517
|
+
modification of the item.
|
|
518
|
+
|
|
519
|
+
Raises:
|
|
520
|
+
NotImplementedError: Must be implemented by subclasses.
|
|
357
521
|
"""
|
|
358
522
|
raise NotImplementedError
|
|
359
523
|
|
|
360
524
|
|
|
361
525
|
def oldest_keys(self, max_n=None):
|
|
362
|
-
"""Return max_n
|
|
526
|
+
"""Return up to max_n oldest keys in the dictionary.
|
|
363
527
|
|
|
364
|
-
|
|
528
|
+
Args:
|
|
529
|
+
max_n (int | None): Maximum number of keys to return. If None,
|
|
530
|
+
return all keys sorted by age (oldest first). Values <= 0
|
|
531
|
+
yield an empty list.
|
|
532
|
+
|
|
533
|
+
Returns:
|
|
534
|
+
list[SafeStrTuple]: The oldest keys, oldest first.
|
|
365
535
|
|
|
366
536
|
This method is absent in the original Python dict API.
|
|
367
537
|
"""
|
|
@@ -381,9 +551,15 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
381
551
|
|
|
382
552
|
|
|
383
553
|
def oldest_values(self, max_n=None):
|
|
384
|
-
"""Return max_n
|
|
554
|
+
"""Return up to max_n oldest values in the dictionary.
|
|
555
|
+
|
|
556
|
+
Args:
|
|
557
|
+
max_n (int | None): Maximum number of values to return. If None,
|
|
558
|
+
return values for all keys sorted by age (oldest first). Values
|
|
559
|
+
<= 0 yield an empty list.
|
|
385
560
|
|
|
386
|
-
|
|
561
|
+
Returns:
|
|
562
|
+
list[Any]: Values corresponding to the oldest keys.
|
|
387
563
|
|
|
388
564
|
This method is absent in the original Python dict API.
|
|
389
565
|
"""
|
|
@@ -391,9 +567,15 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
391
567
|
|
|
392
568
|
|
|
393
569
|
def newest_keys(self, max_n=None):
|
|
394
|
-
"""Return max_n
|
|
570
|
+
"""Return up to max_n newest keys in the dictionary.
|
|
395
571
|
|
|
396
|
-
|
|
572
|
+
Args:
|
|
573
|
+
max_n (int | None): Maximum number of keys to return. If None,
|
|
574
|
+
return all keys sorted by age (newest first). Values <= 0
|
|
575
|
+
yield an empty list.
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
list[SafeStrTuple]: The newest keys, newest first.
|
|
397
579
|
|
|
398
580
|
This method is absent in the original Python dict API.
|
|
399
581
|
"""
|
|
@@ -413,9 +595,15 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
413
595
|
|
|
414
596
|
|
|
415
597
|
def newest_values(self, max_n=None):
|
|
416
|
-
"""Return max_n
|
|
598
|
+
"""Return up to max_n newest values in the dictionary.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
max_n (int | None): Maximum number of values to return. If None,
|
|
602
|
+
return values for all keys sorted by age (newest first). Values
|
|
603
|
+
<= 0 yield an empty list.
|
|
417
604
|
|
|
418
|
-
|
|
605
|
+
Returns:
|
|
606
|
+
list[Any]: Values corresponding to the newest keys.
|
|
419
607
|
|
|
420
608
|
This method is absent in the original Python dict API.
|
|
421
609
|
"""
|