json-database 0.8.0a1__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.
json_database/utils.py ADDED
@@ -0,0 +1,340 @@
1
+ import json
2
+ from difflib import SequenceMatcher
3
+
4
+
5
+ class DummyLock:
6
+ """ A fake lock.
7
+
8
+ Arguments:
9
+ path (str): path to the lockfile for the lock
10
+ """
11
+ def __init__(self, path=""):
12
+ self.path = path
13
+
14
+ def acquire(self, blocking=True):
15
+ """ Acquire lock, locks thread and process lock.
16
+
17
+ Arguments:
18
+ blocking(bool): Set's blocking mode of acquire operation.
19
+ Default True.
20
+
21
+ Returns: True if lock succeeded otherwise False
22
+ """
23
+ return True
24
+
25
+ def release(self):
26
+ """ Release acquired lock. """
27
+ pass
28
+
29
+ def __enter__(self):
30
+ """ Context handler, acquires lock in blocking mode. """
31
+ return self
32
+
33
+ def __exit__(self, _type, value, traceback):
34
+ """ Releases the lock. """
35
+ pass
36
+
37
+
38
+ def fuzzy_match(x, against):
39
+ """Perform a 'fuzzy' comparison between two strings.
40
+ Returns:
41
+ float: match percentage -- 1.0 for perfect match,
42
+ down to 0.0 for no match at all.
43
+ """
44
+ return SequenceMatcher(None, x, against).ratio()
45
+
46
+
47
+ def match_one(query, choices):
48
+ """
49
+ Find best match from a list or dictionary given an input
50
+
51
+ Arguments:
52
+ query: string to test
53
+ choices: list or dictionary of choices
54
+
55
+ Returns: tuple with best match, score
56
+ """
57
+ if isinstance(choices, dict):
58
+ _choices = list(choices.keys())
59
+ elif isinstance(choices, list):
60
+ _choices = choices
61
+ else:
62
+ raise ValueError('a list or dict of choices must be provided')
63
+
64
+ best = (_choices[0], fuzzy_match(query, _choices[0]))
65
+ for c in _choices[1:]:
66
+ score = fuzzy_match(query, c)
67
+ if score > best[1]:
68
+ best = (c, score)
69
+
70
+ if isinstance(choices, dict):
71
+ return (choices[best[0]], best[1])
72
+ else:
73
+ return best
74
+
75
+
76
+ def merge_dict(base, delta, merge_lists=True, skip_empty=True,
77
+ no_dupes=True, new_only=False):
78
+ """
79
+ Recursively merging configuration dictionaries.
80
+
81
+ Args:
82
+ base: Target for merge
83
+ delta: Dictionary to merge into base
84
+ merge_lists: if a list is found merge contents instead of replacing
85
+ skip_empty: if an item in delta is empty, dont overwrite base
86
+ no_dupes: when merging lists deduplicate entries
87
+ new_only: only merge keys not yet in base
88
+ """
89
+
90
+ for k, d in delta.items():
91
+ b = base.get(k)
92
+ if isinstance(d, dict) and isinstance(b, dict):
93
+ merge_dict(b, d, merge_lists, skip_empty, no_dupes, new_only)
94
+ else:
95
+ if new_only and k in base:
96
+ continue
97
+ if skip_empty and not d and d is not False:
98
+ # dont replace if new entry is empty
99
+ pass
100
+ elif all((isinstance(b, list), isinstance(d, list), merge_lists)):
101
+ if no_dupes:
102
+ base[k] += [item for item in d if item not in base[k]]
103
+ else:
104
+ base[k] += d
105
+ else:
106
+ base[k] = d
107
+ return base
108
+
109
+
110
+ def load_commented_json(filename):
111
+ """ Loads an JSON file, ignoring comments
112
+
113
+ Supports a trivial extension to the JSON file format. Allow comments
114
+ to be embedded within the JSON, requiring that a comment be on an
115
+ independent line starting with '//' or '#'.
116
+
117
+ NOTE: A file created with these style comments will break strict JSON
118
+ parsers. This is similar to but lighter-weight than "human json"
119
+ proposed at https://hjson.org
120
+
121
+ Args:
122
+ filename (str): path to the commented JSON file
123
+
124
+ Returns:
125
+ obj: decoded Python object
126
+ """
127
+ with open(filename, encoding='utf-8') as f:
128
+ contents = f.read()
129
+
130
+ return json.loads(uncomment_json(contents))
131
+
132
+
133
+ def uncomment_json(commented_json_str):
134
+ """ Removes comments from a JSON string.
135
+
136
+ Supporting a trivial extension to the JSON format. Allow comments
137
+ to be embedded within the JSON, requiring that a comment be on an
138
+ independent line starting with '//' or '#'.
139
+
140
+ Example...
141
+ {
142
+ // comment
143
+ 'name' : 'value'
144
+ }
145
+
146
+ Args:
147
+ commented_json_str (str): a JSON string
148
+
149
+ Returns:
150
+ str: uncommented, legal JSON
151
+ """
152
+ lines = commented_json_str.splitlines()
153
+ # remove all comment lines, starting with // or #
154
+ nocomment = []
155
+ for line in lines:
156
+ stripped = line.lstrip()
157
+ if stripped.startswith("//") or stripped.startswith("#"):
158
+ continue
159
+ nocomment.append(line)
160
+
161
+ return " ".join(nocomment)
162
+
163
+
164
+ def is_jsonifiable(thing):
165
+ if not isinstance(thing, dict):
166
+ if isinstance(thing, str):
167
+ try:
168
+ json.loads(thing)
169
+ return True
170
+ except:
171
+ pass
172
+ else:
173
+ try:
174
+ thing.__dict__
175
+ return True
176
+ except:
177
+ pass
178
+ return False
179
+ return True
180
+
181
+
182
+ def get_key_recursively(search_dict, field, filter_None=True):
183
+ """
184
+ Takes a dict with nested lists and dicts,
185
+ and searches all dicts for a key of the field
186
+ provided.
187
+ """
188
+ if not is_jsonifiable(search_dict):
189
+ raise ValueError("unparseable format")
190
+ fields_found = []
191
+
192
+ for key, value in search_dict.items():
193
+ if value is None and filter_None:
194
+ continue
195
+ if key == field:
196
+ fields_found.append(search_dict)
197
+
198
+ elif isinstance(value, dict):
199
+ fields_found += get_key_recursively(value, field, filter_None)
200
+
201
+ elif isinstance(value, list):
202
+ for item in value:
203
+ if not isinstance(item, dict):
204
+ try:
205
+ if get_key_recursively(item.__dict__, field, filter_None):
206
+ fields_found.append(item)
207
+ except:
208
+ continue # can't parse
209
+ else:
210
+ fields_found += get_key_recursively(item, field, filter_None)
211
+
212
+ return fields_found
213
+
214
+
215
+ def get_key_recursively_fuzzy(search_dict, field, thresh=0.6, filter_None=True):
216
+ """
217
+ Takes a dict with nested lists and dicts,
218
+ and searches all dicts for a key of the field
219
+ provided.
220
+ """
221
+ if not is_jsonifiable(search_dict):
222
+ raise ValueError("unparseable format")
223
+
224
+ fields_found = []
225
+
226
+ for key, value in search_dict.items():
227
+ if value is None and filter_None:
228
+ continue
229
+ score = 0
230
+ if isinstance(key, str):
231
+ score = fuzzy_match(key, field)
232
+
233
+ if score >= thresh:
234
+ fields_found.append((search_dict, score))
235
+ elif isinstance(value, dict):
236
+ fields_found += get_key_recursively_fuzzy(value, field, thresh, filter_None)
237
+
238
+ elif isinstance(value, list):
239
+ for item in value:
240
+ if not isinstance(item, dict):
241
+ try:
242
+ if get_key_recursively_fuzzy(item.__dict__, field, thresh, filter_None):
243
+ fields_found.append((item, score))
244
+ except:
245
+ continue # can't parse
246
+ else:
247
+ fields_found += get_key_recursively_fuzzy(item, field, thresh, filter_None)
248
+ return sorted(fields_found, key = lambda i: i[1],reverse=True)
249
+
250
+
251
+ def get_value_recursively(search_dict, field, target_value):
252
+ """
253
+ Takes a dict with nested lists and dicts,
254
+ and searches all dicts for a key of the field
255
+ provided.
256
+ """
257
+ if not is_jsonifiable(search_dict):
258
+ raise ValueError("unparseable format")
259
+ fields_found = []
260
+
261
+ for key, value in search_dict.items():
262
+
263
+ if key == field and value == target_value:
264
+ fields_found.append(search_dict)
265
+
266
+ elif isinstance(value, dict):
267
+ fields_found += get_value_recursively(value, field, target_value)
268
+
269
+ elif isinstance(value, list):
270
+ for item in value:
271
+ if not isinstance(item, dict):
272
+ try:
273
+ if get_value_recursively(item.__dict__, field, target_value):
274
+ fields_found.append(item)
275
+ except:
276
+ continue # can't parse
277
+ else:
278
+ fields_found += get_value_recursively(item, field, target_value)
279
+
280
+ return fields_found
281
+
282
+
283
+ def get_value_recursively_fuzzy(search_dict, field, target_value, thresh=0.6):
284
+ """
285
+ Takes a dict with nested lists and dicts,
286
+ and searches all dicts for a key of the field
287
+ provided.
288
+ """
289
+ if not is_jsonifiable(search_dict):
290
+ raise ValueError("unparseable format")
291
+ fields_found = []
292
+ for key, value in search_dict.items():
293
+ if key == field:
294
+ if isinstance(value, str):
295
+ score = fuzzy_match(target_value, value)
296
+ if score >= thresh:
297
+ fields_found.append((search_dict, score))
298
+ elif isinstance(value, list):
299
+ for item in value:
300
+ score = fuzzy_match(target_value, item)
301
+ if score >= thresh:
302
+ fields_found.append((search_dict, score))
303
+ elif isinstance(value, dict):
304
+ fields_found += get_value_recursively_fuzzy(value, field, target_value, thresh)
305
+
306
+ elif isinstance(value, list):
307
+ for item in value:
308
+ if not isinstance(item, dict):
309
+ try:
310
+ found = get_value_recursively_fuzzy(item.__dict__, field, target_value, thresh)
311
+ if len(found):
312
+ fields_found.append((item, found[0][1]))
313
+ except:
314
+ continue # can't parse
315
+ else:
316
+ fields_found += get_value_recursively_fuzzy(item, field, target_value, thresh)
317
+
318
+ return sorted(fields_found, key = lambda i: i[1],reverse=True)
319
+
320
+
321
+ def jsonify_recursively(thing):
322
+ if isinstance(thing, list):
323
+ jsonified = list(thing)
324
+ for idx, item in enumerate(thing):
325
+ jsonified[idx] = jsonify_recursively(item)
326
+ elif isinstance(thing, dict):
327
+ try:
328
+ # can't import at top level to do proper check
329
+ jsonified = dict(thing.db)
330
+ except:
331
+ jsonified = dict(thing)
332
+ for key in jsonified.keys():
333
+ value = jsonified[key]
334
+ jsonified[key] = jsonify_recursively(value)
335
+ else:
336
+ try:
337
+ jsonified = thing.__dict__
338
+ except:
339
+ jsonified = thing
340
+ return jsonified
@@ -0,0 +1,6 @@
1
+ # START_VERSION_BLOCK
2
+ VERSION_MAJOR = 0
3
+ VERSION_MINOR = 8
4
+ VERSION_BUILD = 0
5
+ VERSION_ALPHA = 1
6
+ # END_VERSION_BLOCK
@@ -0,0 +1,174 @@
1
+ # Copyright © 2016-2021 Scott Stevenson <scott@stevenson.io>
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for
4
+ # any purpose with or without fee is hereby granted, provided that the
5
+ # above copyright notice and this permission notice appear in all
6
+ # copies.
7
+ #
8
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
9
+ # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
10
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
11
+ # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
12
+ # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
13
+ # PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
14
+ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ # PERFORMANCE OF THIS SOFTWARE.
16
+
17
+ """XDG Base Directory Specification variables.
18
+
19
+ xdg_cache_home(), xdg_config_home(), xdg_data_home(), and xdg_state_home()
20
+ return pathlib.Path objects containing the value of the environment variable
21
+ named XDG_CACHE_HOME, XDG_CONFIG_HOME, XDG_DATA_HOME, and XDG_STATE_HOME
22
+ respectively, or the default defined in the specification if the environment
23
+ variable is unset, empty, or contains a relative path rather than absolute
24
+ path.
25
+
26
+ xdg_config_dirs() and xdg_data_dirs() return a list of pathlib.Path
27
+ objects containing the value, split on colons, of the environment
28
+ variable named XDG_CONFIG_DIRS and XDG_DATA_DIRS respectively, or the
29
+ default defined in the specification if the environment variable is
30
+ unset or empty. Relative paths are ignored, as per the specification.
31
+
32
+ xdg_runtime_dir() returns a pathlib.Path object containing the value of
33
+ the XDG_RUNTIME_DIR environment variable, or None if the environment
34
+ variable is not set, or contains a relative path rather than absolute path.
35
+
36
+ """
37
+
38
+ # pylint: disable=fixme
39
+
40
+ import os
41
+ from pathlib import Path
42
+ from typing import List, Optional
43
+
44
+ __all__ = [
45
+ "xdg_cache_home",
46
+ "xdg_config_dirs",
47
+ "xdg_config_home",
48
+ "xdg_data_dirs",
49
+ "xdg_data_home",
50
+ "xdg_runtime_dir",
51
+ "xdg_state_home",
52
+ "XDG_CACHE_HOME",
53
+ "XDG_CONFIG_DIRS",
54
+ "XDG_CONFIG_HOME",
55
+ "XDG_DATA_DIRS",
56
+ "XDG_DATA_HOME",
57
+ "XDG_RUNTIME_DIR",
58
+ ]
59
+
60
+
61
+ def _path_from_env(variable: str, default: Path) -> Path:
62
+ """Read an environment variable as a path.
63
+
64
+ The environment variable with the specified name is read, and its
65
+ value returned as a path. If the environment variable is not set, is
66
+ set to the empty string, or is set to a relative rather than
67
+ absolute path, the default value is returned.
68
+
69
+ Parameters
70
+ ----------
71
+ variable : str
72
+ Name of the environment variable.
73
+ default : Path
74
+ Default value.
75
+
76
+ Returns
77
+ -------
78
+ Path
79
+ Value from environment or default.
80
+
81
+ """
82
+ # TODO(srstevenson): Use assignment expression in Python 3.8.
83
+ value = os.environ.get(variable)
84
+ if value and os.path.isabs(value):
85
+ return Path(value)
86
+ return default
87
+
88
+
89
+ def _paths_from_env(variable: str, default: List[Path]) -> List[Path]:
90
+ """Read an environment variable as a list of paths.
91
+
92
+ The environment variable with the specified name is read, and its
93
+ value split on colons and returned as a list of paths. If the
94
+ environment variable is not set, or set to the empty string, the
95
+ default value is returned. Relative paths are ignored, as per the
96
+ specification.
97
+
98
+ Parameters
99
+ ----------
100
+ variable : str
101
+ Name of the environment variable.
102
+ default : List[Path]
103
+ Default value.
104
+
105
+ Returns
106
+ -------
107
+ List[Path]
108
+ Value from environment or default.
109
+
110
+ """
111
+ # TODO(srstevenson): Use assignment expression in Python 3.8.
112
+ value = os.environ.get(variable)
113
+ if value:
114
+ paths = [
115
+ Path(path) for path in value.split(":") if os.path.isabs(path)
116
+ ]
117
+ if paths:
118
+ return paths
119
+ return default
120
+
121
+
122
+ def xdg_cache_home() -> Path:
123
+ """Return a Path corresponding to XDG_CACHE_HOME."""
124
+ return _path_from_env("XDG_CACHE_HOME", Path.home() / ".cache")
125
+
126
+
127
+ def xdg_config_dirs() -> List[Path]:
128
+ """Return a list of Paths corresponding to XDG_CONFIG_DIRS."""
129
+ return _paths_from_env("XDG_CONFIG_DIRS", [Path("/etc/xdg")])
130
+
131
+
132
+ def xdg_config_home() -> Path:
133
+ """Return a Path corresponding to XDG_CONFIG_HOME."""
134
+ return _path_from_env("XDG_CONFIG_HOME", Path.home() / ".config")
135
+
136
+
137
+ def xdg_data_dirs() -> List[Path]:
138
+ """Return a list of Paths corresponding to XDG_DATA_DIRS."""
139
+ return _paths_from_env(
140
+ "XDG_DATA_DIRS",
141
+ [Path(path) for path in "/usr/local/share/:/usr/share/".split(":")],
142
+ )
143
+
144
+
145
+ def xdg_data_home() -> Path:
146
+ """Return a Path corresponding to XDG_DATA_HOME."""
147
+ return _path_from_env("XDG_DATA_HOME", Path.home() / ".local" / "share")
148
+
149
+
150
+ def xdg_runtime_dir() -> Optional[Path]:
151
+ """Return a Path corresponding to XDG_RUNTIME_DIR.
152
+
153
+ If the XDG_RUNTIME_DIR environment variable is not set, None will be
154
+ returned as per the specification.
155
+
156
+ """
157
+ value = os.getenv("XDG_RUNTIME_DIR")
158
+ if value and os.path.isabs(value):
159
+ return Path(value)
160
+ return None
161
+
162
+
163
+ def xdg_state_home() -> Path:
164
+ """Return a Path corresponding to XDG_STATE_HOME."""
165
+ return _path_from_env("XDG_STATE_HOME", Path.home() / ".local" / "state")
166
+
167
+
168
+ # The following variables are deprecated, but remain for backward compatibility.
169
+ XDG_CACHE_HOME = xdg_cache_home()
170
+ XDG_CONFIG_DIRS = xdg_config_dirs()
171
+ XDG_CONFIG_HOME = xdg_config_home()
172
+ XDG_DATA_DIRS = xdg_data_dirs()
173
+ XDG_DATA_HOME = xdg_data_home()
174
+ XDG_RUNTIME_DIR = xdg_runtime_dir()
@@ -0,0 +1,19 @@
1
+ The MIT License
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.