Руководство для начинающих по созданию вашего первого веб-сервиса RESTful с использованием Python и Flask для выполнения операций CRUD в базе данных MongoDB.

Введение

Веб-разработка — это уже не просто создание одностраничного веб-сайта с использованием HTML и некоторого CSS, она значительно продвинулась за эти годы, и вы все чаще сталкиваетесь с необходимостью подключения вашего внешнего сайта к той или иной форме серверной службы, будь то это для доступа к базе данных или для обработки данных.

Один из самых простых способов заставить ваш интерфейс общаться с серверной службой — создать веб-службу RESTful. Если вы не знакомы с REST и вам интересно, о чем я вообще говорю, вы можете прочитать мою предыдущую статью, посвященную основам REST и API в целом.

В этом руководстве я расскажу о создании REST API с использованием Python и Flask. Flask — это «микро» фреймворк для создания веб-приложений с использованием Python [1]. Проще говоря, это означает, что он не включает в себя все дополнительные модули, которые могут вам понадобиться для вашей системы, такие как клиенты базы данных, но то, что у него есть работает хорошо, очень хорошо; для быстрого запуска вашего веб-приложения. Его можно использовать для создания полноценных веб-приложений, которые обслуживают динамические HTML-страницы, или для создания веб-службы RESTful, именно для этого мы будем использовать его в этом руководстве.

Если вам не нравится Python, а скорость вам больше нравится Java, я ранее опубликовал руководство для начинающих по созданию веб-сервиса RESTful с использованием Java и Spring Boot, посмотрите его.

Начиная

Система, которую я буду создавать в этом руководстве, представляет собой службу RESTful, которую можно использовать в качестве серверной части для вашего собственного веб-сайта списка дел или приложения, которое может выполнять операции создания, чтения, обновления и удаления (CRUD) в вашей базе данных. Что касается базы данных, я буду подключать службу к локальному экземпляру MongoDB, для подключения к размещенному экземпляру Mongo достаточно просто добавить URL-адрес вашего подключения. Хотя я назвал это руководство для начинающих, я все же предполагаю, что у вас есть хотя бы некоторые знания о Python и концепциях программирования в целом.

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

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

На момент написания статьи в проекте используются последние версии всех используемых компонентов Python 3.9, Flask 2.0.3 и Pymongo 4.0.2.

Давайте напишем код

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

Как только мы создадим наш репозиторий, мы сможем создать его экземпляр для использования в методах нашего REST API, поэтому сначала создадим его.

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

В этом первом разделе репозитория, начиная с первой строки, мы импортируем модули, которые будем использовать в классе. Этими модулями являются MongoClient для подключения к нашей базе данных и некоторые инструменты BSON и JSON для форматирования наших документов ToDo до их сохранения в базе данных и после их повторного извлечения из нее. Это форматирование станет более понятным, когда вы будете читать дальше.

В шестой строке мы объявляем, что создаем класс с именем ToDoRepository. В строках с восьмой по одиннадцатую мы создаем наш метод __init__, этот метод используется для инициализации экземпляра нашего класса. В методе init мы создаем три объекта, первый — это объект с именем «client», этот клиент создается с использованием имени хоста и порта нашего экземпляра MongoDB. В моем случае, поскольку я использую локально запущенный экземпляр MongoDB, имя нашего хоста — «localhost», а номер порта — 27017.

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

Если вы хотите подключиться к размещенному экземпляру Mongo, вы можете заменить localhost и номер порта на свой URI MongoDB, например.. MongoClient(‘mongodb://externalmongoinstance:27017/’)

Теперь с объектом client мы создадим другой объект с именем 'db', этот объект является ссылкой на базу данных, которую мы хотим использовать 'todo_database. >'. Если эта база данных еще не существует, она будет создана.

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

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

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

Первый метод, начинающийся с первой строки, объявляет метод с именем «get_id(self, todo_id)». Этот метод принимает два параметра: self и todo_id; self — это ссылка на экземпляр класса, для которого вызывается метод, это позволяет нам получить доступ к переменным экземпляра, подобным объявленным в нашем методе инициализации. Второй параметр — todo_id, это идентификатор документа ToDo, который мы хотим получить из нашей коллекции.

Во второй строке мы вызываем метод find_one объекта коллекции todos. Этот метод принимает запрос в качестве параметра, для нашего запроса мы передаем объект JSON с одним ключом «_id». Вы заметите, что для значения идентификатора мы передаем новый экземпляр ObjectId, который создается с идентификатором документа, который мы хотим найти.

Когда у нас есть результат запроса, нам нужно преобразовать его в JSON, потому что Mongo использует BSON, который визуально похож на JSON, но отличается [2]. Преобразование происходит в третьей строке, с этим недавно преобразованным объектом JSON нам нужно немного отформатировать его, чтобы с ним было легче обращаться.

В приведенном выше Gist вы можете увидеть два объекта JSON, первый — это то, как форматируются наши документы, когда мы получаем их из метода find. Как вы можете видеть в верхнем объекте, _id имеет другой объект в качестве значения, которое содержит в себе идентификатор документа. Чтобы упростить обработку, когда мы получаем это в нашем интерфейсе, мы хотим отформатировать это, чтобы оно выглядело как нижний объект.

После форматирования в четвертой строке мы возвращаем результирующий объект JSON. Метод 'get_all' очень похож на предыдущий, за исключением того, что он использует метод 'find' для коллекции todos, который возвращает список документов, каждый из которых отформатирован. в цикле for.

В этом блоке кода у нас есть свои методы сохранения, обновления и удаления документов в коллекции. Метод save использует метод insert_one для сохранения новой задачи. После сохранения ответ форматируется, и мы возвращаем идентификатор вновь созданного документа.

Метод update используется для обновления существующего документа в коллекции To-Do, он делает это с помощью метода «update_one». Этот метод принимает два аргумента: один — это фильтр для поиска документов, которые нужно обновить, а второй — новые значения, которые нужно установить в документе (документах), найденном с помощью фильтра. Метод обновления можно использовать для обновления нескольких объектов в зависимости от переданного фильтра. После обновления мы возвращаем количество обновленных документов.

delete довольно прямолинеен, он использует метод «delete_one», которому мы передаем фильтр, содержащий идентификатор задачи, которую мы хотим удалить. Опять же, поскольку этот метод можно использовать для удаления, мы возвращаем количество удаленных документов.

Поскольку мы используем идентификаторы в качестве фильтров в методах update_one и delete_one, мы будем обновлять только один документ, поскольку идентификатор документа должен быть уникальным. Фильтр можно легко обновить для поиска любых других пар ключ-значение в документах, если вы хотите обновить несколько документов одновременно.

Конечные точки REST

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

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

В приведенном выше коде мы импортируем модули, которые нам понадобятся для нашего скрипта, мы также импортируем наш ToDoRepository. Следующее, что мы делаем, — это создаем два объекта: приложение и репозиторий. Объект приложения является экземпляром класса Flask, это позволит нам украсить наши методы и позволит распознавать их как наши конечные точки REST. Объект repo является экземпляром нашего класса ToDoRepository, поэтому мы можем вызывать наши методы CRUD для взаимодействия с нашей базой данных.

Два метода в этом коде предназначены для извлечения наших задач из базы данных, один для получения одной задачи с использованием ее идентификатора и еще один метод для получения всех задач в базе данных. Мы делаем это с помощью методов get_all и get_id, которые мы только что создали в нашем классе ToDoRepository. Здесь важно отметить, как мы получаем идентификатор в методе get_todo, мы получаем его через URL-адрес. Это можно сделать, добавив следующее в путь в декораторе над методом «/‹string:todo_id›». Это сообщает Flask, что мы ожидаем строковое значение в качестве параметра пути, и оно будет называться todo_id,это значение затем может использоваться в методе для вызова метода get_id наш ToDoRepository.

После извлечения из нашей базы данных мы преобразуем документы To-Do в объект JSON и возвращаем их с кодом состояния HTTP 200, что означает, что запрос был успешным. Flask достаточно умен, чтобы обрабатывать большинство неудачных ответов за нас.

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

Создать и обновить

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

В методе POST с именем 'save_todo' мы читаем тело HTTP-запроса, используемого для вызова метода, мы делаем это с помощью 'request.get_json( )' это вернет тело запроса в объекте JSON. Этот объект JSON является новой задачей, которую мы хотим сохранить. Чтобы сохранить его, мы используем метод save нашего ToDoRepository,поскольку наш метод сохранения возвращает идентификатор только что сохраненного документа, мы можем использовать его, чтобы затем найти новый To-Do. в нашей коллекции и вернуть его в теле нашего HTTP-ответа с кодом состояния 201, что означает, что что-то было создано.

Метод PUT используется для обновления существующих задач. Для этого мы можем использовать метод update нашего ToDoRepository. Метод PUT считывает тело запроса аналогично методу POST. Как только у нас есть ToDo, отправленное в запросе, мы сохраняем идентификатор в отдельной переменной для последующего использования в методе. Поскольку метод обновления ToDoRepository возвращает количество обновленных документов, мы можем проверить это число, чтобы убедиться, что обновление прошло успешно. Если это было успешно, мы можем получить только что обновленный документ из коллекции, используя сохраненный ранее идентификатор, и вернуть его в ответе с кодом состояния 200. Если обновление не было успешным, например, если мы попытались обновить не -existent ToDo мы можем вернуть код состояния 404, что означает, что ресурс не найден.

Удалить

Метод удаления довольно прост для понимания, он принимает идентификатор в качестве параметра пути в URL-адресе и использует его для вызова метода delete в ToDoRepository. Ответ метода Метод удаления — это количество документов, удаленных из базы данных, поэтому мы можем использовать его, чтобы проверить, было ли удаление успешным или нет. Если количество удаленных документов больше или равно единице, это было успешно, и мы можем вернуть код состояния HTTP 200, если нет, мы вернем 404.

Последняя часть приведенного выше Gist заключается в вызове метода run объекта Flask, созданного в верхней части этого скрипта. Это запустит нашу службу REST, и если все в порядке, она будет готова принимать запросы.

Тестируем наш новый REST API

Теперь, когда у нас написан код, нам нужно его как-то протестировать, мы можем сделать это, запустив приложение и отправив ему несколько запросов с помощью REST-клиента, такого как Postman.

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

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

Хорошо, давайте начнем тестировать наши конечные точки. Сначала мы проверим метод POST, так как метод GET ничего не вернет, пока мы не добавим несколько задач в базу данных. Мы можем отправить следующий объект JSON на этот URL: «http://localhost:5000/».

У объекта есть два ключа: один — логическое значение для отслеживания состояния выполнения задачи, а второй — строка для описания задачи. Используя Postman, я отправил вышеуказанный JSON на конечную точку POST.

На изображении вы можете видеть, что объект ответа содержит только что созданное задание с добавленным к нему значением _id. Вы также могли заметить, что наш ответ вернулся с пометкой 201 CREATED в середине справа от изображения.

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

На изображении вы увидите, что мы получили назад нашу задачу, созданную нашим POST-запросом выше, а также мы получили код состояния 200 OK. Поскольку у нас не было никаких параметров пути, добавленных к URL выше мы вызвали метод get_todos, который вернет все документы в коллекции. Затем мы должны протестировать наш метод get_todo, который будет возвращать конкретную задачу на основе ее идентификатора. Для этого мы можем добавить идентификатор, полученный из одного из наших предыдущих ответов, добавить его к URL-адресу и отправить запрос GET.

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

Затем мы можем протестировать наш метод PUT, это делается аналогично нашему POST-запросу, за исключением того, что тело запроса также должно включать идентификатор.

На изображении выше вы увидите, что мы отправили задачу, созданную нашим POST-запросом, за исключением того, что мы изменили описание с «Ешьте закуски» на «Ешьте больше закусок». Это обновит To-Do в нашей базе данных с заданным идентификатором до значений, отправленных в этом запросе, и, как видно из ответа, он сделал именно это. Чтобы убедиться в этом, мы можем снова использовать метод GET для перекрестной проверки результатов.

Последний метод, который нам нужно протестировать, — это наш метод DELETE. Это очень просто, мы можем использовать параметр URL и путь из нашего запроса GET выше, чтобы отправить наш запрос DELETE.

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

Вот и все! Мы создали и протестировали REST API с использованием Flask и Python! Отсюда я бы предложил попытаться сделать вышеуказанные методы немного более сложными или добавить больше методов для поиска задач с определенными значениями в них, а не только по их идентификатору. Вы узнаете больше о том, как работает Flask, чем больше будете с ним экспериментировать и пробовать новые вещи, как насчет того, чтобы попытаться добавить скрипты в образ Docker и запустить его в контейнере.

Подведение итогов

Я надеюсь, что вы запустили проект и заработали, как ожидалось, и смогли протестировать REST API, если вы этого не сделали и обнаружили некоторые проблемы с руководством, оставьте ответ в истории, и я постараюсь помочь вам, чем смогу. . Если вы обнаружили какие-либо проблемы с руководством или с чем-то, что я мог бы сделать лучше, не стесняйтесь просветить меня и оставить ответ! Как и все мои статьи, я пишу это как образовательный опыт для себя и других, поэтому я приветствую любые конструктивные отзывы.
Если вам понравилось руководство, рассмотрите возможность подписаться на обновления моих будущих статей. Я планирую сделать руководство по контейнеризации этого проекта и моего проекта Java and Spring Boot и запуску их в Docker, поэтому, если это звучит интересно, рассмотрите возможность подписаться на меня, чтобы получать обновления, также, если вы еще не являетесь участником Medium, вы можете присоединиться по моей членской ссылке.

Рекомендации

[1]^https://flask.palletsprojects.com/en/2.0.x/foreword/

[2] ^ https://www.mongodb.com/json-and-bson