Добавление элемента-массива документа mongoDB с использованием Python Eve

Фон: (с использованием Евы и Монго)

Я работаю в Python, используя подключение к библиотеке поставщика Eve REST и к mongoDB, чтобы предоставить ряд конечных точек REST из базы данных. До сих пор мне повезло с Евой, но я столкнулся с проблемой, которая может быть немного выше того, что Ева может делать изначально.

Моя проблема в том, что в моем формате документа mongoDb есть поле (называемое «слотами»), значение которого представляет собой список / массив словарей / встроенных документов.

Итак, структура документа mongoDB:

{
   blah1: data1,
   blah2: data2,
   ...
   slots: [
       {thing1:data1, thing2:data2},
       {thingX:dataX, thingY:dataY}
   ]
}

Мне нужно добавить новые записи (например, добавить предварительно заполненные словари) в список «слотов».

Если я представлю, как вставить вставку напрямую через pymongo, это будет выглядеть так:

mongo.connection = MongoClient()
mongo.db = mongo.connection['myDB']
mongo.coll = mongo.db['myCollection']

...

mongo.coll.update({'_id' : document_id}, 
                  {'$push': { "slot" : {"thing1":"data1","thingX":"dataX"}  }  } )

Комбинация действий REST / URI, которую я хотел бы выполнить для этого действия, представляет собой POST на '_id / slots', например URI /app/012345678901234567890123/slots.

Проблема: (вставка элемента в массив в Eve)

Из SO: Как добавить к типу списка в Python Eve без замены старых значений и eve project issue похоже, что Ева в настоящее время не поддерживает работу со встроенными документами mongoDB (или массивами?), если только весь встроенный документ не будет перезаписан, а перезапись всего массива в моем случае очень нежелательна.


Итак, если предположить, что у ее истинной Евы нет метода, позволяющего вставлять элементы массива (и учитывая, что у меня уже есть множество других конечных точек, хорошо работающих внутри Евы) ...


... Сейчас я ищу способ внутри конфигурации Eve / Flask с несколькими рабочими конечными точками перехватить и изменить запись mongoDB Евы только для этой одной конечной точки.

Я знаю (худший случай), что могу переопределить маршрутизацию Евы и полностью выполнить запись вручную, но тогда мне пришлось бы управлять _updated, проверять вручную и изменять значение _etag документов, и я бы, конечно, предпочел бы не делать этого. напишите новый код для.

Я просмотрел перехватчики событий базы данных Евы, но не увидеть способ изменения выполняемых команд базы данных (я вижу, как изменить данные, но не команды).

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


person Mike Lutz    schedule 29.05.2015    source источник


Ответы (2)


Это интересный вопрос. Я считаю, что для достижения цели вам необходимо выполнить два действия:

  1. Создайте и передайте собственный валидатор.
  2. Создайте и передайте настраиваемый уровень данных Mongo.

Может показаться, что это слишком много работы, но, вероятно, это не так.


Пользовательский валидатор

Пользовательский валидатор будет необходим, потому что, когда вы выполняете свой запрос PATCH на конечной точке с «включенной push», вы хотите передать документ, который синтаксически отличается от схемы проверки конечной точки. Вы собираетесь передать dict ({"slot": {"thing1": "data1", "thingX": "dataX"}}), тогда как конечная точка ожидает список:

'mycollection': {
    'type': 'list',
    'schema': {
        'type': 'dict',
        'schema': {
            'thing1': {'type': 'string'},
            'thingX': {'type': 'string'},
        }
    }
}

Если вы не настроите проверку, вы получите ошибку проверки (list type expected). Думаю, ваш собственный валидатор может выглядеть примерно так:

from eve.data.mongo.validation import Validator
from flask import request

class MyValidator(Validator):
    def validate_replace(self, document, _id, original_document=None):
        if self.resource = 'mycollection' and request.method = 'PATCH':
            # you want to perform some real validation here
            return True
        return super(Validator, self).validate(document)

Имейте в виду, что я не пробовал этот код, поэтому может потребоваться некоторая корректировка здесь и там.

Альтернативный подход - настроить альтернативную конечную точку только для запросов PATCH. Эта конечная точка будет использовать тот же источник данных и иметь схему, подобную dict. Это позволит избежать необходимости в настраиваемом валидаторе, а также у вас по-прежнему будут нормальные атомарные обновления полей ($set) на стандартной конечной точке. На самом деле я думаю, что мне больше нравится этот подход, так как вы не теряете функциональность и не уменьшаете сложность. Инструкции по обращению нескольких конечных точек к одному источнику данных см. В документации


Пользовательский уровень данных

Это необходимо, потому что вы хотите выполнить $push вместо $set, когда mycollection участвует в запросе PATCH. Что-то вроде этого может быть:

from eve.io.mongo import Mongo
from flask import request

class MyMongo(Mongo):
    def update(self, resource, id_, updates, original):
        op = '$push' if resource == 'mycollection' else '$set'
        return self._change_request(resource, id_, {op: updates}, original)

Собираем все вместе

Затем вы используете свой собственный валидатор и уровни данных при инициализации приложения:

app = Eve(validator=MyValidator, data=MyMongo)
app.run()

Опять же, я не проверял все это; Сейчас воскресенье, я на пляже, так что, возможно, придется немного поработать, но это должно работать.

С учетом всего вышесказанного я фактически собираюсь поэкспериментировать с добавлением поддержки push-обновлений в стандартный уровень данных Mongo. В частной ветке реализована новая пара глобальных / конечных настроек, например _11 _ / _ 12_. Первое значение по умолчанию - $set, поэтому все конечные точки API по-прежнему выполняют атомарные обновления полей. Можно решить, что определенная конечная точка должна выполнять что-то еще, например $push. Реализовать валидацию чистым и элегантным способом немного сложно, но, если предположить, что я найду время поработать над этим, вполне вероятно, что это может дойти до Eve 0.6 или выше.

Надеюсь это поможет.

person Nicola Iarocci    schedule 31.05.2015
comment
Спасибо за отличный ответ! Я читаю и надеюсь реализовать в ближайшие несколько дней. Один вопрос: почему вы превратили мой POST в PATCH. С точки зрения REST я добавляю новую запись (хотя в БД это новая подзапись, но пользователь REST API этого не знает), поэтому POST (добавить новый) кажется более логичным, чем ПАТЧ (изменить существующие), вы не согласны? Будет ли ваш ответ по-прежнему применяться, если вместо этого используется логика POST? Спасибо еще раз! - person Mike Lutz; 01.06.2015
comment
Ну, вы используете $push - это оператор обновления в mongo, вы не можете использовать его со вставкой, поэтому выполнение POST (вставка) с помощью push будет невозможно. - person Nicola Iarocci; 02.06.2015
comment
К сожалению, этот ответ работает не так, как написано. Словарь updates, переданный в def update, содержит значения из полей REST PATCH и, обновленные поля _etag и _updated, причем для первого требуется $push, а для второго - $set. Я пытаюсь обойти эту проблему, и я опубликую ее, если заставлю ее работать, но это будет некрасиво. - person Mike Lutz; 04.06.2015
comment
В вашем updated вы можете разделить полезную нагрузку, а затем дважды вызвать _change_request, один раз для части push, а другой - для набора. Не идеально с точки зрения производительности, но и имейте в виду, что etag в любом случае будет проблемой, поскольку предварительно вычисленный не будет репрезентативным для окончательного сохраненного документ (из-за ваших изменений). Возможно, вам придется пересчитать его. - person Nicola Iarocci; 04.06.2015
comment
@NicolaIarocci Вы когда-нибудь добавляли поддержку «push-обновлений» для Eve? - person Steven; 01.03.2018
comment
@MikeLutz Прошло 3 года, но ты когда-нибудь в этом догадывался? - person Steven; 01.03.2018
comment
@Steven Я просмотрел свои кодовые базы eve и не нашел никаких решений, реализованных, как указано выше - я давно не думал об этом коде, поэтому я не могу вспомнить, что читаю. Приносим извинения за то, что не обновили, как я сказал, - person Mike Lutz; 06.03.2018
comment
@MikeLutz Mike - спасибо, что снова заглянул. В итоге я написал свой собственный маршрут к флеш-приложению и поговорил напрямую через pymongo. К сожалению, производительность «добавления в массив» со временем ухудшалась, поэтому мы пропустили эту технику. - person Steven; 06.03.2018

Я решил это по-другому. Я создал второй домен, в котором был каждый отдельный элемент в массиве. Затем я подключился к событиям вставки Евы http://python-eve.org/features.html#insert-events. Затем в обработчике события вставки поста я взял элемент и использовал следующий код:

    return current_app.data.driver.db['friends'].update(
            { 'id': items[0]['_friend_id']  },
            {
                 '$push': { 'enemies': {'enemy':items[0]['enemy'],

                 }
                 },
                    '$currentDate': { 'lastModified': True }
            }
    )

В основном у меня теперь есть нормализованные и денормализованные представления в db.

person irhetoric    schedule 25.05.2018