Статьи

Как Python MongoDB Toolkit Ming может облегчить обслуживание схемы

Ведение схемы с помощью Ming и MongoDB

Продолжая мою серию статей о MongoDB и Python, эта статья знакомит с инструментарием Python MongoDB Ming и тем, что он может сделать, чтобы упростить код MongoDB и упростить обслуживание. Если вы только начинаете работать с MongoDB, вы можете сначала прочитать предыдущие статьи серии:

И теперь, когда вы все пойманы, давайте сразу же начнем с Мингом ….

Почему мин?

Если вы пришли в MongoDB из мира реляционных баз данных, вы, вероятно, были поражены тем, насколько просто все: нет необходимости в большом объектном / реляционном маппере, не нужно изучать новый язык запросов (ну, может быть, немного, но мы пока что замаскирую) все просто словари Python, и это так, очень быстро! Хотя в какой-то степени это все верно, одной из важных вещей, которые вы бросаете с MongoDB, является структура .

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

Дело в том, что даже если база данных не заботится о вашей схеме, ваше приложение заботится о ней , и если вы слишком быстро играете и проигрываете со структурой документа, в конце концов она снова будет преследовать вас. Основной причиной создания Ming в SourceForge было решение именно этой проблемы. Мы хотели (тонкий) слой поверх пимонго, который бы сделал для нас пару вещей:

  • Убедитесь, что мы не помещаем искаженные данные в базу данных
  • Попробуйте «исправить» искаженные данные, возвращающиеся из базы данных

Итак, не осмысливая точку его существования, давайте прыгнем в Мин.

Определение вашей схемы

При использовании Ming первое, что вам нужно сделать, это рассказать, как выглядят ваши документы. Для этого Ming предоставляет функцию сбора.

from datetime import datetime

from ming import collection, Field, Session
from ming import schema as S

session = Session()
MyDoc = collection(
    'user', session,
    Field('_id', S.ObjectId),
    Field('username', str),
    Field('created', datetime, if_missing=datetime.utcnow),
    ...)

Есть несколько вещей, чтобы отметить выше:

  • Имя коллекции MongoDB передается в качестве первого аргумента коллекции
  • Объект Session используется для абстрагирования соединения pymongo. Мы увидим, как настроить его ниже.
  • Каждое поле в нашей схеме получает свое собственное определение поля. Поля содержат имя, элемент схемы (в данном примере S.ObjectId, str и datetime) и необязательные аргументы, которые влияют на поле.
  • Специальный аргумент ключевого слова if_missing позволяет вам указать аргументы по умолчанию, которые будут «заполнены» Ming. Если вы передадите функцию, как указано выше, она будет вызвана для генерации значения по умолчанию.

Элементы схемы имеют немного больше объяснений. Внутри Ming всегда работает с объектами из модуля ming.schema, но также предоставляет ярлыки для упрощения определений схемы. Перевод между ярлыком и ming.schema.SchemaItem приведен ниже:

стенография SchemaItem Заметки
Никто Что-нибудь  
ИНТ Int  
ул строка Unicode
поплавок терка  
BOOL Bool  
Дата и время DateTime  
[] Array (Все ()) Любой допустимый массив
[INT] Array (Int ())  
{Ул: Нет} Объект ({ул: Нет}) Любой действительный объект
{«a»: int} Объект ({«a»: int}) Встроенная схема

Обратите внимание, что мы можем создавать сложные схемы, используя Ming. Сообщение в блоге может иметь следующее определение, например:

BlogPost = collection(
   'blog.post', session,
   Field('_id', S.ObjectId),
   Field('posted', datetime, if_missing=datetime.utcnow),
   Field('title', str),
   Field('author', dict(
       username=str,
       display_name=str)),
   Field('text', str),
   Field('comments', [
       dict(
           author=dict(
               username=str,
               display_name=str),
           posted=S.DateTime(if_missing=datetime.utcnow),
           text=str) ]))

Обратите внимание, что в приведенной выше схеме автор является встроенным документом, а комментарии — встроенным массивом документов.

индексирование

Если бы мы ожидали выполнить много запросов к user.username, мы могли бы добавить индекс, просто обновив приведенный выше код следующим образом:

   ...
    Field('username', str, index=True)
    ...

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

    ...
    Field('username', str, unique=True)
    ...

Ming также поддерживает указание составных индексов с помощью объекта Index в определении коллекции. Предположим, мы хотим сохранить отдельный список пользователей, ограниченный client_id. В этом случае схема может выглядеть примерно так:

from datetime import datetime

from ming import collection, Field, Index, Session
from ming import schema as S

session = Session()
MyDoc = collection(
    'user', session,
    Field('_id', S.ObjectId),
    Field('client_id', S.ObjectId, if_missing=None),
    Field('username', str),
    Field('created', datetime, if_missing=datetime.utcnow),
    Index('client_id', 'username', unique=True),
    ...)

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

db.user.ensure_index([('client_id', 1), ('username', 1)], unique=True)

По умолчанию каждый ключ в индексе, созданном Ming, сортируется в порядке возрастания. Если вы хотите изменить это, вы можете явно указать порядок сортировки для индекса:

    ...
    Index(('client_id', -1), ('username', 1), unique=True)
    ...

Подключение и настройка

После того, как мы определили нашу схему, мы можем использовать его привязку сеанса к соответствующей базе данных MongoDB с помощью ming.datastore:

from ming import datastore

session.bind = datastore.DataStore(
    'mongodb://localhost:27017', database='test')

Более типично, мы создадим наш сеанс как именованный сеанс и свяжем его где-нибудь еще в нашем приложении (возможно, в нашем скрипте запуска):

session = ming.Session.by_name('test)

...

ming.config.configure_from_nested_dict(dict(
    test=dict(
        master='mongodb://localhost:27017', 
        database='test')
    ))

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

Запросы и обновления

Чтобы показать, как Ming поддерживает запросы и обновления, давайте вернемся к нашей простой схеме User выше:

from datetime import datetime

from ming import collection, Field, Index, Session
from ming import schema as S

session = Session()
MyDoc = collection(
    'user', session,
    Field('_id', S.ObjectId),
    Field('client_id', S.ObjectId, if_missing=None),
    Field('username', str),
    Field('created', datetime, if_missing=datetime.utcnow),
    Index('client_id', 'username', unique=True),
    ...)

Теперь давайте вставим некоторые данные:

>>> import pymongo
>>> conn = pymongo.Connection()
>>> db = conn.test
>>> db.user.insert([
...     dict(username='rick'),
...     dict(username='jenny'),
...     dict(username='mark')])
[ObjectId('4fd24c96fb72f08265000000'), 
 ObjectId('4fd24c96fb72f08265000001'), 
 ObjectId('4fd24c96fb72f08265000002')]

Чтобы вернуть данные, мы просто используем свойство менеджера коллекции m:

>>> MyDoc.m.find().all()
[{'username': u'rick', 
  '_id': ObjectId('4fd24c96fb72f08265000000'), 
  'client_id': None, 
  'created': datetime.datetime(2012, 6, 8, 19, 8, 28, 522073)}, 
 {'username': u'jenny', 
  '_id': ObjectId('4fd24c96fb72f08265000001'), 
  'client_id': None, 
  'created': datetime.datetime(2012, 6, 8, 19, 8, 28, 522195)}, 
 {'username': u'mark', 
  '_id': ObjectId('4fd24c96fb72f08265000002'), 
  'client_id': None, 
  'created': datetime.datetime(2012, 6, 8, 19, 8, 28, 522315)}]

Обратите внимание, как Ming заполнил значения, которые мы пропустили при создании пользовательских документов. В этом случае он фактически заполняет их, когда они возвращаются из базы данных. Мы можем перейти к слою pymongo, чтобы увидеть это, используя свойство m.collection в MyDoc:

>>> list(MyDoc.m.collection.find())
[{u'username': u'rick', 
  u'_id': ObjectId('4fd24c96fb72f08265000000')}, 
 {u'username': u'jenny', 
  u'_id': ObjectId('4fd24c96fb72f08265000001')}, 
 {u'username': u'mark', 
  u'_id': ObjectId('4fd24c96fb72f08265000002')}]

Теперь давайте удалим документы, которые мы создали, и создадим некоторые, используя Ming:

>>> MyDoc.m.remove()
>>> 
>>> MyDoc(dict(username='rick')).m.insert()
>>> MyDoc(dict(username='jenny')).m.insert()
>>> MyDoc(dict(username='mark')).m.insert()
>>> 
>>> MyDoc.m.collection.find_one()
{u'username': u'rick', 
 u'_id': ObjectId('4fd24f95fb72f08265000003'), 
 u'client_id': None, 
 u'created': datetime.datetime(2012, 6, 8, 19, 16, 37, 565000)}

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

Еще одна вещь, которую стоит отметить выше, это то, что при вставке новых документов нам не нужно было указывать таблицу. Документы Ming на самом деле являются подклассами dict, но они «помнят», откуда они пришли. Чтобы обновить документ, все, что нам нужно сделать, это вызвать .m.save () для документа:

>>> doc = MyDoc.m.get(username='rick')
>>> import bson
>>> doc.client_id=bson.ObjectId()
>>> doc.username
u'rick'
>>> doc.client_id
ObjectId('4fd250bdfb72f08265000006')
>>> doc.m.save()

Если вы предпочитаете использовать атомарные обновления MongoDB, вы можете использовать метод менеджера update_partial:

>>> MyDoc.m.update_partial(
...     dict(username='rick'), 
...     {'$set': { 'client_id': None}})
{u'updatedExisting': True, u'connectionId': 232, 
 u'ok': 1.0, u'err': None, u'n': 1}

Еще не все

В Ming есть еще много чего, о чем я расскажу в следующих статьях, включая полиморфизм данных, нетерпеливую и ленивую миграцию данных, поддержку [gridfs] [gridfs] и средство отображения документов объектов, обеспечивающее возможности объектно-реляционного типа.

Так что ты думаешь? Ming — это то, что вы бы использовали для своих проектов? Вы выбрали один из других картографов MongoDB? Пожалуйста, дайте мне знать в комментариях ниже.