Пример #1
0
 def db_connect(self, config, target_version=None):
     """
     Connect to database
     :param config: Configuration of database
     :param target_version: if provided it checks if database contains required version, raising exception otherwise.
     :return: None or raises DbException on error
     """
     try:
         if "logger_name" in config:
             self.logger = logging.getLogger(config["logger_name"])
         master_key = config.get("commonkey") or config.get(
             "masterpassword")
         if master_key:
             self.database_key = master_key
             self.set_secret_key(master_key)
         if config.get("uri"):
             self.client = MongoClient(config["uri"])
         else:
             self.client = MongoClient(config["host"], config["port"])
         # TODO add as parameters also username=config.get("user"), password=config.get("password"))
         # when all modules are ready
         self.db = self.client[config["name"]]
         if "loglevel" in config:
             self.logger.setLevel(getattr(logging, config['loglevel']))
         # get data to try a connection
         now = time()
         while True:
             try:
                 version_data = self.get_one("admin", {"_id": "version"},
                                             fail_on_empty=False,
                                             fail_on_more=True)
                 # check database status is ok
                 if version_data and version_data.get(
                         "status") != 'ENABLED':
                     raise DbException(
                         "Wrong database status '{}'".format(
                             version_data.get("status")),
                         http_code=HTTPStatus.INTERNAL_SERVER_ERROR)
                 # check version
                 db_version = None if not version_data else version_data.get(
                     "version")
                 if target_version and target_version != db_version:
                     raise DbException(
                         "Invalid database version {}. Expected {}".format(
                             db_version, target_version))
                 # get serial
                 if version_data and version_data.get("serial"):
                     self.secret_obtained = True
                     self.set_secret_key(b64decode(version_data["serial"]))
                 self.logger.info(
                     "Connected to database {} version {}".format(
                         config["name"], db_version))
                 return
             except errors.ConnectionFailure as e:
                 if time() - now >= self.conn_initial_timout:
                     raise
                 self.logger.info("Waiting to database up {}".format(e))
                 sleep(2)
     except errors.PyMongoError as e:
         raise DbException(e)
Пример #2
0
    def get_one_by_id(db, session, topic, id):
        # find owned by this project
        _filter = BaseTopic._get_project_filter(session,
                                                write=False,
                                                show_all=False)
        _filter["id"] = id
        desc_list = db.get_list(topic, _filter)
        if len(desc_list) == 1:
            return desc_list[0]
        elif len(desc_list) > 1:
            raise DbException(
                "Found more than one {} with id='{}' belonging to this project"
                .format(topic[:-1], id), HTTPStatus.CONFLICT)

        # not found any: try to find public
        _filter = BaseTopic._get_project_filter(session,
                                                write=False,
                                                show_all=True)
        _filter["id"] = id
        desc_list = db.get_list(topic, _filter)
        if not desc_list:
            raise DbException(
                "Not found any {} with id='{}'".format(topic[:-1], id),
                HTTPStatus.NOT_FOUND)
        elif len(desc_list) == 1:
            return desc_list[0]
        else:
            raise DbException(
                "Found more than one public {} with id='{}'; and no one belonging to this project"
                .format(topic[:-1], id), HTTPStatus.CONFLICT)
Пример #3
0
 def get_one(self,
             table,
             q_filter=None,
             fail_on_empty=True,
             fail_on_more=True):
     """
     Obtain one entry matching q_filter
     :param table: collection or table
     :param q_filter: Filter
     :param fail_on_empty: If nothing matches filter it returns None unless this flag is set tu True, in which case
     it raises a DbException
     :param fail_on_more: If more than one matches filter it returns one of then unless this flag is set tu True, so
     that it raises a DbException
     :return: The requested element, or None
     """
     try:
         result = None
         with self.lock:
             for _, row in self._find(table, self._format_filter(q_filter)):
                 if not fail_on_more:
                     return deepcopy(row)
                 if result:
                     raise DbException(
                         "Found more than one entry with filter='{}'".
                         format(q_filter), HTTPStatus.CONFLICT.value)
                 result = row
         if not result and fail_on_empty:
             raise DbException(
                 "Not found entry with filter='{}'".format(q_filter),
                 HTTPStatus.NOT_FOUND)
         return deepcopy(result)
     except Exception as e:  # TODO refine
         raise DbException(str(e))
Пример #4
0
 def replace(self, table, _id, indata, fail_on_empty=True):
     """
     Replace the content of an entry
     :param table: collection or table
     :param _id: internal database id
     :param indata: content to replace
     :param fail_on_empty: If nothing matches filter it returns None unless this flag is set tu True, in which case
     it raises a DbException
     :return: Dict with the number of entries replaced
     """
     try:
         with self.lock:
             for i, _ in self._find(table, self._format_filter({"_id":
                                                                _id})):
                 break
             else:
                 if fail_on_empty:
                     raise DbException(
                         "Not found entry with _id='{}'".format(_id),
                         HTTPStatus.NOT_FOUND)
                 return None
             self.db[table][i] = deepcopy(indata)
         return {"updated": 1}
     except DbException:
         raise
     except Exception as e:  # TODO refine
         raise DbException(str(e))
Пример #5
0
 def _iterate_keys(k, db_nested, populate=True):
     k_list = k.split(".")
     k_item_prev = k_list[0]
     populated = False
     if k_item_prev not in db_nested and populate:
         populated = True
         db_nested[k_item_prev] = None
     for k_item in k_list[1:]:
         if isinstance(db_nested[k_item_prev], dict):
             if k_item not in db_nested[k_item_prev]:
                 if not populate:
                     raise DbException(
                         "Cannot set '{}', not existing '{}'".format(
                             k, k_item))
                 populated = True
                 db_nested[k_item_prev][k_item] = None
         elif isinstance(db_nested[k_item_prev],
                         list) and k_item.isdigit():
             # extend list with Nones if index greater than list
             k_item = int(k_item)
             if k_item >= len(db_nested[k_item_prev]):
                 if not populate:
                     raise DbException(
                         "Cannot set '{}', index too large '{}'".format(
                             k, k_item))
                 populated = True
                 db_nested[k_item_prev] += [None] * (
                     k_item - len(db_nested[k_item_prev]) + 1)
         elif db_nested[k_item_prev] is None:
             if not populate:
                 raise DbException(
                     "Cannot set '{}', not existing '{}'".format(
                         k, k_item))
             populated = True
             db_nested[k_item_prev] = {k_item: None}
         else:  # number, string, boolean, ... or list but with not integer key
             raise DbException(
                 "Cannot set '{}' on existing '{}={}'".format(
                     k, k_item_prev, db_nested[k_item_prev]))
         db_nested = db_nested[k_item_prev]
         k_item_prev = k_item
     return db_nested, k_item_prev, populated
Пример #6
0
 def set_list(self,
              table,
              q_filter,
              update_dict,
              unset=None,
              pull=None,
              push=None,
              push_list=None,
              pull_list=None):
     """
     Modifies al matching entries at database
     :param table: collection or table
     :param q_filter: Filter
     :param update_dict: Plain dictionary with the content to be updated. It is a dot separated keys and a value
     :param unset: Plain dictionary with the content to be removed if exist. It is a dot separated keys, value is
                   ignored. If not exist, it is ignored
     :param pull: Plain dictionary with the content to be removed from an array. It is a dot separated keys and value
                  if exist in the array is removed. If not exist, it is ignored
     :param push: Plain dictionary with the content to be appended to an array. It is a dot separated keys, the
                  single value is appended to the end of the array
     :param pull_list: Same as pull but values are arrays where each item is removed from the array
     :param push_list: Same as push but values are arrays where each item is and appended instead of appending the
                       whole array
     :return: Dict with the number of entries modified
     """
     try:
         db_oper = {}
         if update_dict:
             db_oper["$set"] = update_dict
         if unset:
             db_oper["$unset"] = unset
         if pull or pull_list:
             db_oper["$pull"] = pull or {}
             if pull_list:
                 db_oper["$pull"].update(
                     {k: {
                         "$in": v
                     }
                      for k, v in pull_list.items()})
         if push or push_list:
             db_oper["$push"] = push or {}
             if push_list:
                 db_oper["$push"].update(
                     {k: {
                         "$each": v
                     }
                      for k, v in push_list.items()})
         with self.lock:
             collection = self.db[table]
             rows = collection.update_many(self._format_filter(q_filter),
                                           db_oper)
         return {"modified": rows.modified_count}
     except Exception as e:  # TODO refine
         raise DbException(e)
Пример #7
0
 def del_one(self, table, q_filter=None, fail_on_empty=True):
     """
     Deletes one entry that matches q_filter
     :param table: collection or table
     :param q_filter: Filter
     :param fail_on_empty: If nothing matches filter it returns '0' deleted unless this flag is set tu True, in
     which case it raises a DbException
     :return: Dict with the number of entries deleted
     """
     try:
         with self.lock:
             collection = self.db[table]
             rows = collection.delete_one(self._format_filter(q_filter))
         if rows.deleted_count == 0:
             if fail_on_empty:
                 raise DbException(
                     "Not found any {} with filter='{}'".format(
                         table[:-1], q_filter), HTTPStatus.NOT_FOUND)
             return None
         return {"deleted": rows.deleted_count}
     except Exception as e:  # TODO refine
         raise DbException(e)
Пример #8
0
 def del_one(self, table, q_filter=None, fail_on_empty=True):
     """
     Deletes one entry that matches q_filter
     :param table: collection or table
     :param q_filter: Filter
     :param fail_on_empty: If nothing matches filter it returns '0' deleted unless this flag is set tu True, in
     which case it raises a DbException
     :return: Dict with the number of entries deleted
     """
     try:
         with self.lock:
             for i, _ in self._find(table, self._format_filter(q_filter)):
                 break
             else:
                 if fail_on_empty:
                     raise DbException(
                         "Not found entry with filter='{}'".format(
                             q_filter), HTTPStatus.NOT_FOUND)
                 return None
             del self.db[table][i]
         return {"deleted": 1}
     except Exception as e:  # TODO refine
         raise DbException(str(e))
Пример #9
0
 def create(self, table, indata):
     """
     Add a new entry at database
     :param table: collection or table
     :param indata: content to be added
     :return: database id of the inserted element. Raises a DbException on error
     """
     try:
         with self.lock:
             collection = self.db[table]
             data = collection.insert_one(indata)
         return data.inserted_id
     except Exception as e:  # TODO refine
         raise DbException(e)
Пример #10
0
 def replace(self, table, _id, indata, fail_on_empty=True):
     """
     Replace the content of an entry
     :param table: collection or table
     :param _id: internal database id
     :param indata: content to replace
     :param fail_on_empty: If nothing matches filter it returns None unless this flag is set tu True, in which case
     it raises a DbException
     :return: Dict with the number of entries replaced
     """
     try:
         db_filter = {"_id": _id}
         with self.lock:
             collection = self.db[table]
             rows = collection.replace_one(db_filter, indata)
         if rows.matched_count == 0:
             if fail_on_empty:
                 raise DbException(
                     "Not found any {} with _id='{}'".format(
                         table[:-1], _id), HTTPStatus.NOT_FOUND)
             return None
         return {"replaced": rows.modified_count}
     except Exception as e:  # TODO refine
         raise DbException(e)
Пример #11
0
 def get_one(self,
             table,
             q_filter=None,
             fail_on_empty=True,
             fail_on_more=True):
     """
     Obtain one entry matching q_filter
     :param table: collection or table
     :param q_filter: Filter
     :param fail_on_empty: If nothing matches filter it returns None unless this flag is set tu True, in which case
     it raises a DbException
     :param fail_on_more: If more than one matches filter it returns one of then unless this flag is set tu True, so
     that it raises a DbException
     :return: The requested element, or None
     """
     try:
         db_filter = self._format_filter(q_filter)
         with self.lock:
             collection = self.db[table]
             if not (fail_on_empty and fail_on_more):
                 return collection.find_one(db_filter)
             rows = collection.find(db_filter)
         if rows.count() == 0:
             if fail_on_empty:
                 raise DbException(
                     "Not found any {} with filter='{}'".format(
                         table[:-1], q_filter), HTTPStatus.NOT_FOUND)
             return None
         elif rows.count() > 1:
             if fail_on_more:
                 raise DbException(
                     "Found more than one {} with filter='{}'".format(
                         table[:-1], q_filter), HTTPStatus.CONFLICT)
         return rows[0]
     except Exception as e:  # TODO refine
         raise DbException(e)
Пример #12
0
 def del_list(self, table, q_filter=None):
     """
     Deletes all entries that match q_filter
     :param table: collection or table
     :param q_filter: Filter
     :return: Dict with the number of entries deleted
     """
     try:
         with self.lock:
             collection = self.db[table]
             rows = collection.delete_many(self._format_filter(q_filter))
         return {"deleted": rows.deleted_count}
     except DbException:
         raise
     except Exception as e:  # TODO refine
         raise DbException(e)
Пример #13
0
 def create_list(self, table, indata_list):
     """
     Add several entries at once
     :param table: collection or table
     :param indata_list: content list to be added.
     :return: the list of inserted '_id's. Exception on error
     """
     try:
         for item in indata_list:
             if item.get("_id") is None:
                 item["_id"] = str(uuid4())
         with self.lock:
             collection = self.db[table]
             data = collection.insert_many(indata_list)
         return data.inserted_ids
     except Exception as e:  # TODO refine
         raise DbException(e)
Пример #14
0
 def count(self, table, q_filter=None):
     """
     Count the number of entries matching q_filter
     :param table: collection or table
     :param q_filter: Filter
     :return: number of entries found (can be zero)
     :raise: DbException on error
     """
     try:
         with self.lock:
             return sum(
                 1
                 for x in self._find(table, self._format_filter(q_filter)))
     except DbException:
         raise
     except Exception as e:  # TODO refine
         raise DbException(str(e))
Пример #15
0
 def get_list(self, table, q_filter=None):
     """
     Obtain a list of entries matching q_filter
     :param table: collection or table
     :param q_filter: Filter
     :return: a list (can be empty) with the found entries. Raises DbException on error
     """
     try:
         result = []
         with self.lock:
             for _, row in self._find(table, self._format_filter(q_filter)):
                 result.append(deepcopy(row))
         return result
     except DbException:
         raise
     except Exception as e:  # TODO refine
         raise DbException(str(e))
Пример #16
0
 def count(self, table, q_filter=None):
     """
     Count the number of entries matching q_filter
     :param table: collection or table
     :param q_filter: Filter
     :return: number of entries found (can be zero)
     :raise: DbException on error
     """
     try:
         with self.lock:
             collection = self.db[table]
             db_filter = self._format_filter(q_filter)
             count = collection.count(db_filter)
         return count
     except DbException:
         raise
     except Exception as e:  # TODO refine
         raise DbException(e)
Пример #17
0
 def set_one(self,
             table,
             q_filter,
             update_dict,
             fail_on_empty=True,
             unset=None,
             pull=None,
             push=None,
             push_list=None,
             pull_list=None):
     """
     Modifies an entry at database
     :param table: collection or table
     :param q_filter: Filter
     :param update_dict: Plain dictionary with the content to be updated. It is a dot separated keys and a value
     :param fail_on_empty: If nothing matches filter it returns None unless this flag is set tu True, in which case
     it raises a DbException
     :param unset: Plain dictionary with the content to be removed if exist. It is a dot separated keys, value is
                   ignored. If not exist, it is ignored
     :param pull: Plain dictionary with the content to be removed from an array. It is a dot separated keys and value
                  if exist in the array is removed. If not exist, it is ignored
     :param pull_list: Same as pull but values are arrays where each item is removed from the array
     :param push: Plain dictionary with the content to be appended to an array. It is a dot separated keys and value
                  is appended to the end of the array
     :param push_list: Same as push but values are arrays where each item is and appended instead of appending the
                       whole array
     :return: Dict with the number of entries modified. None if no matching is found.
     """
     with self.lock:
         for i, db_item in self._find(table, self._format_filter(q_filter)):
             updated = self._update(db_item,
                                    update_dict,
                                    unset=unset,
                                    pull=pull,
                                    push=push,
                                    push_list=push_list,
                                    pull_list=pull_list)
             return {"updated": 1 if updated else 0}
         else:
             if fail_on_empty:
                 raise DbException(
                     "Not found entry with _id='{}'".format(q_filter),
                     HTTPStatus.NOT_FOUND)
             return None
Пример #18
0
 def create(self, table, indata):
     """
     Add a new entry at database
     :param table: collection or table
     :param indata: content to be added
     :return: database '_id' of the inserted element. Raises a DbException on error
     """
     try:
         id = indata.get("_id")
         if not id:
             id = str(uuid4())
             indata["_id"] = id
         with self.lock:
             if table not in self.db:
                 self.db[table] = []
             self.db[table].append(deepcopy(indata))
         return id
     except Exception as e:  # TODO refine
         raise DbException(str(e))
Пример #19
0
 def del_list(self, table, q_filter=None):
     """
     Deletes all entries that match q_filter
     :param table: collection or table
     :param q_filter: Filter
     :return: Dict with the number of entries deleted
     """
     try:
         id_list = []
         with self.lock:
             for i, _ in self._find(table, self._format_filter(q_filter)):
                 id_list.append(i)
         deleted = len(id_list)
         for i in reversed(id_list):
             del self.db[table][i]
         return {"deleted": deleted}
     except DbException:
         raise
     except Exception as e:  # TODO refine
         raise DbException(str(e))
Пример #20
0
 def get_list(self, table, q_filter=None):
     """
     Obtain a list of entries matching q_filter
     :param table: collection or table
     :param q_filter: Filter
     :return: a list (can be empty) with the found entries. Raises DbException on error
     """
     try:
         result = []
         with self.lock:
             collection = self.db[table]
             db_filter = self._format_filter(q_filter)
             rows = collection.find(db_filter)
         for row in rows:
             result.append(row)
         return result
     except DbException:
         raise
     except Exception as e:  # TODO refine
         raise DbException(e)
Пример #21
0
 def create_list(self, table, indata_list):
     """
     Add a new entry at database
     :param table: collection or table
     :param indata_list: list content to be added
     :return: list of inserted 'id's. Raises a DbException on error
     """
     try:
         _ids = []
         with self.lock:
             for indata in indata_list:
                 _id = indata.get("_id")
                 if not _id:
                     _id = str(uuid4())
                     indata["_id"] = _id
                 with self.lock:
                     if table not in self.db:
                         self.db[table] = []
                     self.db[table].append(deepcopy(indata))
                 _ids.append(_id)
         return _ids
     except Exception as e:  # TODO refine
         raise DbException(str(e))
Пример #22
0
        def recursive_find(key_list, key_next_index, content, oper, target):
            if key_next_index == len(key_list) or content is None:
                try:
                    if oper in ("eq", "cont"):
                        if isinstance(target, list):
                            if isinstance(content, list):
                                return any(content_item in target
                                           for content_item in content)
                            return content in target
                        elif isinstance(content, list):
                            return target in content
                        else:
                            return content == target
                    elif oper in ("neq", "ne", "ncont"):
                        if isinstance(target, list):
                            if isinstance(content, list):
                                return all(content_item not in target
                                           for content_item in content)
                            return content not in target
                        elif isinstance(content, list):
                            return target not in content
                        else:
                            return content != target
                    if oper == "gt":
                        return content > target
                    elif oper == "gte":
                        return content >= target
                    elif oper == "lt":
                        return content < target
                    elif oper == "lte":
                        return content <= target
                    else:
                        raise DbException(
                            "Unknown filter operator '{}' in key '{}'".format(
                                oper, ".".join(key_list)),
                            http_code=HTTPStatus.BAD_REQUEST)
                except TypeError:
                    return False

            elif isinstance(content, dict):
                return recursive_find(key_list, key_next_index + 1,
                                      content.get(key_list[key_next_index]),
                                      oper, target)
            elif isinstance(content, list):
                look_for_match = True  # when there is a match return immediately
                if (target is None) != (oper in ("neq", "ne", "ncont")
                                        ):  # one True and other False (Xor)
                    look_for_match = False  # when there is not a match return immediately

                for content_item in content:
                    if key_list[key_next_index] == "ANYINDEX" and isinstance(
                            v, dict):
                        matches = True
                        for k2, v2 in target.items():
                            k_new_list = k2.split(".")
                            new_operator = "eq"
                            if k_new_list[-1] in ("eq", "ne", "gt", "gte",
                                                  "lt", "lte", "cont", "ncont",
                                                  "neq"):
                                new_operator = k_new_list.pop()
                            if not recursive_find(k_new_list, 0, content_item,
                                                  new_operator, v2):
                                matches = False
                                break

                    else:
                        matches = recursive_find(key_list, key_next_index,
                                                 content_item, oper, target)
                    if matches == look_for_match:
                        return matches
                if key_list[key_next_index].isdecimal() and int(
                        key_list[key_next_index]) < len(content):
                    matches = recursive_find(
                        key_list, key_next_index + 1,
                        content[int(key_list[key_next_index])], oper, target)
                    if matches == look_for_match:
                        return matches
                return not look_for_match
            else:  # content is not dict, nor list neither None, so not found
                if oper in ("neq", "ne", "ncont"):
                    return target is not None
                else:
                    return target is None
Пример #23
0
    def _format_filter(q_filter):
        """
        Translate query string q_filter into mongo database filter
        :param q_filter: Query string content. Follows SOL005 section 4.3.2 guidelines, with the follow extensions and
        differences:
            It accept ".nq" (not equal) in addition to ".neq".
            For arrays you can specify index (concrete index must match), nothing (any index may match) or 'ANYINDEX'
            (two or more matches applies for the same array element). Examples:
                with database register: {A: [{B: 1, C: 2}, {B: 6, C: 9}]}
                query 'A.B=6' matches because array A contains one element with B equal to 6
                query 'A.0.B=6' does no match because index 0 of array A contains B with value 1, but not 6
                query 'A.B=6&A.C=2' matches because one element of array matches B=6 and other matchesC=2
                query 'A.ANYINDEX.B=6&A.ANYINDEX.C=2' does not match because it is needed the same element of the
                    array matching both

        Examples of translations from SOL005 to  >> mongo  # comment
            A=B; A.eq=B         >> A: B             # must contain key A and equal to B or be a list that contains B
            A.cont=B            >> A: B
            A=B&A=C; A=B,C      >> A: {$in: [B, C]} # must contain key A and equal to B or C or be a list that contains
                # B or C
            A.cont=B&A.cont=C; A.cont=B,C  >> A: {$in: [B, C]}
            A.ncont=B           >> A: {$nin: B}     # must not contain key A or if present not equal to B or if a list,
                # it must not not contain B
            A.ncont=B,C; A.ncont=B&A.ncont=C    >> A: {$nin: [B,C]}     # must not contain key A or if present not equal
                # neither B nor C; or if a list, it must not contain neither B nor C
            A.ne=B&A.ne=C; A.ne=B,C             >> A: {$nin: [B, C]}
            A.gt=B              >> A: {$gt: B}      # must contain key A and greater than B
            A.ne=B; A.neq=B         >> A: {$ne: B}          # must not contain key A or if present not equal to B, or if
                # an array not contain B
            A.ANYINDEX.B=C          >> A: {$elemMatch: {B=C}
        :return: database mongo filter
        """
        try:
            db_filter = {}
            if not q_filter:
                return db_filter
            for query_k, query_v in q_filter.items():
                dot_index = query_k.rfind(".")
                if dot_index > 1 and query_k[dot_index + 1:] in (
                        "eq", "ne", "gt", "gte", "lt", "lte", "cont", "ncont",
                        "neq"):
                    operator = "$" + query_k[dot_index + 1:]
                    if operator == "$neq":
                        operator = "$ne"
                    k = query_k[:dot_index]
                else:
                    operator = "$eq"
                    k = query_k

                v = query_v
                if isinstance(v, list):
                    if operator in ("$eq", "$cont"):
                        operator = "$in"
                        v = query_v
                    elif operator in ("$ne", "$ncont"):
                        operator = "$nin"
                        v = query_v
                    else:
                        v = query_v.join(",")

                if operator in ("$eq", "$cont"):
                    # v cannot be a comma separated list, because operator would have been changed to $in
                    db_v = v
                elif operator == "$ncount":
                    # v cannot be a comma separated list, because operator would have been changed to $nin
                    db_v = {"$ne": v}
                else:
                    db_v = {operator: v}

                # process the ANYINDEX word at k.
                kleft, _, kright = k.rpartition(".ANYINDEX.")
                while kleft:
                    k = kleft
                    db_v = {"$elemMatch": {kright: db_v}}
                    kleft, _, kright = k.rpartition(".ANYINDEX.")

                # insert in db_filter
                # maybe db_filter[k] exist. e.g. in the query string for values between 5 and 8: "a.gt=5&a.lt=8"
                deep_update(db_filter, {k: db_v})

            return db_filter
        except Exception as e:
            raise DbException(
                "Invalid query string filter at {}:{}. Error: {}".format(
                    query_k, v, e),
                http_code=HTTPStatus.BAD_REQUEST)
Пример #24
0
    def set_one(self,
                table,
                q_filter,
                update_dict,
                fail_on_empty=True,
                unset=None,
                pull=None,
                push=None,
                push_list=None,
                pull_list=None):
        """
        Modifies an entry at database
        :param table: collection or table
        :param q_filter: Filter
        :param update_dict: Plain dictionary with the content to be updated. It is a dot separated keys and a value
        :param fail_on_empty: If nothing matches filter it returns None unless this flag is set tu True, in which case
        it raises a DbException
        :param unset: Plain dictionary with the content to be removed if exist. It is a dot separated keys, value is
                      ignored. If not exist, it is ignored
        :param pull: Plain dictionary with the content to be removed from an array. It is a dot separated keys and value
                     if exist in the array is removed. If not exist, it is ignored
        :param pull_list: Same as pull but values are arrays where each item is removed from the array
        :param push: Plain dictionary with the content to be appended to an array. It is a dot separated keys and value
                     is appended to the end of the array
        :param push_list: Same as push but values are arrays where each item is and appended instead of appending the
                          whole array
        :return: Dict with the number of entries modified. None if no matching is found.
        """
        try:
            db_oper = {}
            if update_dict:
                db_oper["$set"] = update_dict
            if unset:
                db_oper["$unset"] = unset
            if pull or pull_list:
                db_oper["$pull"] = pull or {}
                if pull_list:
                    db_oper["$pull"].update(
                        {k: {
                            "$in": v
                        }
                         for k, v in pull_list.items()})
            if push or push_list:
                db_oper["$push"] = push or {}
                if push_list:
                    db_oper["$push"].update(
                        {k: {
                            "$each": v
                        }
                         for k, v in push_list.items()})

            with self.lock:
                collection = self.db[table]
                rows = collection.update_one(self._format_filter(q_filter),
                                             db_oper)
            if rows.matched_count == 0:
                if fail_on_empty:
                    raise DbException(
                        "Not found any {} with filter='{}'".format(
                            table[:-1], q_filter), HTTPStatus.NOT_FOUND)
                return None
            return {"modified": rows.modified_count}
        except Exception as e:  # TODO refine
            raise DbException(e)
Пример #25
0
    def _update(self,
                db_item,
                update_dict,
                unset=None,
                pull=None,
                push=None,
                push_list=None,
                pull_list=None):
        """
        Modifies an entry at database
        :param db_item: entry of the table to update
        :param update_dict: Plain dictionary with the content to be updated. It is a dot separated keys and a value
        :param unset: Plain dictionary with the content to be removed if exist. It is a dot separated keys, value is
                      ignored. If not exist, it is ignored
        :param pull: Plain dictionary with the content to be removed from an array. It is a dot separated keys and value
                     if exist in the array is removed. If not exist, it is ignored
        :param pull_list: Same as pull but values are arrays where each item is removed from the array
        :param push: Plain dictionary with the content to be appended to an array. It is a dot separated keys and value
                     is appended to the end of the array
        :param push_list: Same as push but values are arrays where each item is and appended instead of appending the
                          whole array
        :return: True if database has been changed, False if not; Exception on error
        """
        def _iterate_keys(k, db_nested, populate=True):
            k_list = k.split(".")
            k_item_prev = k_list[0]
            populated = False
            if k_item_prev not in db_nested and populate:
                populated = True
                db_nested[k_item_prev] = None
            for k_item in k_list[1:]:
                if isinstance(db_nested[k_item_prev], dict):
                    if k_item not in db_nested[k_item_prev]:
                        if not populate:
                            raise DbException(
                                "Cannot set '{}', not existing '{}'".format(
                                    k, k_item))
                        populated = True
                        db_nested[k_item_prev][k_item] = None
                elif isinstance(db_nested[k_item_prev],
                                list) and k_item.isdigit():
                    # extend list with Nones if index greater than list
                    k_item = int(k_item)
                    if k_item >= len(db_nested[k_item_prev]):
                        if not populate:
                            raise DbException(
                                "Cannot set '{}', index too large '{}'".format(
                                    k, k_item))
                        populated = True
                        db_nested[k_item_prev] += [None] * (
                            k_item - len(db_nested[k_item_prev]) + 1)
                elif db_nested[k_item_prev] is None:
                    if not populate:
                        raise DbException(
                            "Cannot set '{}', not existing '{}'".format(
                                k, k_item))
                    populated = True
                    db_nested[k_item_prev] = {k_item: None}
                else:  # number, string, boolean, ... or list but with not integer key
                    raise DbException(
                        "Cannot set '{}' on existing '{}={}'".format(
                            k, k_item_prev, db_nested[k_item_prev]))
                db_nested = db_nested[k_item_prev]
                k_item_prev = k_item
            return db_nested, k_item_prev, populated

        updated = False
        try:
            if update_dict:
                for dot_k, v in update_dict.items():
                    dict_to_update, key_to_update, _ = _iterate_keys(
                        dot_k, db_item)
                    dict_to_update[key_to_update] = v
                    updated = True
            if unset:
                for dot_k in unset:
                    try:
                        dict_to_update, key_to_update, _ = _iterate_keys(
                            dot_k, db_item, populate=False)
                        del dict_to_update[key_to_update]
                        updated = True
                    except Exception:
                        pass
            if pull:
                for dot_k, v in pull.items():
                    try:
                        dict_to_update, key_to_update, _ = _iterate_keys(
                            dot_k, db_item, populate=False)
                    except Exception:
                        continue
                    if key_to_update not in dict_to_update:
                        continue
                    if not isinstance(dict_to_update[key_to_update], list):
                        raise DbException(
                            "Cannot pull '{}'. Target is not a list".format(
                                dot_k))
                    while v in dict_to_update[key_to_update]:
                        dict_to_update[key_to_update].remove(v)
                        updated = True
            if pull_list:
                for dot_k, v in pull_list.items():
                    if not isinstance(v, list):
                        raise DbException(
                            "Invalid content at pull_list, '{}' must be an array"
                            .format(dot_k),
                            http_code=HTTPStatus.BAD_REQUEST)
                    try:
                        dict_to_update, key_to_update, _ = _iterate_keys(
                            dot_k, db_item, populate=False)
                    except Exception:
                        continue
                    if key_to_update not in dict_to_update:
                        continue
                    if not isinstance(dict_to_update[key_to_update], list):
                        raise DbException(
                            "Cannot pull_list '{}'. Target is not a list".
                            format(dot_k))
                    for single_v in v:
                        while single_v in dict_to_update[key_to_update]:
                            dict_to_update[key_to_update].remove(single_v)
                            updated = True
            if push:
                for dot_k, v in push.items():
                    dict_to_update, key_to_update, populated = _iterate_keys(
                        dot_k, db_item)
                    if isinstance(
                            dict_to_update,
                            dict) and key_to_update not in dict_to_update:
                        dict_to_update[key_to_update] = [v]
                        updated = True
                    elif populated and dict_to_update[key_to_update] is None:
                        dict_to_update[key_to_update] = [v]
                        updated = True
                    elif not isinstance(dict_to_update[key_to_update], list):
                        raise DbException(
                            "Cannot push '{}'. Target is not a list".format(
                                dot_k))
                    else:
                        dict_to_update[key_to_update].append(v)
                        updated = True
            if push_list:
                for dot_k, v in push_list.items():
                    if not isinstance(v, list):
                        raise DbException(
                            "Invalid content at push_list, '{}' must be an array"
                            .format(dot_k),
                            http_code=HTTPStatus.BAD_REQUEST)
                    dict_to_update, key_to_update, populated = _iterate_keys(
                        dot_k, db_item)
                    if isinstance(
                            dict_to_update,
                            dict) and key_to_update not in dict_to_update:
                        dict_to_update[key_to_update] = v.copy()
                        updated = True
                    elif populated and dict_to_update[key_to_update] is None:
                        dict_to_update[key_to_update] = v.copy()
                        updated = True
                    elif not isinstance(dict_to_update[key_to_update], list):
                        raise DbException(
                            "Cannot push '{}'. Target is not a list".format(
                                dot_k),
                            http_code=HTTPStatus.CONFLICT)
                    else:
                        dict_to_update[key_to_update] += v
                        updated = True

            return updated
        except DbException:
            raise
        except Exception as e:  # TODO refine
            raise DbException(str(e))