Руководство по PyMongo

Эта статья является переводом официального руководства по PyMongo с некоторыми добавлениями от автора и предназначается в качестве введения по работе с Mongodb и PyMongo.


Введение

Статья предполагает что у вас установлена mongodb, работающая на стандартном хосте и порте. Запускаем сервис монги:

$ mongod

Далее стоит убедиться что у нас установлена PyMongo.


Подключаемся к MongoClient

Первым шагом при работе с PyMongo является создание MongoClient для запуска экземпляра mongod:

from pymongo import MongoClient
client = MongoClient()

Таким образом мы подключаемся к стандартному хосту и порту. Чтобы явно указать хост и порт пишем так:

client = MongoClient('localhost', 27017)

Или используя MongoDB URI формат:

client = MongoClient('mongodb://localhost:27017/')

Получаем базу данных

Один экземпляр MongoDB может поддерживать несколько независимых баз данных. Получение доступа к базе данных:

db = client.test_database

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

db = client['test-database']

Получаем коллекцию

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

collection = db.test_collection

Или вот так:

>>> collection = db['test-collection']

Важное примечание о коллекциях базах данных) в MongoDB. На самом деле при выполнении этих операций базы данных и коллекции физически не создаются. А создаются они при добавлении в эту базу данных и эту коллекцию первого документа.


Документы

Данные в MongoDB представлены и хранятся в виде JSON документов . В PyMongo мы используем словари представления документов. В качестве примера, следующий словарь может быть использован для представления поста в блоге:

import datetime
post = {"author": "Mike",
        "text": "My first blog post!",
        "tags": ["mongodb", "python", "pymongo"],
        "date": datetime.datetime.utcnow()}

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


Добавление документа

Чтобы добавить документ в коллекции, вы можете использовать метод insert_one ():

posts = db.posts
post_id = posts.insert_one(post).inserted_id

Когда документ будет добавлен, автоматически создается специальный ключ - "_id", если документ уже не содержит таковой. Значение "_id" должно быть уникальным для коллекции. insert_one () возвращает экземпляр InsertOneResult. Для получения более подробной информации о "_id", смотрите в документации по _id.

После добавления первого документа, коллекция post физически создалась на сервере.

>>> db.collection_names(include_system_collections=False)
[u'posts']

Получение одного документа через

Самый простой тип запроса, который может быть выполнен в MongoDB является find_one (). Этот метод возвращает один документ, соответствующий запросу (или ни одного, если не было совпадений). Это полезно, когда вы знаете, что есть только один подходящий документ, или заинтересованы только в первом совпадении. Здесь мы используем find_one (), чтобы получить первый документ из коллекции постов:

>>> posts.find_one()
{u'date': datetime.datetime(...), u'text': u'My first blog post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'mongodb', u'python', u'pymongo']}

В результате мы получили ранее добавленый документ.

Заметка. Возвращенный документ содержит "_id", который был автоматически создан при добавлении.

find_one () также поддерживает запрос на конкретные элемены, которым документ должен соответствовать. Чтобы получить только документы за авторством "Mike" мы делаем так:

>>> posts.find_one({"author": "Mike"})
{u'date': datetime.datetime(...), u'text': u'My first blog post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'mongodb', u'python', u'pymongo']}

Если мы сделаем тоже самое с другим автором мы не получите результата:

>>> posts.find_one({"author": "Eliot"})
>>>


Запросы по ObjectId

Мы также можем найти запись по ее "_id", что в нашем примере - ObjectId:

>>> post_id
ObjectId(...)
>>> posts.find_one({"_id": post_id})
{u'date': datetime.datetime(...), u'text': u'My first blog post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'mongodb', u'python', u'pymongo']}

Обратите внимание, что ObjectId не то же самое, что и его строчное представление:

post_id_as_str = str(post_id)
>>> posts.find_one({"_id": post_id_as_str}) # No result
>>>

В веб-приложениях частой задачей является получение ObjectId из запрашиваемого URL и нахождение соответствующий документ. При этом необходимо преобразовние из строки в ObjectId перед передачей в find_one:

from bson.objectid import ObjectId

# Веб фреймворк получает post_id из URL и передает его в виде строки
def get(post_id):
    # Преобразовываем из строки в ObjectId:
    document = client.db.collection.find_one({'_id': ObjectId(post_id)})



Обратите внимание на стоки в Unicode

Вы, наверное, заметили, что строки Python, которые мы поместили ранее отличаться, от полученых с сервера (например, u'Mike 'вместо' Майка ').

MongoDB хранит данные в формате BSON. BSON строки в закодированы UTF-8. Поэтому PyMongo должна удоставериться, что любые строки, которые она хранит, содержат данные допустимые в кодировке UTF-8. Регулярные строки ( 'str'>) проверяются и сохраняются неизменными. Unicode строки () сначало кодируются в  UTF-8. Причина почему в нашем примере строка представлена в оболочке Python в качестве u'Mike 'вместо 'Mike', в том что PyMongo декодирует каждую BSON строку в юникод.

Вы можете прочитать больше о Юникрде в Python здесь.


Массовые добавления

Для того, чтобы сделать запросы к БД немного более интереснее, давайте добавим несколько документов. В дополнение к добавлению одного документа, мы также можем выполнять операции массового добавления, проходя по списку в качестве первого аргумента insert_many (). Так мы добавляем каждый документ в списке, посылая только одну команду серверу:

new_posts = [{"author": "Mike",
              "text": "Another post!",
              "tags": ["bulk", "insert"],
              "date": datetime.datetime(2009, 11, 12, 11, 14)},
             {"author": "Eliot",
              "title": "MongoDB is fun",
              "text": "and pretty easy too!",
              "date": datetime.datetime(2009, 11, 10, 10, 45)}]
result = posts.insert_many(new_posts)

>>> result.inserted_ids [ObjectId('...'), ObjectId('...')]

Есть несколько интересных вещей, чтобы отметить о данном примере:

  • Результат от insert_many () теперь возвращает два экземпляра ObjectId, по одному для каждого добавленного документа.
  • new_posts [1] имеет другую «форму», чем другие посты - нет поля "tags", и мы добавили новое поле "title". Это то, что мы имеем в виду, когда мы говорим, что MongoDB schema-free.

Запросы к более чем одному документу

Чтобы получить несколько документов в результате запроса, мы используем метод find(). Find() возвращает экземпляр Cursor, который позволяет нам перебрать все соответствующие документы. Например, мы можем перебрать все документы в коллекции post:

for post in posts.find():
    post
...
{u'date': datetime.datetime(...), u'text': u'My first blog post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'mongodb', u'python', u'pymongo']}
{u'date': datetime.datetime(2009, 11, 12, 11, 14), u'text': u'Another post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'bulk', u'insert']}
{u'date': datetime.datetime(2009, 11, 10, 10, 45), u'text': u'and pretty easy too!', u'_id': ObjectId('...'), u'author': u'Eliot', u'title': u'MongoDB is fun'}

Так же, как мы это делали с find_one (), мы можем ограничить возвращаемые find() результаты. Здесь мы получаем только документы, автор которых "Mike":

for post in posts.find({"author": "Mike"}):
    post
...
{u'date': datetime.datetime(...), u'text': u'My first blog post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'mongodb', u'python', u'pymongo']}
{u'date': datetime.datetime(2009, 11, 12, 11, 14), u'text': u'Another post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'bulk', u'insert']}

Пересчет

Если мы просто хотим знать, сколько документов совпадают с запросом мы можем выполнить операцию count() вместо полного запроса. Мы можем получить количество всех документов в коллекции:

>>> posts.count()
3

Или просто из тех документов, которые соответствуют конкретному запросу:

>>> posts.find({"author": "Mike"}).count()
2



Диапазон запросов

MongoDB поддерживает множество различных типов сложных запросов. В качестве примера, давайте выполнить запрос, где мы ограничиваем результаты по дате постов, а также сортируем их по автору:

d = datetime.datetime(2009, 11, 12, 12)
for post in posts.find({"date": {"$lt": d}}).sort("author"):
    print post
...
{u'date': datetime.datetime(2009, 11, 10, 10, 45), u'text': u'and pretty easy too!', u'_id': ObjectId('...'), u'author': u'Eliot', u'title': u'MongoDB is fun'}
{u'date': datetime.datetime(2009, 11, 12, 11, 14), u'text': u'Another post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'bulk', u'insert']}

Здесь мы используем специальный оператор "$lt"(less than) чтобы ограничить запросы, а также вызываем sort() для сортировки результатов по автору.


Индексация

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

Во-первых, мы должны создать индекс:

result = db.profiles.create_index([('user_id', pymongo.ASCENDING)],
                                     unique=True)
>>> list(db.profiles.index_information())
[u'user_id_1', u'_id_']

Обратите внимание, что у нас теперь есть два индекса: один - указывает на _id, который автоматически создает MongoDB, а другой указывает на user_id, который мы только что создали.

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

user_profiles = [
    {'user_id': 211, 'name': 'Luke'},
    {'user_id': 212, 'name': 'Ziltoid'}]
result = db.profiles.insert_many(user_profiles)

Индекс мешает добавить документ user_id которого уже в коллекции:

new_profile = {'user_id': 213, 'name': 'Drew'}
duplicate_profile = {'user_id': 212, 'name': 'Tommy'}
result = db.profiles.insert_one(new_profile)    # Тут все хорошо.
result = db.profiles.insert_one(duplicate_profile)

Traceback (most recent call last): pymongo.errors.DuplicateKeyError: E11000 duplicate key error index: test_database.profiles.$user_id_1 dup key: { : 212 }