pymongo — использовать собственный класс курсора

Возможно ли в pymongo, чтобы метод collection.find() возвращал пользовательский класс курсора, который наследуется от базы, но переопределяет, как происходит итерация?

Я хотел бы создать экземпляр конкретных моделей приложения из данных монго внутри курсора прямо во время итерации. Документы имеют атрибут type, который определяет, какой экземпляр следует создать. Я думал, что метод next может просмотреть эти данные и решить, какой тип создать и вернуть. Наследовать от курсора достаточно просто, но я не могу понять, где это подключить к операции find()?

ИЗМЕНИТЬ В качестве альтернативы...

Что я сейчас делаю, так это использую yield для создания генератора, который будет классифицировать объект после того, как будет выполнена выборка.

@classmethod
def gather(cls,place_id):
    """
    Gather instances of all the shouts for one place
    """
    shouts = cls.collection.find({'place_id':place_id})
    for s in shouts:
        yield Shout.classify(s)

@classmethod
def classify(cls,shout):
    if shout.type == 1:
        return Type1(dict(shout))
    elif shout.type == 2:
        return Type2(dict(shout))
    elif shout.type == 3:
        return Type3(dict(shout))

Проблема в том, что это не сохраняет первоначальный подход к доступу к ключу скобки, который инкапсулирован в pymongo Cursor по умолчанию.

Если бы мне пришлось создать класс-оболочку, который будет просто принимать экземпляр курсора и обертывать его методы, какие магические методы мне нужно будет переопределить, чтобы сохранить поведение итераций исходного курсора? Я думаю примерно так:

class StuffCursor():

    cursor = False

    def __init__(self,cursor):
        self.cursor = cursor

    def __getattr__(self,attr):
        #This passes off most key-based calls, like bracket-access, to the cursor
        return getattr(self.cursor,attr)

Это только то, что я мог придумать, но все, что накладывало бы некоторую дополнительную обработку поверх итератора, а затем возвращало измененный итератор, работало бы.


person DeaconDesperado    schedule 11.10.2012    source источник


Ответы (2)


Ненавижу отвечать на мой собственный вопрос, но это может оказаться полезным для любого, кто гуглится. Вот что у меня в итоге получилось:

class ShoutCursor():
    """
    Custom cursor class that will instantiate shout models
    at query time.
    """

    cursor = False

    def __init__(self,cursor):
        self.cursor = cursor
        self.cursor.sort('created',DESCENDING)

    def __iter__(self):
        for ent in self.cursor:
            yield Shout.classify(ent)

    def __len__(self):
        return self.cursor.count()

    def __getitem__(self,index):
        try:
            ents = itertools.islice(self.cursor,index,index+1)
            return [Shout.classify(ent) for ent in ents][0]
        except TypeError:
            return list(Shout.classify(ent) for ent in itertools.islice(self.cursor,index.start,index.stop,index.step))

    def sort(*args,**kwargs):
        self.cursor.sort(*args,**kwargs)

    def __getattr__(self,attr):
        return getattr(self.cursor,attr)

Методы __iter__ и __getiem__ — это то, где модифицированные модели с экземплярами загружаются в генераторы и возвращаются. Другие методы сохраняют собственное действие курсора. Передача курсора в __init__ настраивает функциональность, поэтому особая логика для каждой итерации может выполняться по мере того, как объекты извлекаются из монго.

person DeaconDesperado    schedule 11.10.2012

Я не знаю, можно ли для этого использовать исправление обезьян, но в итоге я сделал это, как показано ниже. Это позволяет мне добавлять/переопределять функции в классе Cursor.

from pymongo import MongoClient
from pymongo.collection import Collection
from pymongo.cursor import Cursor
from pymongo.database import Database

class CustomCursor(Cursor):
    # Customize Cursor here, either by overriding or extending with more methods


class CustomCollection(Collection):
    def __init__(self, database: Database, name: str) -> None:
        super().__init__(database, name)

    # find_one seems to use find, but you may need to override in more places.
    def find(self, *args, **kwargs):
        return CustomCursor(self, *args, **kwargs)


class CustomDatabase(Database):
    def __init__(self, client: MongoClient, name: str) -> None:
        super().__init__(client, name)

    def __getitem__(self, name: str) -> CustomCollection:
        return CustomCollection(self, name)

# usage:
client = MongoClient('127.0.0.1', 27017)
database = CustomDatabase(client, 'mydb')

Я определил базовый класс Serializable с методами from_dict и to_dict. Это позволило мне определить to_list() для CustomCursor, чтобы я мог запрашивать и возвращать экземпляры классов:

result: List[School] = database.schools.find().to_list(School)
person Jeppe    schedule 12.05.2021