Автор оригинала: Geoffery, Joseph.
Руководство по Flask-MongoEngine в Python
Вступление
Создание веб-приложения почти всегда означает работу с данными из базы данных. Существуют различные базы данных на выбор, в зависимости от ваших предпочтений.
В этом руководстве мы рассмотрим, как интегрировать одну из самых популярных баз данных NoSQL – MongoDB – с микро-фреймворком Flask.
В этом руководстве мы рассмотрим, как интегрировать MongoDB с Flask с помощью популярной библиотеки – MongoEngine , а точнее, ее оболочки – Flask-MongoEngine .
Кроме того, вы можете интегрировать MongoDB с Flask-PyMongo .
Колба-Монгоинжин
MongoEngine-это ODM (Object Document Mapper), который сопоставляет классы Python (модели) с документами MongoDB, что позволяет легко создавать и манипулировать документами программно прямо из нашего кода.
Настройка и настройка
Чтобы изучить некоторые особенности MongoEngine, мы создадим простой API movie, который позволит нам выполнять CRUD – операции над экземплярами Movie
.
Для начала давайте установим Flask, если у вас его еще нет:
$ pip install flask
Далее нам понадобится доступ к экземпляру MongoDB, MongoDB предоставляет облачный экземпляр – MongoDB Atlas – который мы можем использовать бесплатно, однако мы будем использовать локально установленный экземпляр. Инструкции по получению и установке MongoDB можно найти в официальной документации .
И когда это будет сделано, мы также захотим установить библиотеку Flask-MongoEngine:
$ pip install flask-mongoengine
Подключение к экземпляру базы данных MongoDB
Теперь, когда мы установили Flask и Flask-MongoEngine, нам нужно подключить наше приложение Flask к экземпляру MongoDB.
Мы начнем с импорта Flask и Flask-MongoEngine в наше приложение:
from flask import Flask from flask_mongoengine import MongoEngine
Затем мы можем создать объект приложения Flask:
app = Flask(__name__)
Который мы будем использовать для инициализации объекта MongoEngine
. Но прежде чем инициализация будет выполнена, нам понадобится ссылка на наш экземпляр MongoDB.
Эта ссылка является ключом в файле app.config
, значение которого представляет собой dict, содержащий параметры подключения:
app.config['MONGODB_SETTINGS'] = { 'db':'db_name', 'host':'localhost', 'port':'27017' }
Вместо этого мы также могли бы предоставить URI соединения:
app.config['MONGODB_SETTINGS'] = { 'host':'mongodb://localhost/db_name' }
После завершения настройки мы теперь можем инициализировать объект MongoEngine
:
db = MongoEngine(app)
Мы также могли бы использовать метод init_app()
объекта MongoEngine
для инициализации:
db = MongoEngine() db.init_app(app)
После того, как конфигурация и инициализация были сделаны, мы можем начать исследовать некоторые из удивительных особенностей MongoEngine.
Создание классов Моделей
Будучи ODM, MongoEngine использует классы Python для представления документов в нашей базе данных.
MongoEngine предоставляет несколько типов классов документов:
- Документ
- Встроенный документ
- Динамический документ
- DynamicEmbeddedDocument
Документ
Это представляет собой документ, который имеет свою собственную коллекцию в базе данных, он создается путем наследования от mongoengine.Документ
или из нашего MongoEngine
экземпляра ( db.Document
):
class Movie(db.Document): title = db.StringField(required=True) year = db.IntField() rated = db.StringField() director = db.ReferenceField(Director) cast = db.EmbeddedDocumentListField(Cast) poster = db.FileField() imdb = db.EmbeddedDocumentField(Imdb)
MongoEngine также предоставляет дополнительные классы, описывающие и проверяющие тип данных, которые должны принимать поля документа, а также дополнительные модификаторы для добавления дополнительных сведений или ограничений к каждому полю.
Примерами полей являются:
String Field()
для строковых значенийInt Field()
для значений intList Field()
для спискаFloatField()
для значений с плавающей запятойСправочное поле()
для ссылок на другие документыEmbeddedDocumentField()
для встроенных документов и т. Д.File Field()
для хранения файлов (подробнее об этом позже)
Вы также можете применить модификаторы в этих полях, например:
требуемый
по умолчанию
уникальный
primary_key
и т. Д.
Установив любой из них в True
, они будут применены именно к этому полю.
Встроенный документ
Это представляет собой документ, который не имеет собственной коллекции в базе данных, но встроен в другой документ, он создается путем наследования от класса EmbeddedDocument
:
class Imdb(db.EmbeddedDocument): imdb_id = db.StringField() rating = db.DecimalField() votes = db.IntField()
Динамический документ
Это документ, поля которого добавляются динамически, используя преимущества динамической природы MongoDB.
Как и другие типы документов, MongoEngine
предоставляет класс для Динамического документа
s:
class Director(db.DynamicDocument): pass
DynamicEmbeddedDocument
Это имеет все свойства Динамического документа
и Встроенного документа
class Cast(db.DynamicEmbeddedDocument): pass
Поскольку мы закончили создавать все наши классы данных, пришло время начать изучать некоторые особенности MongoEngine
Доступ к Документам
MongoEngine позволяет очень легко запрашивать нашу базу данных, мы можем получить все фильмы в базе данных следующим образом;
from flask import jsonify @app.route('/movies') def get_movies(): movies = Movie.objects() return jsonify(movies), 200
Если мы отправим запрос GET на:
localhost:5000/movies/
Это вернет все фильмы в виде списка JSON:
[ { "_id": { "$oid": "600eb604b076cdbc347e2b99" }, "cast": [], "rated": "5", "title": "Movie 1", "year": 1998 }, { "_id": { "$oid": "600eb604b076cdbc347e2b9a" }, "cast": [], "rated": "4", "title": "Movie 2", "year": 1999 } ]
Когда вы имеете дело с большими результатами таких запросов, вы захотите усечь их и позволить конечному пользователю медленно загружать больше по мере необходимости.
Flask-MongoEngine позволяет нам очень легко разбивать результаты на страницы:
@app.route('/movies') def get_movies(): page = int(request.args.get('page',1)) limit = int(request.args.get('limit',10)) movies = Movie.objects.paginate(page=page, per_page=limit) return jsonify([movie.to_dict() for movie in movies.items]), 200
Movie.objects.paginate(page=page,)
возвращает объект Pagination
, который содержит список фильмов в своем свойстве .items
, повторяя это свойство, мы получаем наши фильмы на выбранной странице:
[ { "_id": { "$oid": "600eb604b076cdbc347e2b99" }, "cast": [], "rated": "5", "title": "Back to The Future III", "year": 1998 }, { "_id": { "$oid": "600fb95dcb1ba5529bbc69e8" }, "cast": [], "rated": "4", "title": "Spider man", "year": 2004 }, ... ]
Получение Одного Документа
Мы можем получить один Movie
результат, передав идентификатор в качестве параметра методу Movie.objects()
:
@app.route('/movies/')
def get_one_movie(id: str):
movie = Movie.objects(id=id).first()
return jsonify(movie), 200
Movie.objects(id=id)
вернет набор всех фильмов, чей id
соответствует параметру, а first()
вернет первый Movie
объект в наборе запросов, если их несколько.
Если мы отправим запрос GET на:
localhost:5000/movies/600eb604b076cdbc347e2b99
Мы получим такой результат:
{ "_id": { "$oid": "600eb604b076cdbc347e2b99" }, "cast": [], "rated": "5", "title": "Back to The Future III", "year": 1998 }
В большинстве случаев мы хотели бы вызвать ошибку 404_NOT_FOUND
, если ни один документ не соответствует предоставленному id
. Flask-MongoEngine покрыл нас своими first_or_404()
и get_or_404()
пользовательскими наборами запросов:
@app.route('/movies/')
def get_one_movie(id: str):
movie = Movie.objects.first_or_404(id=id)
return movie.to_dict(), 200
Создание/Сохранение Документов
MongoEngine позволяет очень легко создавать новые документы, используя наши модели. Все, что нам нужно сделать, это вызвать метод save()
в нашем экземпляре класса модели, как показано ниже:
@app.route('/movies/', methods=["POST"]) def add_movie(): body = request.get_json() movie = Movie(**body).save() return jsonify(movie), 201
**body
распаковывает словарь body
в объект Movie
в виде именованных параметров. Например, если body = {"title": "Movie Title", "year": 2015}
, То Movie(**body)
совпадает с Movie(title="Movie Title",)
Если мы отправим этот запрос на localhost:5000/movies/
:
$ curl -X POST -H "Content-Type: application/json" \ -d '{"title": "Spider Man 3", "year": 2009, "rated": "5"}' \ localhost:5000/movies/
Он сохранит и вернет документ:
{ "_id": { "$oid": "60290817f3918e990ba24f14" }, "cast": [], "director": { "$oid": "600fb8138724900858706a56" }, "rated": "5", "title": "Spider Man 3", "year": 2009 }
Создание документов с помощью встроенных документов
Чтобы добавить внедренный документ, нам сначала нужно создать документ для внедрения, а затем назначить его соответствующему полю в нашей модели фильма:
@app.route('/movies-embed/', methods=["POST"]) def add_movie_embed(): # Created Imdb object imdb = Imdb(imdb_id="12340mov", rating=4.2, votes=7.9) body = request.get_json() # Add object to movie and save movie = Movie(imdb=imdb, **body).save() return jsonify(movie), 201
Если мы отправим этот запрос:
$ curl -X POST -H "Content-Type: application/json"\ -d '{"title": "Batman", "year": 2016, "rated": "yes"}'\ localhost:5000/movies-embed/
Это вернет только что добавленный документ вместе со встроенным документом:
{ "_id": { "$oid": "601096176cc65fa421dd905d" }, "cast": [], "imdb": { "imdb_id": "12340mov", "rating": 4.2, "votes": 7 }, "rated": "yes", "title": "Batman", "year": 2016 }
Создание Динамических Документов
Поскольку в модели не было определено ни одного поля, нам нужно будет предоставить любой произвольный набор полей нашему объекту динамического документа.
Вы можете поместить здесь любое количество полей любого типа. Вам даже не нужно, чтобы типы полей были одинаковыми для нескольких документов.
Есть несколько способов добиться этого:
В любом случае мы можем добавить любой набор полей, так как реализация Dynamic Document
сама их не определяет.
Если мы отправим запрос POST на localhost:5000/director/
:
$ curl -X POST -H "Content-Type: application/json"\ -d '{"name": "James Cameron", "age": 57}'\ localhost:5000/director/
Это приводит к:
{ "_id": { "$oid": "6029111e184c2ceefe175dfe" }, "age": 57, "name": "James Cameron" }
Обновление документов
Чтобы обновить документ, мы извлекаем постоянный документ из базы данных, обновляем его поля и вызываем метод update()
для измененного объекта в памяти:
@app.route('/movies/', methods=['PUT'])
def update_movie(id):
body = request.get_json()
movie = Movie.objects.get_or_404(id=id)
movie.update(**body)
return jsonify(str(movie.id)), 200
Давайте отправим запрос на обновление:
$ curl -X PUT -H "Content-Type: application/json"\ -d '{"year": 2016}'\ localhost:5000/movies/600eb609b076cdbc347e2b9a/
Это вернет идентификатор обновленного документа:
"600eb609b076cdbc347e2b9a"
Мы также можем обновить сразу несколько документов с помощью метода update ()
. Мы просто запрашиваем базу данных для документов, которые мы намерены обновить, учитывая некоторые условия, и вызываем метод update для результирующего набора запросов:
@app.route('/movies_many/', methods=['PUT']) def update_movie_many(title): body = request.get_json() movies = Movie.objects(year=year) movies.update(**body) return jsonify([str(movie.id) for movie in movies]), 200
Давайте отправим запрос на обновление:
$ curl -X PUT -H "Content-Type: application/json"\ -d '{"year": 2016}'\ localhost:5000/movies_many/2010/
Это вернет список идентификаторов обновленных документов:
[ "60123af478a2c347ab08c32b", "60123b0989398f6965f859ab", "60123bfe2a91e52ba5434630", "602907f3f3918e990ba24f13", "602919f67e80d573ad3f15e4" ]
Удаление документов
Подобно методу update ()
, метод delete()
удаляет объект на основе его поля id
:
@app.route('/movies/', methods=['DELETE'])
def delete_movie(id):
movie = Movie.objects.get_or_404(id=id)
movie.delete()
return jsonify(str(movie.id)), 200
Конечно, поскольку у нас может не быть гарантии, что объект с заданным идентификатором присутствует в базе данных, мы используем метод get_or_404()
для его извлечения перед вызовом delete()
.
Давайте отправим запрос на удаление:
$ curl -X DELETE -H "Content-Type: application/json"\ localhost:5000/movies/600eb609b076cdbc347e2b9a/
Это приводит к:
"600eb609b076cdbc347e2b9a"
Мы также можем удалить сразу несколько документов, для этого мы запросим базу данных для документов, которые мы хотим удалить, а затем вызовем метод delete()
в результирующем наборе запросов.
Например, чтобы удалить все фильмы, снятые в определенном году, мы сделаем что-то вроде следующего:
@app.route('/movies/delete-by-year//', methods=['DELETE']) def delete_movie_by_year(year): movies = Movie.objects(year=year) movies.delete() return jsonify([str(movie.id) for movie in movies]), 200
Давайте отправим запрос на удаление, удалив все записи фильма за год 2009
:
$ curl -X DELETE -H "Content-Type: application/json" localhost:5000/movies/delete-by-year/2009/
Это приводит к:
[ "60291fdd4756f7031638b703", "60291fde4756f7031638b704", "60291fdf4756f7031638b705" ]
Работа с файлами
Создание и хранение файлов
MongoEngine позволяет очень легко взаимодействовать с MongoDB GridFS для хранения и извлечения файлов. MongoEngine достигает этого с помощью своего FileField()
.
Давайте посмотрим, как мы можем загрузить файл в MongoDB GridFS с помощью MongoEngine:
@app.route('/movies_with_poster', methods=['POST']) def add_movie_with_image(): # 1 image = request.files['file'] # 2 movie = Movie(title = "movie with poster", year=2021) # 3 movie.poster.put(image, filename=image.filename) # 4 movie.save() # 5 return jsonify(movie), 201
Давайте пройдемся по вышеприведенному блоку, строка за строкой:
- Сначала мы получаем изображение из ключа
file
inrequest.файлы
- Далее мы создаем объект
Movie
- В отличие от других полей, мы не можем присвоить значение полю
File()
с помощью обычного оператора присваивания, вместо этого мы будем использовать методput()
для отправки нашего изображения. Методput()
принимает в качестве аргументов загружаемый файл (это должен быть файлоподобный объект или байтовый поток), имя файла и необязательные метаданные. - Чтобы сохранить наш файл, мы, как обычно, вызываем метод
save()
для объекта movie. - Мы возвращаем объект
movie
с идентификатором, ссылающимся на изображение:
{ "_id": { "$oid": "60123e4d2628f541032a0900" }, "cast": [], "poster": { "$oid": "60123e4d2628f541032a08fe" }, "title": "movie with poster", "year": 2021 }
Как вы можете видеть из ответа JSON, файл фактически сохраняется как отдельный документ MongoDB, и у нас просто есть ссылка на него в базе данных.
Извлечение файлов
Как только мы поместили()
файл в Файловое поле()
, мы можем прочитать()
его обратно в память, как только у нас есть объект, содержащий это поле. Давайте посмотрим, как мы можем извлекать файлы из документов MongoDB:
from io import BytesIO from flask.helpers import send_file @app.route('/movies_with_poster//', methods=['GET']) def get_movie_image(id): # 1 movie = Movie.objects.get_or_404(id=id) # 2 image = movie.poster.read() content_type = movie.poster.content_type filename = movie.poster.filename # 3 return send_file( # 4 BytesIO(image), attachment_filename=filename, mimetype=content_type), 200
Давайте посмотрим, что делается в сегментах:
- Мы извлекли документ фильма, содержащий изображение.
- Затем мы сохранили изображение в виде строки байтов в переменной
image
, получили имя файла и тип контента и сохранили их в переменныхfilename
иcontent_type
. - Используя вспомогательный метод Flasks
send_file ()
, мы пытаемся отправить файл пользователю, но так как изображение является объектомbytes
, мы получимAttributeError: объект 'bytes' не имеет атрибута 'read'
asЧтобы решить эту проблему, мы используем класс
BytesIO() - из модуля
io
для декодирования объекта bytes обратно в файлоподобный объект, который может отправитьsend_file ()
.
Удаление файлов
Удаление документов, содержащих файлы, не приведет к удалению файла из GridFS, поскольку они хранятся как отдельные объекты.
Чтобы удалить документы и сопровождающие их файлы, мы должны сначала удалить файл, прежде чем удалять документ.
File Field()
также предоставляет метод delete ()
, который мы можем использовать, чтобы просто удалить его из базы данных и файловой системы, прежде чем мы продолжим удаление самого объекта:
@app.route('/movies_with_poster//', methods=['DELETE']) def delete_movie_image(id): movie = Movie.objects.get_or_404(id=id) movie.poster.delete() movie.delete() return "", 204
Вывод
MongoEngine предоставляет относительно простой, но многофункциональный Pythonic интерфейс для взаимодействия с MongoDB из приложения python, а Flask-MongoEngine делает еще проще интеграцию MongoDB в наши приложения Flask.
В этом руководстве мы изучили некоторые особенности MongoEngine и его расширения колбы. Мы создали простой CRUD API и использовали MongoDB GridFS для сохранения, извлечения и удаления файлов с помощью MongoEngine.В этом руководстве мы изучили некоторые особенности MongoEngine и его расширения колбы. Мы создали простой CRUD API и использовали MongoDB GridFS для сохранения, извлечения и удаления файлов с помощью MongoEngine.