Введедине в MongoDB и Python

Spread the love

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

Что если у нас есть сложное приложение, которое требует базу данных такой же гибкости как и сам язык?

Это то самое время когда NoSQL и особенно MongoDB вступает в игру.

SQL vs NoSQL

В случае, если вы не знакомы с нереляционными БД, MongoDB — это NoSQL база данных, которая стала популярной в индустрии за последние годы. NoSQL базы данных предоставляют возможности поиска и хранения данных гораздо большими способами, чем реляционные.

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

Когда дело доходит до выбора, большинству людей приходится задавать себе вопрос: «SQL или NoSQL?». Как SQL, так и NoSQL имеют свои сильные и слабые стороны, и вам следует выбрать тот, который наилучшим образом соответствует требованиям вашего приложения. Вот несколько различий между ними:

SQL

  • Модель носит реляционный характер
  • Данные хранятся в таблицах
  • Подходит для решений, где каждая запись имеет одинаковый вид и обладает одинаковыми свойствами
  • Добавление нового свойства означает, что вы должны изменить всю схему
  • Структура базы очень строгая
  • Поддерживаются ACID транзакции
  • Хорошо масштабируется вертикально

NoSQL

  • Нереляционная модель
  • Может храниться как JSON, ключ-значение и т. д. (В зависимости от типа базы данных NoSQL)
  • Не каждая запись должна быть одинаковой, что делает ее очень гибкой
  • Добавление новых свойств ничего не нарушает
  • Нет жестких требований к структуре
  • Поддержка транзакций ACID может варьироваться в зависимости от того, какая база данных NoSQL используется.
  • Типы могут варьироваться
  • Хорошо масштабируется по горизонтали

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

В зависимости от вашего конкретного сценария, использование базы данных SQL может быть предпочтительным, в то время как в других случаях NoSQL является более очевидным выбором. При выборе базы данных вы должны внимательно рассмотреть сильные и слабые стороны каждой базы данных.

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

Key-Value Store: DynamoDB
Document Store: CouchDB, MongoDB, RethinkDB
Column Store: Cassandra
Data-Structures: Redis

В последние годы базы данных SQL и NoSQL даже начали объединяться. Например, PostgreSQL теперь поддерживает хранение и запросы данных JSON, как и Mongo. Таким образом, теперь вы можете добиться того же с Postgres, что и с Mongo, но вы по-прежнему не имеете многих преимуществ Mongo (таких как горизонтальное масштабирование и простой интерфейс и т. д.).

Если ваши данные удобно размещать в реляционной схеме, а содержимое JSON является группой, то вам будет проще с PostgreSQL и его гораздо более эффективными возможностями представления и индексирования JSONB. Тем не менее, если ваша модель данных представляет собой набор изменяемых документов, то вам, вероятно, стоит взглянуть на базу данных, созданную в основном для JSON-документов, таких как MongoDB или RethinkDB.

MongoDB

Давайте теперь переключим наше внимание на основную тему этой статьи и проясним некоторые особенности MongoDB.

MongoDB — это ориентированная на документы программа базы данных с открытым исходным кодом, не зависящая от платформы. MongoDB, как и некоторые другие базы данных NoSQL (но не все!), Хранит свои данные в документах, используя структуру JSON. Это то, что позволяет данным быть гибкими и не требовать строгой схемы.

Некоторые из наиболее важных функций:

  • У вас есть поддержка многих стандартных типов запросов, таких как сопоставление (==), сравнение (<,>) или даже регулярное выражение
  • Вы можете хранить практически любые данные — будь то структурированные, частично структурированные или даже полиморфные
  • Чтобы масштабировать и обрабатывать больше запросов, просто добавьте больше машин
  • Это очень гибкий и гибкий инструмент, позволяющий быстро разрабатывать приложения
  • База данных на основе документов означает, что вы можете хранить всю информацию о вашей модели в одном документе
  • Вы можете изменить схему вашей базы данных на лету
  • Многие функции реляционной базы данных также доступны в MongoDB (например, индексирование)

Что касается операционной стороны, существует множество инструментов и функций для MongoDB, которые вы просто не можете найти в любой другой системе баз данных:

  • Независимо от того, нужен ли вам отдельный сервер или целые кластеры независимых серверов, MongoDB настолько масштабируема, насколько вам нужно.
  • MongoDB также обеспечивает поддержку балансировки нагрузки, автоматически перемещая данные между различными шардами.
    Она поддерживает автоматическое переключение при сбое — если ваш основной сервер выйдет из строя, новый основной будет работать автоматически
  • MongoDB Management Service или MMS — это очень хороший веб-инструмент, который дает вам возможность отслеживать ваши машины
  • Благодаря отображенным в память файлам вы сэкономите немало оперативной памяти, в отличие от реляционных баз данных.

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

Хотя на первый взгляд может показаться, что Mongo решает многие проблемы с базами данных, он не лишен недостатков. Одним из распространенных недостатков, которые вы услышите о Mongo, является отсутствие поддержки транзакций ACID. Mongo поддерживает ACID-транзакции в ограниченном смысле, но не во всех случаях. На уровне одного документа поддерживаются ACID-транзакции (где в любом случае происходит большинство транзакций). Однако транзакции, связанные с несколькими документами, не поддерживаются из-за распределенной архитектуры Mongo.

В Mongo также отсутствует поддержка JOIN, которая должна выполняться вручную (и следовательно, гораздо медленнее). Документы должны быть всеобъемлющими, то есть, в общем, им не нужно ссылаться на другие документы. В реальном мире это не всегда работает, так как большая часть данных, с которыми мы работаем, носит реляционный характер. Поэтому многие будут утверждать, что Mongo следует использовать в качестве дополнительной базы данных для базы SQL, но, когда вы будете  использовать MongoDB, вы обнаружите, что это не обязательно верно.

PyMongo

Теперь, когда мы описали, что такое MongoDB, давайте выясним, как вы на самом деле использовать его с Python. Официальный драйвер, опубликованный разработчиками Mongo, называется PyMongo. Это хорошая инструкция для начала при ознакомлении Python и MongoDB. Здесь мы рассмотрим некоторые примеры, но вам также следует ознакомиться с полной документацией, поскольку мы не сможем охватить все.

Первое, что нам нужно сделать, это установить PyMongo в виртуальной среде. Самый простой способ сделать это с помощью pip:

pip install pymongo==3.4.0

Закончив установку, запустите консоль Python и выполните следующую команду

import pymongo

Если скрипт запускается без каких-либо проблем в оболочке Python, тогда ваша установка сработала нормально. Если нет, то тщательно выполните шаги снова. Выйдите из оболочки, когда закончите.

Затем вы должны установить актуальную базу данных MongoDB.

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

brew install mongodb

После этого следуйте указаниям здесь, чтобы настроить каталог данных для локального хранения данных.

Если вы не используете Mac, вы можете найти отличные руководства по установке на странице Install MongoDB в официальных документах. Там вы найдете руководства по установке MongoDB для Linux, OS X и Windows.

После установки в новом окне терминала используйте следующую команду для запуска демона Mongo:

mongod

Теперь давайте начнем с основ PyMongo.

Установка соединения

Чтобы установить соединение, мы будем использовать объект MongoClient.

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

from pymongo import MongoClient
client = MongoClient()

Используя приведенный выше фрагмент, будет установлено соединение с хостом по умолчанию (localhost) и портом (27017). Вы также можете указать хост и/или порт, используя:

client = MongoClient('localhost', 27017)

Или использовать Mongo URI формат

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

 

Доступ к базам данных

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

db = client.pymongo_test

или dictionary-style

db = client['pymongo_test']

На самом деле не имеет значения, была ли создана указанная вами база данных. Указав это имя базы данных и сохранив в нем данные, вы автоматически создадите базу данных.

Вставка документов

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

Во второй строке вы фактически вставляете данные в коллекцию с помощью метода insert_one():

posts = db.posts
post_data = {
    'title': 'Python and MongoDB',
    'content': 'PyMongo is fun, you guys',
    'author': 'Scott'
}
result = posts.insert_one(post_data)
print('One post: {0}'.format(result.inserted_id))

Мы даже можем вставлять много документов за раз, что намного быстрее, чем использование insert_one(), если у вас есть много документов для добавления в базу данных. Здесь используется метод insert_many(). Этот метод принимает массив данных документа:

post_1 = {
    'title': 'Python and MongoDB',
    'content': 'PyMongo is fun, you guys',
    'author': 'Scott'
}
post_2 = {
    'title': 'Virtual Environments',
    'content': 'Use virtual environments, you guys',
    'author': 'Scott'
}
post_3 = {
    'title': 'Learning Python',
    'content': 'Learn Python, it is easy',
    'author': 'Bill'
}
new_result = posts.insert_many([post_1, post_2, post_3])
print('Multiple posts: {0}'.format(new_result.inserted_ids))

Когда вы выполните эту чать кода, то получите что-то вроде этого:

Multiple posts: [
    ObjectId('584d947dea542a13e9ec7ae7'),
    ObjectId('584d947dea542a13e9ec7ae8'),
    ObjectId('584d947dea542a13e9ec7ae9')
]

Не беспокойтесь о том, что ваши ObjectIds не соответствуют показанным выше. Они генерируются динамически при вставке данных и состоят из Unix timestamp, идентификатора машины и других уникальных данных.

Получение документов

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

bills_post = posts.find_one({'author': 'Bill'})
print(bills_post)

Результат выполнения:

{
    'author': 'Bill',
    'title': 'Learning Python',
    'content': 'Learn Python, it is easy',
    '_id': ObjectId('584c4afdea542a766d254241')
}

Возможно, вы заметили, что ObjectId поста установлен под ключом _id, который мы можем позже использовать для уникальной идентификации, если это необходимо. Если мы хотим найти более одного документа, мы можем использовать метод find(). На этот раз давайте найдем все посты, написанные Скоттом:

scotts_posts = posts.find({'author': 'Scott'})
print(scotts_posts)

Запуск

<pymongo.cursor.Cursor object at 0x109852f98>

Основным отличием здесь является то, что данные документа не возвращаются напрямую в виде массива. Вместо этого мы получаем экземпляр объекта Cursor. Этот Cursor является итеративным объектом, который содержит довольно много вспомогательных методов, которые помогут вам работать с данными. Чтобы получить каждый документ, просто переберите результат:

for post in scotts_posts:
    print(post)

MongoEngine

Хотя PyMongo очень проста в использовании и в целом отличная библиотека, она, вероятно, слишком низкоуровневая для многих проектов. Иными словами, вам придется написать много собственного кода, чтобы последовательно сохранять, извлекать и удалять объекты.

MongoEngine — это еще одна библиотека, которая обеспечивает более высокую абстракцию поверх PyMongo. MongoEngine — это object document mapper (ODM), который примерно эквивалентен объектному реляционному картографу (ORM) на основе SQL. Абстракция, предоставляемая MongoEngine, основана на классах, поэтому все создаваемые вами модели являются классами.

Хотя существует довольно много библиотек Python, которые помогут вам работать с MongoDB, MongoEngine — одна из лучших, так как она имеет хорошее сочетание функций, гибкости и поддержки сообщества.

Для установки используйте pip:

pip install mongoengine==0.10.7

После установки нам нужно указать библиотеке как подключиться к рабочему экземпляру Mongo. Для этого нужно будет использовать функцию connect() и передать ей хост и порт базы данных MongoDB. В оболочке Python введите:

from mongoengine import *
connect('mongoengine_test', host='localhost', port=27017)

Здесь мы указываем название базы данных и местоположение. Поскольку мы все еще используем хост и порт по умолчанию, вы можете опустить эти параметры.

Определение документа

Чтобы настроить объект документа, нам нужно определить, какие данные мы хотим получать. Подобно многим другим ORM, сделаем это путем создания подкласса класса Document, предоставив типы данных, которые мы хотим получить:

import datetime

class Post(Document):
    title = StringField(required=True, max_length=200)
    content = StringField(required=True)
    author = StringField(required=True, max_length=50)
    published = DateTimeField(default=datetime.datetime.now)

Одной из наиболее сложных задач с моделями баз данных является проверка данных. Как убедиться, что сохраняемые данные соответствуют нужному формату? То, что база данных называется «бессхемовой», не означает, что она не содержит схем.

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

Так, например, если попытаться сохранить сообщение без заголовка, то получим  исключение. Мы можем пойти еще дальше и добавить больше ограничений, таких как длина строки. Обратите внимание, что для некоторых полей установлен параметр max_length. Это говорит Document, как вы наверное догадались, разрешить максимальную длину строки только из того количества символов, которое мы указываем. Мы можем установить еще несколько таких параметров, в том числе:

  • db_field: другое имя поля
  • required: убедитесь, что это поле установлено
  • default: использовать заданное значение по умолчанию, если не указано другое значение
  • unique: убедитесь, что ни один другой документ в коллекции не имеет такого же значения для этого поля
  • choices: убедитесь, что значение поля равно одному из значений, указанных в массиве

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

Сохранение документов

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

Вот пример создания и сохранения документа:

post_1 = Post(
    title='Sample Post',
    content='Some engaging content',
    author='Scott'
)
post_1.save()       # This will perform an insert
print(post_1.title)
post_1.title = 'A Better Post Title'
post_1.save()       # This will perform an atomic edit on "title"
print(post_1.title)

Несколько замечаний по поводу вызова .save():

PyMongo выполнит проверку при вызове .save(). Это означает, что он будет проверять данные, которые вы сохраняете, по схеме, объявленной в классе. Если схема (или ограничение) нарушается, возникает исключение, и данные не сохраняются.
Поскольку Mongo не поддерживает настоящие транзакции, то нет способа «откатить» вызов .save(), как это можно сделать в базах данных SQL. Хотя вы можете приблизиться к выполнению транзакций с «двухфазными коммитами«, они по-прежнему не поддерживают откат.
Что происходит, когда вы не заполняете название?

post_2 = Post(content='Content goes here', author='Michael')
post_2.save()

Вы увидите следующее исключение

raise ValidationError(message, errors=errors)
mongoengine.errors.ValidationError:
ValidationError (Post:None) (Field is required: ['title'])

Объектно-ориентированные функции

Поскольку MongoEngine является объектно-ориентированным, вы также можете добавлять методы к вашему подклассу документа. Рассмотрим следующий пример, где функция используется для изменения набора запросов по умолчанию (который возвращает все объекты коллекции). Благодаря этому мы можем применить фильтр по умолчанию к классу и получить только нужные объекты:

class Post(Document):
    title = StringField()
    published = BooleanField()

    @queryset_manager
    def live_posts(clazz, queryset):
        return queryset.filter(published=True)

Привязка других документов

Вы также можете использовать объект ReferenceField для создания ссылки из одного документа на другой. MongoEngine автоматически обрабатывает «lazy de-referencing», что более надежно и менее подвержено ошибкам, чем необходимость делать это самостоятельно. Пример:

class Author(Document):
    name = StringField()

class Post(Document):
    author = ReferenceField(Author)

Post.objects.first().author.name

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

Существует довольно много классов полей (и параметров), поэтому обязательно ознакомьтесь с документацией по полям для получения дополнительной информации.

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

Заключение

Поскольку Python является высокоуровневым, высоко-масштабируемым, современным языком, ему нужна база данных (и драйвер), который соответствовует его потенциалу, эта та причина по которой MongoDB так хорошо подходит для работы с Python.
В этой статье мы увидели, как можно использовать сильные стороны MongoDB в наших интересах и создать очень гибкое и масштабируемое приложение. Не стесняйтесь, оставлять комментарии!

Introduction to MongoDB and Python, Scott Robinson, «Real Python»

Добавить комментарий