コード例 #1
0
def test_bisect_left():
    slt = SortedKeyList(key=modulo)
    assert slt.bisect_left(0) == 0
    slt = SortedKeyList(range(100), key=modulo)
    slt._reset(17)
    slt.update(range(100))
    slt._check()
    assert slt.bisect_left(50) == 0
    assert slt.bisect_left(0) == 0
def test_bisect_left():
    slt = SortedKeyList(key=modulo)
    assert slt.bisect_left(0) == 0
    slt = SortedKeyList(range(100), key=modulo)
    slt._reset(17)
    slt.update(range(100))
    slt._check()
    assert slt.bisect_left(50) == 0
    assert slt.bisect_left(0) == 0
def test_bisect_left():
    slt = SortedKeyList(key=negate)
    assert slt.bisect_left(0) == 0
    slt = SortedKeyList(range(100), key=negate)
    slt._reset(17)
    slt.update(range(100))
    slt._check()
    assert slt.bisect_left(50) == 98
    assert slt.bisect_left(0) == 198
    assert slt.bisect_left(-1) == 200
def test_bisect_left():
    slt = SortedKeyList(key=negate)
    assert slt.bisect_left(0) == 0
    slt = SortedKeyList(range(100), key=negate)
    slt._reset(17)
    slt.update(range(100))
    slt._check()
    assert slt.bisect_left(50) == 98
    assert slt.bisect_left(0) == 198
    assert slt.bisect_left(-1) == 200
コード例 #5
0
class MyMongoCollection:
    def __init__(self, database, name: str):
        from mymongoDB import MyMongoDB

        if not isinstance(database, MyMongoDB):
            raise MongoException(
                "Only MongoDB objects can be passed as the database argument")
        self.name: str = name
        self.parent_database: MyMongoDB = database
        # сортирован по ключам словарей
        self.__docs: SortedKeyList[MongoId, MyMongoDoc] = SortedKeyList(
            key=lambda doc: doc['objectId'])
        self.__indices: Dict[FieldName, SortedKeyList] = SortedDict()
        self.__reserved_ids: MutableSet = SortedSet(set())

    def __repr__(self) -> str:
        meta = f"MyMongoCollection({repr(self.parent_database)}, {self.name})"
        return meta

    def __len__(self) -> int:
        return len(self.__docs)

    def __getstate__(self):
        return self.__dict__

    def __setstate__(self, state):
        self.__dict__ = state

    def sort(self, key):
        self.__docs = SortedKeyList(self.__docs, key=key)
        for field in self.__indices.keys():
            self.create_index(field)

    def create_index(self, field: str) -> None:
        if not isinstance(field, str):
            raise MongoException("'Field' argument must be a string")
        # только документы с данным полем
        relevant_docs: List[MyMongoDoc] = [
            doc for doc in self.__docs if field in doc.keys()
        ]
        self.__indices[field] = SortedKeyList(relevant_docs,
                                              key=lambda doc: doc[field])

    def insert_one(self, doc: Union[Dict,
                                    MutableMapping]) -> MongoLastInserted:
        if not (isinstance(doc, dict)
                or issubclass(doc.__class__, MutableMapping)):
            raise MongoException(
                f"Document \n{doc}\n must be an instance of dict"
                "or a type that inherits from collections.MutableMapping")
        new_doc = _MyMongoDocFactory.get_doc(data=doc)
        if new_doc.objectId in self.__reserved_ids:
            raise MongoException(
                f"Duplicate key error: document already in collection {new_doc}"
            )
        else:
            self.__reserved_ids.add(new_doc.objectId)

        self.__docs.add(new_doc)

        return MongoLastInserted(new_doc)

    def insert_many(self, *docs) -> List[MyMongoDoc]:
        last: List[MyMongoDoc] = list()
        if isinstance(docs[0], dict) or issubclass(docs[0].__class__,
                                                   MutableMapping):
            pass
        # любой другой iterable на свой страх и риск
        elif hasattr(docs[0], "__iter__") and len(docs) == 1:
            docs = docs[0]
        else:
            raise MongoException(
                f"Function accepts iterables of dicts or a type that inherits from "
                "collections.MutableMapping, or simply non-keyword arguments.")
        for doc in docs:
            last.append(self.insert_one(doc).document)
        return last

    def delete_one(self, object_id: MongoId) -> None:
        try:
            if isinstance(object_id, MongoId):
                self.__reserved_ids.remove(object_id)

                # найдём объект для удаления в теле коллекции документов по его MongoId, используя
                # куклу с таким же индексом
                dummy = {"objectId": object_id}
                object_idx: int = self.__docs.bisect_left(dummy)
                doc: MyMongoDoc = self.__docs[object_idx]

                # очистим индексы от удаляемого объекта
                for field in self.__indices.keys():
                    if field in doc.keys():
                        obj_idx = self.__indices[field].bisect_left(doc)
                        self.__indices[field].remove(obj_idx)
                self.__docs.pop(object_idx)
            else:
                raise TypeError(
                    "Only instances of MongoId can serve as document identifiers."
                )
        except KeyError as e:
            raise KeyError(
                f"Collection {self.name} has no object with id {object_id}.")

    def delete_many(self, object_ids: Iterable[MongoId]):
        for object_id in object_ids:
            self.delete_one(object_id)

    def clear(self):
        self.__docs.clear()
        self.__indices.clear()
        self.__reserved_ids.clear()

    def find_one(self, query: Dict[str, Any]):
        result = None
        relevant_docs, _query = self.__get_relevant_info(query)
        if len(_query) == 0:
            return relevant_docs
        else:
            relevant_docs = iter(relevant_docs)
        while result is None:
            candidate = next(relevant_docs)
            try:
                boolean = [
                    candidate[key] == value for key, value in _query.items()
                ]
                if all(boolean):
                    result = candidate
            except KeyError:
                pass
        return result

    def find(self, query: Dict[str, Any]) -> Iterable[MyMongoDoc]:
        result = list()
        relevant_docs, _query = self.__get_relevant_info(query)
        if len(_query) == 0:
            return list(relevant_docs)
        else:
            relevant_docs = iter(relevant_docs)
        for candidate in relevant_docs:
            try:
                boolean = [
                    candidate[key] == value for key, value in _query.items()
                ]
                if all(boolean):
                    result.append(candidate)
            except KeyError:
                pass
        return list(result)

    def find_and_update(self, filter_, update_: Dict[FieldName, Any]) -> None:
        docs = self.find(filter_)
        for doc in docs:
            for k, v in update_.items():
                doc[k] = v
        return docs

    def find_one_and_update(self, filter_, update_: Dict[FieldName,
                                                         Any]) -> None:
        doc = self.find_one(filter_)
        for k, v in update_.items():
            doc[k] = v
        return doc

    def query(
        self, where: MutableMapping[FieldName, Callable[..., Bool]]
    ) -> Iterable[MyMongoDoc]:
        result = list()
        relevant_docs, _query = self.__get_relevant_info(where)
        if len(_query) == 0:
            return list(relevant_docs)
        else:
            # уменьшаем количество документов для поиска
            relevant_docs = iter(relevant_docs)
        for candidate in relevant_docs:
            try:
                boolean = [
                    function(candidate[key])
                    for key, function in _query.items()
                ]
                if all(boolean):
                    result.append(candidate)
            except KeyError:
                pass
        return list(result)

    def __get_relevant_info(self, query) -> Tuple[Iterable, MutableMapping]:
        relevant_docs = set()
        query_ = deepcopy(query)
        indexed_query_fields = self.__indices.keys() & query_.keys()
        # Авось придёт запрос по индексирвованным полям
        if len(indexed_query_fields) != 0:
            # если по полям составлен индекс, то учитывая, что все элементы query логически
            # связаны оператором AND, то для начала можно просто вынуть пересечение документов, удовлетворяющих
            # требованиям к индексированным полям.
            for indexed_query_field in indexed_query_fields:
                # marker - dummy объект, словарик с искомым полем и значением. Мы ищем с логарифмической сложностью,
                # куда его можно приткнуть в наш индекс (находим точку до документов с идентичным значением в
                # искомом поле), а затем извлекаем 0+ равнозначных (в рамках искомого поля) объектов
                marker = {indexed_query_field: query[indexed_query_field]}
                index: SortedKeyList = self.__indices[indexed_query_field]

                # Return an index to insert value in the sorted list. If the value is already present,
                # the insertion point will be before (to the left of) any
                # existing values.
                idx = index.bisect_left(marker)
                while idx < len(index) and query[indexed_query_field] == index[
                        idx][indexed_query_field]:
                    relevant_docs.add(index[idx])
                    idx += 1
            # мы можем больше не смотреть на поля, по которым имеется индекс
            query_ = {
                k: v
                for k, v in query.items() if k not in indexed_query_fields
            }

        else:
            # Что поделать, раз уж запрос такой?
            relevant_docs = self.__docs
        return relevant_docs, query_