persidict 0.18.0__py3-none-any.whl → 0.20.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/__init__.py +2 -1
- persidict/file_dir_dict.py +25 -13
- persidict/nochange_const.py +17 -0
- persidict/persi_dict.py +13 -0
- persidict/s3_dict.py +12 -3
- {persidict-0.18.0.dist-info → persidict-0.20.0.dist-info}/METADATA +3 -2
- persidict-0.20.0.dist-info/RECORD +12 -0
- persidict-0.18.0.dist-info/RECORD +0 -11
- {persidict-0.18.0.dist-info → persidict-0.20.0.dist-info}/WHEEL +0 -0
persidict/__init__.py
CHANGED
|
@@ -26,4 +26,5 @@ from .safe_chars import get_safe_chars, replace_unsafe_chars
|
|
|
26
26
|
from .safe_str_tuple import SafeStrTuple
|
|
27
27
|
from .persi_dict import PersiDict
|
|
28
28
|
from .file_dir_dict import FileDirDict
|
|
29
|
-
from .s3_dict import S3Dict
|
|
29
|
+
from .s3_dict import S3Dict
|
|
30
|
+
from .nochange_const import *
|
persidict/file_dir_dict.py
CHANGED
|
@@ -21,6 +21,7 @@ import jsonpickle.ext.numpy as jsonpickle_numpy
|
|
|
21
21
|
import jsonpickle.ext.pandas as jsonpickle_pandas
|
|
22
22
|
import parameterizable
|
|
23
23
|
|
|
24
|
+
from .nochange_const import NO_CHANGE
|
|
24
25
|
from .safe_chars import replace_unsafe_chars
|
|
25
26
|
from .safe_str_tuple import SafeStrTuple
|
|
26
27
|
from .safe_str_tuple_signing import sign_safe_str_tuple, unsign_safe_str_tuple
|
|
@@ -45,7 +46,7 @@ class FileDirDict(PersiDict):
|
|
|
45
46
|
text files (either in jason format or as a plain text).
|
|
46
47
|
"""
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
_base_dir:str
|
|
49
50
|
file_type:str
|
|
50
51
|
|
|
51
52
|
def __init__(self
|
|
@@ -56,7 +57,7 @@ class FileDirDict(PersiDict):
|
|
|
56
57
|
, base_class_for_values: Optional[type] = None):
|
|
57
58
|
"""A constructor defines location of the store and file format to use.
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
_base_dir is a directory that will contain all the files in
|
|
60
61
|
the FileDirDict. If the directory does not exist, it will be created.
|
|
61
62
|
|
|
62
63
|
base_class_for_values constraints the type of values that can be
|
|
@@ -98,14 +99,15 @@ class FileDirDict(PersiDict):
|
|
|
98
99
|
os.mkdir(base_dir)
|
|
99
100
|
assert os.path.isdir(base_dir)
|
|
100
101
|
|
|
101
|
-
self.base_dir_param =
|
|
102
|
-
self.
|
|
102
|
+
# self.base_dir_param = _base_dir
|
|
103
|
+
self._base_dir = os.path.abspath(base_dir)
|
|
104
|
+
|
|
103
105
|
|
|
104
106
|
def __repr__(self):
|
|
105
107
|
"""Return repr(self)."""
|
|
106
108
|
|
|
107
109
|
repr_str = super().__repr__()
|
|
108
|
-
repr_str = repr_str[:-1] + f",
|
|
110
|
+
repr_str = repr_str[:-1] + f", _base_dir={self._base_dir}"
|
|
109
111
|
repr_str += f", file_type={self.file_type}"
|
|
110
112
|
repr_str += " )"
|
|
111
113
|
|
|
@@ -116,15 +118,22 @@ class FileDirDict(PersiDict):
|
|
|
116
118
|
"""Return configuration parameters of the dictionary."""
|
|
117
119
|
params = super().get_params()
|
|
118
120
|
additional_params = dict(
|
|
119
|
-
base_dir=self.
|
|
121
|
+
base_dir=self.base_dir
|
|
120
122
|
, file_type=self.file_type)
|
|
121
123
|
params.update(additional_params)
|
|
122
124
|
return params
|
|
123
125
|
|
|
126
|
+
|
|
124
127
|
@property
|
|
125
128
|
def base_url(self) -> str:
|
|
126
129
|
"""Return dictionary's URL"""
|
|
127
|
-
return f"file://{self.
|
|
130
|
+
return f"file://{self._base_dir}"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def base_dir(self) -> str:
|
|
135
|
+
"""Return dictionary's base directory"""
|
|
136
|
+
return self._base_dir
|
|
128
137
|
|
|
129
138
|
|
|
130
139
|
def __len__(self) -> int:
|
|
@@ -132,7 +141,7 @@ class FileDirDict(PersiDict):
|
|
|
132
141
|
|
|
133
142
|
num_files = 0
|
|
134
143
|
suffix = "." + self.file_type
|
|
135
|
-
for subdir_info in os.walk(self.
|
|
144
|
+
for subdir_info in os.walk(self._base_dir):
|
|
136
145
|
files = subdir_info[2]
|
|
137
146
|
files = [f_name for f_name in files
|
|
138
147
|
if f_name.endswith(suffix)]
|
|
@@ -146,13 +155,13 @@ class FileDirDict(PersiDict):
|
|
|
146
155
|
if self.immutable_items:
|
|
147
156
|
raise KeyError("Can't clear a dict that contains immutable items")
|
|
148
157
|
|
|
149
|
-
for subdir_info in os.walk(self.
|
|
158
|
+
for subdir_info in os.walk(self._base_dir, topdown=False):
|
|
150
159
|
(subdir_name, _, files) = subdir_info
|
|
151
160
|
suffix = "." + self.file_type
|
|
152
161
|
for f in files:
|
|
153
162
|
if f.endswith(suffix):
|
|
154
163
|
os.remove(os.path.join(subdir_name, f))
|
|
155
|
-
if (subdir_name != self.
|
|
164
|
+
if (subdir_name != self._base_dir) and (
|
|
156
165
|
len(os.listdir(subdir_name)) == 0 ):
|
|
157
166
|
os.rmdir(subdir_name)
|
|
158
167
|
|
|
@@ -163,7 +172,7 @@ class FileDirDict(PersiDict):
|
|
|
163
172
|
"""Convert a key into a filesystem path."""
|
|
164
173
|
|
|
165
174
|
key = sign_safe_str_tuple(key, self.digest_len)
|
|
166
|
-
key = [self.
|
|
175
|
+
key = [self._base_dir] + list(key.strings)
|
|
167
176
|
dir_names = key[:-1] if is_file_path else key
|
|
168
177
|
|
|
169
178
|
if create_subdirs:
|
|
@@ -290,6 +299,9 @@ class FileDirDict(PersiDict):
|
|
|
290
299
|
def __setitem__(self, key:PersiDictKey, value:Any):
|
|
291
300
|
"""Set self[key] to value."""
|
|
292
301
|
|
|
302
|
+
if value is NO_CHANGE:
|
|
303
|
+
return
|
|
304
|
+
|
|
293
305
|
if isinstance(value, PersiDict):
|
|
294
306
|
raise TypeError(
|
|
295
307
|
f"You are not allowed to store a PersiDict "
|
|
@@ -319,7 +331,7 @@ class FileDirDict(PersiDict):
|
|
|
319
331
|
def _generic_iter(self, iter_type: str):
|
|
320
332
|
"""Underlying implementation for .items()/.keys()/.values() iterators"""
|
|
321
333
|
assert iter_type in {"keys", "values", "items"}
|
|
322
|
-
walk_results = os.walk(self.
|
|
334
|
+
walk_results = os.walk(self._base_dir)
|
|
323
335
|
ext_len = len(self.file_type) + 1
|
|
324
336
|
|
|
325
337
|
def splitter(dir_path: str):
|
|
@@ -341,7 +353,7 @@ class FileDirDict(PersiDict):
|
|
|
341
353
|
for f in files:
|
|
342
354
|
if f.endswith(suffix):
|
|
343
355
|
prefix_key = os.path.relpath(
|
|
344
|
-
dir_name, start=self.
|
|
356
|
+
dir_name, start=self._base_dir)
|
|
345
357
|
|
|
346
358
|
result_key = (*splitter(prefix_key), f[:-ext_len])
|
|
347
359
|
result_key = SafeStrTuple(result_key)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""A singleton constant to indicate no change in a value.
|
|
2
|
+
|
|
3
|
+
When updating a val ue in a persistent dictionary,
|
|
4
|
+
use NO_CHANGE as the new value to indicate that
|
|
5
|
+
the existing value should remain unchanged.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
class NoChange_Class:
|
|
9
|
+
_instance = None
|
|
10
|
+
|
|
11
|
+
def __new__(cls):
|
|
12
|
+
if cls._instance is None:
|
|
13
|
+
cls._instance = super().__new__(cls)
|
|
14
|
+
return cls._instance
|
|
15
|
+
|
|
16
|
+
NoChange = NoChange_Class()
|
|
17
|
+
NO_CHANGE = NoChange_Class()
|
persidict/persi_dict.py
CHANGED
|
@@ -26,6 +26,7 @@ from parameterizable import ParameterizableClass
|
|
|
26
26
|
from typing import Any, Sequence, Optional
|
|
27
27
|
from collections.abc import MutableMapping
|
|
28
28
|
|
|
29
|
+
from .nochange_const import NO_CHANGE
|
|
29
30
|
from .safe_str_tuple import SafeStrTuple
|
|
30
31
|
|
|
31
32
|
PersiDictKey = SafeStrTuple | Sequence[str] | str
|
|
@@ -93,6 +94,7 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
93
94
|
self.immutable_items = bool(immutable_items)
|
|
94
95
|
self.base_class_for_values = base_class_for_values
|
|
95
96
|
|
|
97
|
+
|
|
96
98
|
def get_params(self):
|
|
97
99
|
"""Return a dictionary of parameters for the PersiDict object."""
|
|
98
100
|
params = dict(
|
|
@@ -102,6 +104,7 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
102
104
|
)
|
|
103
105
|
return params
|
|
104
106
|
|
|
107
|
+
|
|
105
108
|
@property
|
|
106
109
|
@abstractmethod
|
|
107
110
|
def base_url(self):
|
|
@@ -109,6 +112,13 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
109
112
|
raise NotImplementedError
|
|
110
113
|
|
|
111
114
|
|
|
115
|
+
@property
|
|
116
|
+
@abstractmethod
|
|
117
|
+
def base_dir(self):
|
|
118
|
+
"""Return dictionary's base directory in the local filesystem"""
|
|
119
|
+
raise NotImplementedError
|
|
120
|
+
|
|
121
|
+
|
|
112
122
|
def __repr__(self) -> str:
|
|
113
123
|
"""Return repr(self)"""
|
|
114
124
|
repr_str = self.__class__.__name__ + "("
|
|
@@ -139,6 +149,8 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
139
149
|
|
|
140
150
|
def __setitem__(self, key:PersiDictKey, value:Any):
|
|
141
151
|
"""Set self[key] to value."""
|
|
152
|
+
if value is NO_CHANGE:
|
|
153
|
+
return
|
|
142
154
|
if self.immutable_items:
|
|
143
155
|
if key in self:
|
|
144
156
|
raise KeyError("Can't modify an immutable key-value pair")
|
|
@@ -192,6 +204,7 @@ class PersiDict(MutableMapping, ParameterizableClass):
|
|
|
192
204
|
"""
|
|
193
205
|
# TODO: check edge cases to ensure the same semantics as standard dicts
|
|
194
206
|
key = SafeStrTuple(key)
|
|
207
|
+
assert not default is NO_CHANGE
|
|
195
208
|
if key in self:
|
|
196
209
|
return self[key]
|
|
197
210
|
else:
|
persidict/s3_dict.py
CHANGED
|
@@ -10,6 +10,7 @@ import parameterizable
|
|
|
10
10
|
from .safe_str_tuple import SafeStrTuple
|
|
11
11
|
from .safe_str_tuple_signing import sign_safe_str_tuple, unsign_safe_str_tuple
|
|
12
12
|
from .persi_dict import PersiDict
|
|
13
|
+
from .nochange_const import NO_CHANGE
|
|
13
14
|
from .file_dir_dict import FileDirDict, PersiDictKey
|
|
14
15
|
|
|
15
16
|
S3DICT_DEFAULT_BASE_DIR = "__s3_dict__"
|
|
@@ -33,7 +34,7 @@ class S3Dict(PersiDict):
|
|
|
33
34
|
bucket_name: str
|
|
34
35
|
root_prefix: str
|
|
35
36
|
file_type: str
|
|
36
|
-
|
|
37
|
+
_base_dir: str
|
|
37
38
|
|
|
38
39
|
def __init__(self, bucket_name:str = "my_bucket"
|
|
39
40
|
, region:str = None
|
|
@@ -52,7 +53,7 @@ class S3Dict(PersiDict):
|
|
|
52
53
|
|
|
53
54
|
root_prefix is a common S3 prefix for all objectnames in a dictionary.
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
_base_dir is a local directory that will be used to store tmp files.
|
|
56
57
|
|
|
57
58
|
base_class_for_values constraints the type of values that can be
|
|
58
59
|
stored in the dictionary. If specified, it will be used to
|
|
@@ -99,7 +100,7 @@ class S3Dict(PersiDict):
|
|
|
99
100
|
"""Return repr(self)."""
|
|
100
101
|
|
|
101
102
|
repr_str = super().__repr__()
|
|
102
|
-
repr_str = repr_str[:-1] + f",
|
|
103
|
+
repr_str = repr_str[:-1] + f", _base_dir={self.local_cache._base_dir}"
|
|
103
104
|
repr_str += f", file_type={self.file_type}"
|
|
104
105
|
repr_str += f", region={self.region}"
|
|
105
106
|
repr_str += f", bucket_name={self.bucket_name}"
|
|
@@ -124,6 +125,11 @@ class S3Dict(PersiDict):
|
|
|
124
125
|
return f"s3://{self.bucket_name}/{self.root_prefix}"
|
|
125
126
|
|
|
126
127
|
|
|
128
|
+
@property
|
|
129
|
+
def base_dir(self) -> str:
|
|
130
|
+
"""Return dictionary's base directory in the local filesystem"""
|
|
131
|
+
return self.local_cache.base_dir
|
|
132
|
+
|
|
127
133
|
|
|
128
134
|
def _build_full_objectname(self, key:PersiDictKey) -> str:
|
|
129
135
|
""" Convert PersiDictKey into an S3 objectname. """
|
|
@@ -174,6 +180,9 @@ class S3Dict(PersiDict):
|
|
|
174
180
|
def __setitem__(self, key:PersiDictKey, value:Any):
|
|
175
181
|
"""Set self[key] to value. """
|
|
176
182
|
|
|
183
|
+
if value is NO_CHANGE:
|
|
184
|
+
return
|
|
185
|
+
|
|
177
186
|
if isinstance(value, PersiDict):
|
|
178
187
|
raise TypeError(
|
|
179
188
|
f"You are not allowed to store a PersiDict "
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: persidict
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.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
|
|
@@ -155,12 +155,13 @@ that simultaneously work with the same instance of a dictionary.
|
|
|
155
155
|
* Values must be pickleable Python objects.
|
|
156
156
|
* You can constrain values to be an instance of a specific class.
|
|
157
157
|
* Insertion order is not preserved.
|
|
158
|
-
* You
|
|
158
|
+
* You cannot assign initial key-value pairs to a dictionary in its constructor.
|
|
159
159
|
* `PersiDict` API has additional methods `delete_if_exists()`, `timestamp()`,
|
|
160
160
|
`get_subdict()`, `subdicts()`, `random_keys()`, `newest_keys()`,
|
|
161
161
|
`oldest_keys()`, `newest_values()`, `oldest_values()`,
|
|
162
162
|
`get_params()`, `get_metaparams()`, and `get_default_metaparams()`,
|
|
163
163
|
which are not available in native Python dicts.
|
|
164
|
+
* You can use NO_CHANGE constant to avoid actually setting/updating a value.
|
|
164
165
|
|
|
165
166
|
## Fine Tuning
|
|
166
167
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
persidict/.DS_Store,sha256=d65165279105ca6773180500688df4bdc69a2c7b771752f0a46ef120b7fd8ec3,6148
|
|
2
|
+
persidict/__init__.py,sha256=34e758e6e566fc18225214b430cf492938d6f3d48b36e678ab947f15c780a699,1185
|
|
3
|
+
persidict/file_dir_dict.py,sha256=438558ea1f06a8e280609a4aaa94a6fea05d82ce2ef79bd401af94b014da82a6,14132
|
|
4
|
+
persidict/nochange_const.py,sha256=d569d9283a684150e380b08ebdeb232783dd0e51938bfbbadf27327952027f3e,443
|
|
5
|
+
persidict/persi_dict.py,sha256=7301a8a6a41bb4c2fbd29d31e0421287d53f6a88c66e9324656db1043e5e2b55,11728
|
|
6
|
+
persidict/s3_dict.py,sha256=acc676f92ae993364f1fa70d74e95c182b2f263f29eae99f3b554f45aededfe5,11834
|
|
7
|
+
persidict/safe_chars.py,sha256=59a20e96205d2e5675d827a911ad42ddbd553f1bd7e2cda1be765a9c2c4ce814,565
|
|
8
|
+
persidict/safe_str_tuple.py,sha256=71393904bdebfb213ad8429fed59e04da52964076c01324f2238821aa4339325,3717
|
|
9
|
+
persidict/safe_str_tuple_signing.py,sha256=e6e0a3015651a8ea2ef8aa43670f84c5ba1cdfedefb9ac8932aaebb7c699d1c9,3742
|
|
10
|
+
persidict-0.20.0.dist-info/WHEEL,sha256=c133ef911c90b05f7e14d8679ba99146f9154fcd271b7398cf8f672283b94e05,79
|
|
11
|
+
persidict-0.20.0.dist-info/METADATA,sha256=f8d494dd74e77144e57c8db653c0b7da471c3d71e313804caee9ad6ae78f3855,9172
|
|
12
|
+
persidict-0.20.0.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
persidict/.DS_Store,sha256=d65165279105ca6773180500688df4bdc69a2c7b771752f0a46ef120b7fd8ec3,6148
|
|
2
|
-
persidict/__init__.py,sha256=f589b6292bef0ee7c6f6ef087dacbecc5943a64deed8839b02f8342db3032b25,1155
|
|
3
|
-
persidict/file_dir_dict.py,sha256=b978200dd48fa77a7b15bc05327d5aa773cc09974e138a581da0a784d0448903,13906
|
|
4
|
-
persidict/persi_dict.py,sha256=cea37db3dcd209e5a4ab382e668fad67ac97b56ef829e8466b60eccc8f771eb6,11431
|
|
5
|
-
persidict/s3_dict.py,sha256=93d8a7945c565adf8aa513568a4dde3b06772e2366acbdfc3402c063aade17ef,11581
|
|
6
|
-
persidict/safe_chars.py,sha256=59a20e96205d2e5675d827a911ad42ddbd553f1bd7e2cda1be765a9c2c4ce814,565
|
|
7
|
-
persidict/safe_str_tuple.py,sha256=71393904bdebfb213ad8429fed59e04da52964076c01324f2238821aa4339325,3717
|
|
8
|
-
persidict/safe_str_tuple_signing.py,sha256=e6e0a3015651a8ea2ef8aa43670f84c5ba1cdfedefb9ac8932aaebb7c699d1c9,3742
|
|
9
|
-
persidict-0.18.0.dist-info/WHEEL,sha256=c133ef911c90b05f7e14d8679ba99146f9154fcd271b7398cf8f672283b94e05,79
|
|
10
|
-
persidict-0.18.0.dist-info/METADATA,sha256=3567ed443b76bb83e481945ef1bfd6b20647db3f178d81022b6234d574242aa9,9096
|
|
11
|
-
persidict-0.18.0.dist-info/RECORD,,
|
|
File without changes
|