Python-Eve: запретить вставку дубликатов без использования уникальных полей

Я пытаюсь предотвратить вставку дубликатов документов следующим образом:

  1. Получите список всех документов из нужной конечной точки, который будет содержать все документы в формате JSON. Этот список называется available_docs.
  2. Используйте хук pre_POST_<endpoint> для обработки запроса перед вставкой в ​​данные. Я не использую хук on_insert, так как мне нужно сделать это перед проверкой.
  3. Поскольку мы можем получить доступ к объекту request, используйте request.json для получения полезной нагрузки в формате JSON.
  4. Проверьте, содержится ли уже request.json в available_docs
  5. Вставьте новый документ, если это не только дубликат, в противном случае прервите.

Используя этот подход, я получил следующий фрагмент:

def check_duplicate(request):
    if not request.json in available_sims:
        print('Not a duplicate')
    else:
        print('Duplicate')
        flask.abort(422, description='Document is a duplicate and already in database.')

Список available_docs выглядит так:

available_docs = [{'foo': ObjectId('565e12c58b724d7884cd02bb'), 'bar': [ObjectId('565e12c58b724d7884cd02b9'), ObjectId('565e12c58b724d7884cd02ba')]}]

Полезная нагрузка request.json выглядит так:

{'foo': '565e12c58b724d7884cd02bb', 'bar': ['565e12c58b724d7884cd02b9', '565e12c58b724d7884cd02ba']}

Как видите, единственная разница между документом, который был передан в API, и документом, уже хранящимся в БД, заключается в типе данных идентификаторов. В связи с этим оператор if в приведенном выше фрагменте оценивается как True и считает, что вставляемый документ не является дубликатом, тогда как он определенно является дубликатом.

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

Я думаю, что что-то вроде приведения данных идентификаторов к ключам foo и bar как ObjectIDs поможет, но я не знаю, как это сделать, так как я не знаю, откуда взять тип данных ObjectID.


person albert    schedule 13.12.2015    source источник
comment
Даже если бы это был один и тот же тип данных, код все равно не работал бы, потому что это разные объекты. Если вы хотите считать их похожими, вам придется создать функцию сравнения и использовать ее вместо выполнения if not request.json in available_sims   -  person Nir Alfasi    schedule 13.12.2015
comment
Я понял вашу точку зрения о разных объектах (например, о ссылках). Однако что-то вроде a = {'albert': 2015} l = [] l.append(a) b = {'albert': 2015} print(b in l) оценивается как True. Так что этот подход пришел мне в голову.   -  person albert    schedule 13.12.2015
comment
Но вы имеете дело с объектами, а не числами, см. следующий пример, который лучше отражает вашу ситуацию: gist.github .com/alfasin/32a50c4817ebf8b390fd   -  person Nir Alfasi    schedule 13.12.2015
comment
Это правда. Поскольку идентификаторы объектов уникальны, я подумал, что сравнение их друг с другом (в виде строк) будет полезным. Я все еще думаю, что эта идея поможет, но мой подход как-то неверен.   -  person albert    schedule 13.12.2015


Ответы (2)


Ваш подход будет намного медленнее, чем установка уникального правила для поля.

Поскольку в вашем примере вы собираетесь сравнивать идентификаторы объектов, не можете ли вы просто использовать их в качестве поля _id для коллекции? В Mongo (и, конечно, в Eve) это поле по умолчанию уникально. На самом деле, вы обычно даже не определяете его. Вам вообще ничего не нужно делать, так как POST документа с уже существующим идентификатором сразу же завершится ошибкой.

Если вы не можете пойти по этому пути (возможно, вам нужно сравнить другое поле objectid, и все же по какой-то причине вы не можете просто установить правило unique для поля), я бы посмотрел на запрос базы данных для значения поля вместо того, чтобы получать все документы из базы данных, а затем последовательно сканировать их в коде. Что-то вроде db.find({db_field: new_document_field_value}). Если это возвращает true, новый документ является дубликатом. Убедитесь, что db_field проиндексировано (что обычно верно и для полей, помеченных правилом unique)

ИЗМЕНИТЬ после комментариев. Тривиальная реализация, вероятно, будет примерно такой:

def pre_POST_callback(resource, request):
    # retrieve mongodb collection using eve connection
    docs = app.data.driver.db['docs']

    if docs.find_one({'foo': <value>}):
        flask.abort(422, description='Document is a duplicate and already in database.')


app = Eve()
app.run()
person Nicola Iarocci    schedule 13.12.2015
comment
Так что я бы сделал запрос db.find({db_field: new_document_field_value}), используя подход Евы ?where={db_field: new_document_field_value}, не так ли? - person albert; 13.12.2015
comment
Или есть способ сделать это внутри, так как я намерен проверять дубликаты с помощью хука pre_POST? Могу ли я как-то использовать базовый экземпляр pymongo Евы? Как я могу убедиться, что индексирование активно? - person albert; 13.12.2015

Вот мой подход к предотвращению дублирования записей:

def on_insert_subscription(items): c_subscription = app.data.driver.db['subscription'] user = decode_token() if user: for item in items: if c_subscription.find_one({ 'topic': ObjectId(item['topic']), 'client': ObjectId(user['user_id']) }): abort(422, description="Client already subscribed to this topic") else: item['client'] = ObjectId(user['user_id']) else: abort(401, description='Please provide proper credentials')

Что я здесь делаю, так это создаю подписки для клиентов. Если клиент уже подписан на тему, я выбрасываю 422. Примечание: идентификатор клиента расшифровывается из токена JWT.

person igniter    schedule 25.08.2017