![Руководство по PyMongo](https://remontka.com/wp-content/uploads/tcc/image-5.png)
Эта статья является переводом официального руководства по PyMongo с некоторыми добавлениями от автора и предназначается в качестве введения по работе с Mongodb и PyMongo.
Введение
Статья предполагает что у вас установлена mongodb, работающая на стандартном хосте и порте. Запускаем сервис mongo:
$ 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 }