1 |
|
# -*- coding: utf-8 -*- |
2 |
|
|
3 |
|
# Copyright 2018 Telefonica S.A. |
4 |
|
# |
5 |
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
6 |
|
# you may not use this file except in compliance with the License. |
7 |
|
# You may obtain a copy of the License at |
8 |
|
# |
9 |
|
# http://www.apache.org/licenses/LICENSE-2.0 |
10 |
|
# |
11 |
|
# Unless required by applicable law or agreed to in writing, software |
12 |
|
# distributed under the License is distributed on an "AS IS" BASIS, |
13 |
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
14 |
|
# implied. |
15 |
|
# See the License for the specific language governing permissions and |
16 |
|
# limitations under the License. |
17 |
|
|
18 |
1 |
import logging |
19 |
1 |
from osm_common.dbbase import DbException, DbBase |
20 |
1 |
from osm_common.dbmongo import deep_update |
21 |
1 |
from http import HTTPStatus |
22 |
1 |
from uuid import uuid4 |
23 |
1 |
from copy import deepcopy |
24 |
|
|
25 |
1 |
__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>" |
26 |
|
|
27 |
|
|
28 |
1 |
class DbMemory(DbBase): |
29 |
|
|
30 |
1 |
def __init__(self, logger_name='db', lock=False): |
31 |
1 |
super().__init__(logger_name, lock) |
32 |
1 |
self.db = {} |
33 |
|
|
34 |
1 |
def db_connect(self, config): |
35 |
|
""" |
36 |
|
Connect to database |
37 |
|
:param config: Configuration of database |
38 |
|
:return: None or raises DbException on error |
39 |
|
""" |
40 |
1 |
if "logger_name" in config: |
41 |
1 |
self.logger = logging.getLogger(config["logger_name"]) |
42 |
1 |
master_key = config.get("commonkey") or config.get("masterpassword") |
43 |
1 |
if master_key: |
44 |
0 |
self.set_secret_key(master_key) |
45 |
|
|
46 |
1 |
@staticmethod |
47 |
|
def _format_filter(q_filter): |
48 |
1 |
db_filter = {} |
49 |
|
# split keys with ANYINDEX in this way: |
50 |
|
# {"A.B.ANYINDEX.C.D.ANYINDEX.E": v } -> {"A.B.ANYINDEX": {"C.D.ANYINDEX": {"E": v}}} |
51 |
1 |
if q_filter: |
52 |
0 |
for k, v in q_filter.items(): |
53 |
0 |
db_v = v |
54 |
0 |
kleft, _, kright = k.rpartition(".ANYINDEX.") |
55 |
0 |
while kleft: |
56 |
0 |
k = kleft + ".ANYINDEX" |
57 |
0 |
db_v = {kright: db_v} |
58 |
0 |
kleft, _, kright = k.rpartition(".ANYINDEX.") |
59 |
0 |
deep_update(db_filter, {k: db_v}) |
60 |
|
|
61 |
1 |
return db_filter |
62 |
|
|
63 |
1 |
def _find(self, table, q_filter): |
64 |
|
|
65 |
0 |
def recursive_find(key_list, key_next_index, content, oper, target): |
66 |
0 |
if key_next_index == len(key_list) or content is None: |
67 |
0 |
try: |
68 |
0 |
if oper in ("eq", "cont"): |
69 |
0 |
if isinstance(target, list): |
70 |
0 |
if isinstance(content, list): |
71 |
0 |
return any(content_item in target for content_item in content) |
72 |
0 |
return content in target |
73 |
0 |
elif isinstance(content, list): |
74 |
0 |
return target in content |
75 |
|
else: |
76 |
0 |
return content == target |
77 |
0 |
elif oper in ("neq", "ne", "ncont"): |
78 |
0 |
if isinstance(target, list): |
79 |
0 |
if isinstance(content, list): |
80 |
0 |
return all(content_item not in target for content_item in content) |
81 |
0 |
return content not in target |
82 |
0 |
elif isinstance(content, list): |
83 |
0 |
return target not in content |
84 |
|
else: |
85 |
0 |
return content != target |
86 |
0 |
if oper == "gt": |
87 |
0 |
return content > target |
88 |
0 |
elif oper == "gte": |
89 |
0 |
return content >= target |
90 |
0 |
elif oper == "lt": |
91 |
0 |
return content < target |
92 |
0 |
elif oper == "lte": |
93 |
0 |
return content <= target |
94 |
|
else: |
95 |
0 |
raise DbException("Unknown filter operator '{}' in key '{}'". |
96 |
|
format(oper, ".".join(key_list)), http_code=HTTPStatus.BAD_REQUEST) |
97 |
0 |
except TypeError: |
98 |
0 |
return False |
99 |
|
|
100 |
0 |
elif isinstance(content, dict): |
101 |
0 |
return recursive_find(key_list, key_next_index + 1, content.get(key_list[key_next_index]), oper, |
102 |
|
target) |
103 |
0 |
elif isinstance(content, list): |
104 |
0 |
look_for_match = True # when there is a match return immediately |
105 |
0 |
if (target is None) != (oper in ("neq", "ne", "ncont")): # one True and other False (Xor) |
106 |
0 |
look_for_match = False # when there is not a match return immediately |
107 |
|
|
108 |
0 |
for content_item in content: |
109 |
0 |
if key_list[key_next_index] == "ANYINDEX" and isinstance(v, dict): |
110 |
0 |
matches = True |
111 |
0 |
for k2, v2 in target.items(): |
112 |
0 |
k_new_list = k2.split(".") |
113 |
0 |
new_operator = "eq" |
114 |
0 |
if k_new_list[-1] in ("eq", "ne", "gt", "gte", "lt", "lte", "cont", "ncont", "neq"): |
115 |
0 |
new_operator = k_new_list.pop() |
116 |
0 |
if not recursive_find(k_new_list, 0, content_item, new_operator, v2): |
117 |
0 |
matches = False |
118 |
0 |
break |
119 |
|
|
120 |
|
else: |
121 |
0 |
matches = recursive_find(key_list, key_next_index, content_item, oper, target) |
122 |
0 |
if matches == look_for_match: |
123 |
0 |
return matches |
124 |
0 |
if key_list[key_next_index].isdecimal() and int(key_list[key_next_index]) < len(content): |
125 |
0 |
matches = recursive_find(key_list, key_next_index + 1, content[int(key_list[key_next_index])], |
126 |
|
oper, target) |
127 |
0 |
if matches == look_for_match: |
128 |
0 |
return matches |
129 |
0 |
return not look_for_match |
130 |
|
else: # content is not dict, nor list neither None, so not found |
131 |
0 |
if oper in ("neq", "ne", "ncont"): |
132 |
0 |
return target is not None |
133 |
|
else: |
134 |
0 |
return target is None |
135 |
|
|
136 |
0 |
for i, row in enumerate(self.db.get(table, ())): |
137 |
0 |
q_filter = q_filter or {} |
138 |
0 |
for k, v in q_filter.items(): |
139 |
0 |
k_list = k.split(".") |
140 |
0 |
operator = "eq" |
141 |
0 |
if k_list[-1] in ("eq", "ne", "gt", "gte", "lt", "lte", "cont", "ncont", "neq"): |
142 |
0 |
operator = k_list.pop() |
143 |
0 |
matches = recursive_find(k_list, 0, row, operator, v) |
144 |
0 |
if not matches: |
145 |
0 |
break |
146 |
|
else: |
147 |
|
# match |
148 |
0 |
yield i, row |
149 |
|
|
150 |
1 |
def get_list(self, table, q_filter=None): |
151 |
|
""" |
152 |
|
Obtain a list of entries matching q_filter |
153 |
|
:param table: collection or table |
154 |
|
:param q_filter: Filter |
155 |
|
:return: a list (can be empty) with the found entries. Raises DbException on error |
156 |
|
""" |
157 |
0 |
try: |
158 |
0 |
result = [] |
159 |
0 |
with self.lock: |
160 |
0 |
for _, row in self._find(table, self._format_filter(q_filter)): |
161 |
0 |
result.append(deepcopy(row)) |
162 |
0 |
return result |
163 |
0 |
except DbException: |
164 |
0 |
raise |
165 |
0 |
except Exception as e: # TODO refine |
166 |
0 |
raise DbException(str(e)) |
167 |
|
|
168 |
1 |
def count(self, table, q_filter=None): |
169 |
|
""" |
170 |
|
Count the number of entries matching q_filter |
171 |
|
:param table: collection or table |
172 |
|
:param q_filter: Filter |
173 |
|
:return: number of entries found (can be zero) |
174 |
|
:raise: DbException on error |
175 |
|
""" |
176 |
0 |
try: |
177 |
0 |
with self.lock: |
178 |
0 |
return sum(1 for x in self._find(table, self._format_filter(q_filter))) |
179 |
0 |
except DbException: |
180 |
0 |
raise |
181 |
0 |
except Exception as e: # TODO refine |
182 |
0 |
raise DbException(str(e)) |
183 |
|
|
184 |
1 |
def get_one(self, table, q_filter=None, fail_on_empty=True, fail_on_more=True): |
185 |
|
""" |
186 |
|
Obtain one entry matching q_filter |
187 |
|
:param table: collection or table |
188 |
|
:param q_filter: Filter |
189 |
|
:param fail_on_empty: If nothing matches filter it returns None unless this flag is set tu True, in which case |
190 |
|
it raises a DbException |
191 |
|
:param fail_on_more: If more than one matches filter it returns one of then unless this flag is set tu True, so |
192 |
|
that it raises a DbException |
193 |
|
:return: The requested element, or None |
194 |
|
""" |
195 |
0 |
try: |
196 |
0 |
result = None |
197 |
0 |
with self.lock: |
198 |
0 |
for _, row in self._find(table, self._format_filter(q_filter)): |
199 |
0 |
if not fail_on_more: |
200 |
0 |
return deepcopy(row) |
201 |
0 |
if result: |
202 |
0 |
raise DbException("Found more than one entry with filter='{}'".format(q_filter), |
203 |
|
HTTPStatus.CONFLICT.value) |
204 |
0 |
result = row |
205 |
0 |
if not result and fail_on_empty: |
206 |
0 |
raise DbException("Not found entry with filter='{}'".format(q_filter), HTTPStatus.NOT_FOUND) |
207 |
0 |
return deepcopy(result) |
208 |
0 |
except Exception as e: # TODO refine |
209 |
0 |
raise DbException(str(e)) |
210 |
|
|
211 |
1 |
def del_list(self, table, q_filter=None): |
212 |
|
""" |
213 |
|
Deletes all entries that match q_filter |
214 |
|
:param table: collection or table |
215 |
|
:param q_filter: Filter |
216 |
|
:return: Dict with the number of entries deleted |
217 |
|
""" |
218 |
0 |
try: |
219 |
0 |
id_list = [] |
220 |
0 |
with self.lock: |
221 |
0 |
for i, _ in self._find(table, self._format_filter(q_filter)): |
222 |
0 |
id_list.append(i) |
223 |
0 |
deleted = len(id_list) |
224 |
0 |
for i in reversed(id_list): |
225 |
0 |
del self.db[table][i] |
226 |
0 |
return {"deleted": deleted} |
227 |
0 |
except DbException: |
228 |
0 |
raise |
229 |
0 |
except Exception as e: # TODO refine |
230 |
0 |
raise DbException(str(e)) |
231 |
|
|
232 |
1 |
def del_one(self, table, q_filter=None, fail_on_empty=True): |
233 |
|
""" |
234 |
|
Deletes one entry that matches q_filter |
235 |
|
:param table: collection or table |
236 |
|
:param q_filter: Filter |
237 |
|
:param fail_on_empty: If nothing matches filter it returns '0' deleted unless this flag is set tu True, in |
238 |
|
which case it raises a DbException |
239 |
|
:return: Dict with the number of entries deleted |
240 |
|
""" |
241 |
0 |
try: |
242 |
0 |
with self.lock: |
243 |
0 |
for i, _ in self._find(table, self._format_filter(q_filter)): |
244 |
0 |
break |
245 |
|
else: |
246 |
0 |
if fail_on_empty: |
247 |
0 |
raise DbException("Not found entry with filter='{}'".format(q_filter), HTTPStatus.NOT_FOUND) |
248 |
0 |
return None |
249 |
0 |
del self.db[table][i] |
250 |
0 |
return {"deleted": 1} |
251 |
0 |
except Exception as e: # TODO refine |
252 |
0 |
raise DbException(str(e)) |
253 |
|
|
254 |
1 |
def _update(self, db_item, update_dict, unset=None, pull=None, push=None, push_list=None, pull_list=None): |
255 |
|
""" |
256 |
|
Modifies an entry at database |
257 |
|
:param db_item: entry of the table to update |
258 |
|
:param update_dict: Plain dictionary with the content to be updated. It is a dot separated keys and a value |
259 |
|
:param unset: Plain dictionary with the content to be removed if exist. It is a dot separated keys, value is |
260 |
|
ignored. If not exist, it is ignored |
261 |
|
:param pull: Plain dictionary with the content to be removed from an array. It is a dot separated keys and value |
262 |
|
if exist in the array is removed. If not exist, it is ignored |
263 |
|
:param pull_list: Same as pull but values are arrays where each item is removed from the array |
264 |
|
:param push: Plain dictionary with the content to be appended to an array. It is a dot separated keys and value |
265 |
|
is appended to the end of the array |
266 |
|
:param push_list: Same as push but values are arrays where each item is and appended instead of appending the |
267 |
|
whole array |
268 |
|
:return: True if database has been changed, False if not; Exception on error |
269 |
|
""" |
270 |
1 |
def _iterate_keys(k, db_nested, populate=True): |
271 |
1 |
k_list = k.split(".") |
272 |
1 |
k_item_prev = k_list[0] |
273 |
1 |
populated = False |
274 |
1 |
if k_item_prev not in db_nested and populate: |
275 |
1 |
populated = True |
276 |
1 |
db_nested[k_item_prev] = None |
277 |
1 |
for k_item in k_list[1:]: |
278 |
1 |
if isinstance(db_nested[k_item_prev], dict): |
279 |
1 |
if k_item not in db_nested[k_item_prev]: |
280 |
1 |
if not populate: |
281 |
1 |
raise DbException("Cannot set '{}', not existing '{}'".format(k, k_item)) |
282 |
1 |
populated = True |
283 |
1 |
db_nested[k_item_prev][k_item] = None |
284 |
1 |
elif isinstance(db_nested[k_item_prev], list) and k_item.isdigit(): |
285 |
|
# extend list with Nones if index greater than list |
286 |
1 |
k_item = int(k_item) |
287 |
1 |
if k_item >= len(db_nested[k_item_prev]): |
288 |
1 |
if not populate: |
289 |
1 |
raise DbException("Cannot set '{}', index too large '{}'".format(k, k_item)) |
290 |
1 |
populated = True |
291 |
1 |
db_nested[k_item_prev] += [None] * (k_item - len(db_nested[k_item_prev]) + 1) |
292 |
1 |
elif db_nested[k_item_prev] is None: |
293 |
1 |
if not populate: |
294 |
0 |
raise DbException("Cannot set '{}', not existing '{}'".format(k, k_item)) |
295 |
1 |
populated = True |
296 |
1 |
db_nested[k_item_prev] = {k_item: None} |
297 |
|
else: # number, string, boolean, ... or list but with not integer key |
298 |
1 |
raise DbException("Cannot set '{}' on existing '{}={}'".format(k, k_item_prev, |
299 |
|
db_nested[k_item_prev])) |
300 |
1 |
db_nested = db_nested[k_item_prev] |
301 |
1 |
k_item_prev = k_item |
302 |
1 |
return db_nested, k_item_prev, populated |
303 |
|
|
304 |
1 |
updated = False |
305 |
1 |
try: |
306 |
1 |
if update_dict: |
307 |
1 |
for dot_k, v in update_dict.items(): |
308 |
1 |
dict_to_update, key_to_update, _ = _iterate_keys(dot_k, db_item) |
309 |
1 |
dict_to_update[key_to_update] = v |
310 |
1 |
updated = True |
311 |
1 |
if unset: |
312 |
1 |
for dot_k in unset: |
313 |
1 |
try: |
314 |
1 |
dict_to_update, key_to_update, _ = _iterate_keys(dot_k, db_item, populate=False) |
315 |
1 |
del dict_to_update[key_to_update] |
316 |
1 |
updated = True |
317 |
1 |
except Exception: |
318 |
1 |
pass |
319 |
1 |
if pull: |
320 |
1 |
for dot_k, v in pull.items(): |
321 |
1 |
try: |
322 |
1 |
dict_to_update, key_to_update, _ = _iterate_keys(dot_k, db_item, populate=False) |
323 |
1 |
except Exception: |
324 |
1 |
continue |
325 |
1 |
if key_to_update not in dict_to_update: |
326 |
1 |
continue |
327 |
1 |
if not isinstance(dict_to_update[key_to_update], list): |
328 |
1 |
raise DbException("Cannot pull '{}'. Target is not a list".format(dot_k)) |
329 |
1 |
while v in dict_to_update[key_to_update]: |
330 |
1 |
dict_to_update[key_to_update].remove(v) |
331 |
1 |
updated = True |
332 |
1 |
if pull_list: |
333 |
0 |
for dot_k, v in pull_list.items(): |
334 |
0 |
if not isinstance(v, list): |
335 |
0 |
raise DbException("Invalid content at pull_list, '{}' must be an array".format(dot_k), |
336 |
|
http_code=HTTPStatus.BAD_REQUEST) |
337 |
0 |
try: |
338 |
0 |
dict_to_update, key_to_update, _ = _iterate_keys(dot_k, db_item, populate=False) |
339 |
0 |
except Exception: |
340 |
0 |
continue |
341 |
0 |
if key_to_update not in dict_to_update: |
342 |
0 |
continue |
343 |
0 |
if not isinstance(dict_to_update[key_to_update], list): |
344 |
0 |
raise DbException("Cannot pull_list '{}'. Target is not a list".format(dot_k)) |
345 |
0 |
for single_v in v: |
346 |
0 |
while single_v in dict_to_update[key_to_update]: |
347 |
0 |
dict_to_update[key_to_update].remove(single_v) |
348 |
0 |
updated = True |
349 |
1 |
if push: |
350 |
1 |
for dot_k, v in push.items(): |
351 |
1 |
dict_to_update, key_to_update, populated = _iterate_keys(dot_k, db_item) |
352 |
1 |
if isinstance(dict_to_update, dict) and key_to_update not in dict_to_update: |
353 |
0 |
dict_to_update[key_to_update] = [v] |
354 |
0 |
updated = True |
355 |
1 |
elif populated and dict_to_update[key_to_update] is None: |
356 |
1 |
dict_to_update[key_to_update] = [v] |
357 |
1 |
updated = True |
358 |
1 |
elif not isinstance(dict_to_update[key_to_update], list): |
359 |
1 |
raise DbException("Cannot push '{}'. Target is not a list".format(dot_k)) |
360 |
|
else: |
361 |
1 |
dict_to_update[key_to_update].append(v) |
362 |
1 |
updated = True |
363 |
1 |
if push_list: |
364 |
1 |
for dot_k, v in push_list.items(): |
365 |
1 |
if not isinstance(v, list): |
366 |
1 |
raise DbException("Invalid content at push_list, '{}' must be an array".format(dot_k), |
367 |
|
http_code=HTTPStatus.BAD_REQUEST) |
368 |
1 |
dict_to_update, key_to_update, populated = _iterate_keys(dot_k, db_item) |
369 |
1 |
if isinstance(dict_to_update, dict) and key_to_update not in dict_to_update: |
370 |
0 |
dict_to_update[key_to_update] = v.copy() |
371 |
0 |
updated = True |
372 |
1 |
elif populated and dict_to_update[key_to_update] is None: |
373 |
1 |
dict_to_update[key_to_update] = v.copy() |
374 |
1 |
updated = True |
375 |
1 |
elif not isinstance(dict_to_update[key_to_update], list): |
376 |
1 |
raise DbException("Cannot push '{}'. Target is not a list".format(dot_k), |
377 |
|
http_code=HTTPStatus.CONFLICT) |
378 |
|
else: |
379 |
1 |
dict_to_update[key_to_update] += v |
380 |
1 |
updated = True |
381 |
|
|
382 |
1 |
return updated |
383 |
1 |
except DbException: |
384 |
1 |
raise |
385 |
0 |
except Exception as e: # TODO refine |
386 |
0 |
raise DbException(str(e)) |
387 |
|
|
388 |
1 |
def set_one(self, table, q_filter, update_dict, fail_on_empty=True, unset=None, pull=None, push=None, |
389 |
|
push_list=None, pull_list=None): |
390 |
|
""" |
391 |
|
Modifies an entry at database |
392 |
|
:param table: collection or table |
393 |
|
:param q_filter: Filter |
394 |
|
:param update_dict: Plain dictionary with the content to be updated. It is a dot separated keys and a value |
395 |
|
:param fail_on_empty: If nothing matches filter it returns None unless this flag is set tu True, in which case |
396 |
|
it raises a DbException |
397 |
|
:param unset: Plain dictionary with the content to be removed if exist. It is a dot separated keys, value is |
398 |
|
ignored. If not exist, it is ignored |
399 |
|
:param pull: Plain dictionary with the content to be removed from an array. It is a dot separated keys and value |
400 |
|
if exist in the array is removed. If not exist, it is ignored |
401 |
|
:param pull_list: Same as pull but values are arrays where each item is removed from the array |
402 |
|
:param push: Plain dictionary with the content to be appended to an array. It is a dot separated keys and value |
403 |
|
is appended to the end of the array |
404 |
|
:param push_list: Same as push but values are arrays where each item is and appended instead of appending the |
405 |
|
whole array |
406 |
|
:return: Dict with the number of entries modified. None if no matching is found. |
407 |
|
""" |
408 |
1 |
with self.lock: |
409 |
1 |
for i, db_item in self._find(table, self._format_filter(q_filter)): |
410 |
1 |
updated = self._update(db_item, update_dict, unset=unset, pull=pull, push=push, push_list=push_list, |
411 |
|
pull_list=pull_list) |
412 |
1 |
return {"updated": 1 if updated else 0} |
413 |
|
else: |
414 |
0 |
if fail_on_empty: |
415 |
0 |
raise DbException("Not found entry with _id='{}'".format(q_filter), HTTPStatus.NOT_FOUND) |
416 |
0 |
return None |
417 |
|
|
418 |
1 |
def set_list(self, table, q_filter, update_dict, unset=None, pull=None, push=None, push_list=None, pull_list=None): |
419 |
|
"""Modifies al matching entries at database. Same as push. Do not fail if nothing matches""" |
420 |
0 |
with self.lock: |
421 |
0 |
updated = 0 |
422 |
0 |
found = 0 |
423 |
0 |
for _, db_item in self._find(table, self._format_filter(q_filter)): |
424 |
0 |
found += 1 |
425 |
0 |
if self._update(db_item, update_dict, unset=unset, pull=pull, push=push, push_list=push_list, |
426 |
|
pull_list=pull_list): |
427 |
0 |
updated += 1 |
428 |
|
# if not found and fail_on_empty: |
429 |
|
# raise DbException("Not found entry with '{}'".format(q_filter), HTTPStatus.NOT_FOUND) |
430 |
0 |
return {"updated": updated} if found else None |
431 |
|
|
432 |
1 |
def replace(self, table, _id, indata, fail_on_empty=True): |
433 |
|
""" |
434 |
|
Replace the content of an entry |
435 |
|
:param table: collection or table |
436 |
|
:param _id: internal database id |
437 |
|
:param indata: content to replace |
438 |
|
:param fail_on_empty: If nothing matches filter it returns None unless this flag is set tu True, in which case |
439 |
|
it raises a DbException |
440 |
|
:return: Dict with the number of entries replaced |
441 |
|
""" |
442 |
0 |
try: |
443 |
0 |
with self.lock: |
444 |
0 |
for i, _ in self._find(table, self._format_filter({"_id": _id})): |
445 |
0 |
break |
446 |
|
else: |
447 |
0 |
if fail_on_empty: |
448 |
0 |
raise DbException("Not found entry with _id='{}'".format(_id), HTTPStatus.NOT_FOUND) |
449 |
0 |
return None |
450 |
0 |
self.db[table][i] = deepcopy(indata) |
451 |
0 |
return {"updated": 1} |
452 |
0 |
except DbException: |
453 |
0 |
raise |
454 |
0 |
except Exception as e: # TODO refine |
455 |
0 |
raise DbException(str(e)) |
456 |
|
|
457 |
1 |
def create(self, table, indata): |
458 |
|
""" |
459 |
|
Add a new entry at database |
460 |
|
:param table: collection or table |
461 |
|
:param indata: content to be added |
462 |
|
:return: database '_id' of the inserted element. Raises a DbException on error |
463 |
|
""" |
464 |
0 |
try: |
465 |
0 |
id = indata.get("_id") |
466 |
0 |
if not id: |
467 |
0 |
id = str(uuid4()) |
468 |
0 |
indata["_id"] = id |
469 |
0 |
with self.lock: |
470 |
0 |
if table not in self.db: |
471 |
0 |
self.db[table] = [] |
472 |
0 |
self.db[table].append(deepcopy(indata)) |
473 |
0 |
return id |
474 |
0 |
except Exception as e: # TODO refine |
475 |
0 |
raise DbException(str(e)) |
476 |
|
|
477 |
1 |
def create_list(self, table, indata_list): |
478 |
|
""" |
479 |
|
Add a new entry at database |
480 |
|
:param table: collection or table |
481 |
|
:param indata_list: list content to be added |
482 |
|
:return: list of inserted 'id's. Raises a DbException on error |
483 |
|
""" |
484 |
0 |
try: |
485 |
0 |
_ids = [] |
486 |
0 |
with self.lock: |
487 |
0 |
for indata in indata_list: |
488 |
0 |
_id = indata.get("_id") |
489 |
0 |
if not _id: |
490 |
0 |
_id = str(uuid4()) |
491 |
0 |
indata["_id"] = _id |
492 |
0 |
with self.lock: |
493 |
0 |
if table not in self.db: |
494 |
0 |
self.db[table] = [] |
495 |
0 |
self.db[table].append(deepcopy(indata)) |
496 |
0 |
_ids.append(_id) |
497 |
0 |
return _ids |
498 |
0 |
except Exception as e: # TODO refine |
499 |
0 |
raise DbException(str(e)) |
500 |
|
|
501 |
|
|
502 |
1 |
if __name__ == '__main__': |
503 |
|
# some test code |
504 |
0 |
db = DbMemory() |
505 |
0 |
db.create("test", {"_id": 1, "data": 1}) |
506 |
0 |
db.create("test", {"_id": 2, "data": 2}) |
507 |
0 |
db.create("test", {"_id": 3, "data": 3}) |
508 |
0 |
print("must be 3 items:", db.get_list("test")) |
509 |
0 |
print("must return item 2:", db.get_list("test", {"_id": 2})) |
510 |
0 |
db.del_one("test", {"_id": 2}) |
511 |
0 |
print("must be emtpy:", db.get_list("test", {"_id": 2})) |