Руководство по созданию API-запросов в Python
Python сейчас переживает свой период возрождения. Собственно, он и не умирал, но сейчас степень его использования высока, как никогда раньше. Именно на этот язык полагаются разработчики машинного обучения и специалисты по обработке данных, а большая часть экосистемы веб-разработки вокруг этого языка продолжает расти.
Один из аспектов, влияющих на все три эти специализации, — мощные преимущества API. Сбор данных и подключение к внешним сервисам — важная часть любого языка. В этой статье мы рассмотрим основные библиотеки для выполнения HTTP-запросов, а также некоторые распространенные варианты их использования, позволяющие подключаться к API в Python. Но сначала следует ответить на один важный вопрос:
Подходит ли Python для API?
Это может показаться странным вопросом, но, учитывая засилье в вебе Node.js и Ruby, вы можете подумать, что Python не так хорош для создания API-запросов. Но это не так. На самом деле Python тоже давно присутствует в вебе, особенно если принять во внимание его библиотеки Flask и Django.
Поскольку Python предоставляет мощные и доступные инструменты для работы с данными, имеет смысл также использовать его и для получения самих источников данных. Для этого и служат запросы к API. Давайте начнем с самой популярной библиотеки Python под названием Requests.
Библиотека Requests
Requests — это широко известная библиотека, которую разработчики используют для выполнения API-запросов в Python. Она предлагает интерфейс для синхронного выполнения HTTP-запросов. Давайте рассмотрим несколько распространенных типов запросов, которые вы можете делать с помощью этой библиотеки. В дальнейшем мы будем предполагать, что эта библиотека у вас уже установлена. Для установки вы можете использовать документацию библиотеки, но сам процесс очень прост.
Библиотеку также нужно импортировать:
Распространенные типы API-запросов в библиотеке Requests
Наиболее простой GET-запрос интуитивно понятен:
Этот запрос был довольно прост. Давайте теперь рассмотрим более сложные запросы. Часто документация по API требует, чтобы вы передавали параметры запроса в конкретную конечную точку. Чтобы это сделать, мы можем передать параметры запроса в метод get в качестве второго аргумента.
Переменная response содержит данные, возвращаемые API в качестве ответа на наш запрос. Есть три основных способа доступа к этим данным:
Помимо параметров запроса, мы также можем передавать в запрос заголовки:
Здесь мы передаем в качестве аргумента заголовок в виде словаря Python.
Последний тип API-запроса, который мы сейчас рассмотрим — это полнофункциональный POST-запрос с аутентификацией. Этот пример объединит два предыдущих, здесь мы передадим в качестве аргументов и заголовок, и данные.
Здесь мы отправляем данные из переменной payload в формате словаря Python. Для большинства современных API нам часто требуется отправлять данные в формате JSON. В следующем примере мы используем встроенный json-конвертер из библиотеки Requests.
Библиотека Requests отлично подходит для выполнения синхронных API-запросов, но иногда вашему приложению нужно выполнять асинхронные запросы. Для этого мы можем использовать асинхронную HTTP-библиотеку aiohttp.
Библиотека aiohttp
Для выполнения асинхронных HTTP-запросов вам необходимо воспользоваться некоторыми новыми функциями Python 3. Хотя у библиотеки Requests есть различные дополнения и плагины для асинхронного программирования, одной из наиболее популярных библиотек для этого является библиотека aiohttp. Используя ее вместе с библиотекой acincio, мы можем эффективно исполнять асинхронные запросы. Код будет немного сложнее, но асинхронные запросы сами по себе предоставляют большую свободу действий.
Для начала установим библиотеку aiohttp:
Распространенные типы API-запросов в библиотеке aiohttp
Как и раньше, начнем с GET-запроса. Для начала импортируем обе библиотеки и определим функцию main() как асинхронную.
В этом коде мы выполняем следующие шаги:
Если вы никогда раньше не работали с асинхронными методами в Python, это может показаться странным и сложным по сравнению с предыдущими примерами. Создатели библиотеки aiohttp рекомендуют устанавливать один сеанс для каждого приложения и открывать / закрывать соединения в этом сеансе. Чтобы сделать наши примеры самодостаточными, мы составили примеры в менее эффективном формате.
Далее давайте рассмотрим полнофункциональный POST-запрос с заголовками аутентификации, как мы сделали в примере для библиотеки Requests.
Между этим примером и предыдущим есть несколько различий:
При помощи этих двух фрагментов кода мы можем выполнять большинство распространенных задач, связанных с API. Дополнительные функции, такие как загрузка файлов и данные формы, можно найти в документации для разработчиков aiohttp.
Некоторые дополнительные библиотеки
Хотя библиотека Requests и является самой популярной и универсальной, в некоторых особых случаях вы можете найти полезными и другие библиотеки.
Пишем API на Python (с Flask и RapidAPI)
Если вы читаете эту статью, вероятно, вы уже знакомы с возможностями, которые открываются при использовании API (Application Programming Interface).
Добавив в свое приложение один из многих открытых API, вы можете расширить функциональность этого приложения либо же дополнить его нужными данными. Но что, если вы разработали уникальную функцию, которой хотите поделиться с коммьюнити?
Ответ прост: нужно создать собственный API.
Несмотря на то, что это поначалу кажется сложной задачей, на самом деле всё просто. Мы расскажем, как это сделать с помощью Python.
Что нужно для начала работы
Для разработки API необходимы:
Рекомендуем бесплатный интенсив по программированию для начинающих:
Разработка telegram-бота на C# — 26–28 августа. Бесплатный интенсив, который позволяет разобраться в том, как работают боты-помощники, в особенностях работы с API Telegram и прочих нюансах. Трое лучших участников получат от Skillbox 30 000 рублей.
Перед тем как начать
Мы собираемся разработать RESTful API с базовой CRUID-функциональностью.
Чтобы полностью понять задачу, давайте разберемся с двумя терминами, упомянутыми выше.
REST API (Representational State Transfer) — это API, которое использует HTTP-запросы для обмена данными.
REST API должны соответствовать определенным критериям:
CRUD — концепция программирования, которая описывает четыре базовых действия (create, read, update и delete).
В REST API типы запросов и методы запроса отвечают за такие действия, как post, get, put, delete.
Теперь, когда мы разобрались с базовыми терминами, можно приступить к созданию API.
Разработка
Давайте создадим репозиторий цитат об искусственном интеллекте. ИИ — одна из наиболее активно развивающихся технологий сегодня, а Python — популярный инструмент для работы с ИИ.
С этим API разработчик Python сможет быстро получать информацию об ИИ и вдохновляться новыми достижениями. Если у разработчика есть ценные мысли по этой теме, он сможет добавлять их в репозиторий.
Начнем с импорта необходимых модулей и настройки Flask:
В этом сниппете Flask, Api и Resource — классы, которые нам нужны.
Reqparse — это интерфейс парсинга запросов Flask-RESTful… Также понадобится модуль random для отображения случайной цитаты.
Теперь мы создадим репозиторий цитат об ИИ.
Каждая запись репо будет содержать:
Теперь нужно создать ресурсный класс Quote, который будет определять операции эндпоинтов нашего API. Внутри класса нужно заявить четыре метода: get, post, put, delete.
Начнем с метода GET
Он дает возможность получить определенную цитату путем указания ее ID или же случайную цитату, если ID не указан.
Метод GET возвращает случайную цитату, если ID содержит дефолтное значение, т.е. при вызове метода ID не был задан.
Если он задан, то метод ищет среди цитат и находит ту, которая содержит заданный ID. Если же ничего не найдено, выводится сообщение “Quote not found, 404”.
Помните: метод возвращает HTTP-статус 200 в случае успешного запроса и 404, если запись не найдена.
Теперь давайте создадим POST-метод для добавления новой цитаты в репозиторий
Он будет получать идентификатор каждой новой цитаты при вводе. Кроме того, POST будет использовать reqparse для парсинга параметров, которые будут идти в теле запроса (автор и текст цитаты).
В коде выше POST-метод принял ID цитаты. Затем, используя reqparse, он получил автора и цитату из запроса, сохранив их в словаре params.
Если цитата с указанным ID уже существует, то метод выводит соответствующее сообщение и код 400.
Если цитата с указанным ID еще не была создана, метод создает новую запись с указанным ID и автором, а также другими параметрами. Затем он добавляет запись в список ai_quotes и возвращает запись с новой цитатой вместе с кодом 201.
Теперь создаем PUT-метод для изменения существующей цитаты в репозитории
PUT-метод, аналогично предыдущему примеру, берет ID и input и парсит параметры цитаты, используя reqparse.
Если цитата с указанным ID существует, метод обновит ее с новыми параметрами, а затем выведет обновленную цитату с кодом 200. Если цитаты с указанным ID еще нет, будет создана новая запись с кодом 201.
Наконец, давайте создадим DELETE-метод для удаления цитаты, которая уже не вдохновляет
Этот метод получает ID цитаты при вводе и обновляет список ai_quotes, используя общий список.
Теперь, когда мы создали все методы, всё, что нам нужно, — просто добавить resource к API, задать путь и запустить Flask.
Наш REST API Service готов!
Далее мы можем сохранить код в файл app.py, запустив его в консоли при помощи команды:
Если все хорошо, то мы получим нечто вроде этого:
* Debug mode: on
* Running on 127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: XXXXXXX
После того как API создан, его нужно протестировать.
Сделать это можно при помощи консольной утилиты curl или клиента Insomnia REST либо же опубликовав API на Rapid API.
RapidAPI — самый большой в мире маркетплейс с более чем 10 000 API (и около 1 млн разработчиков).
RapidAPI не только предоставляет единый интерфейс для работы со сторонними API, но и даtт возможность быстро и без проблем опубликовать ваш собственный API.
Для того чтобы сделать это, сначала нужно опубликовать его на каком-нибудь сервере в сети. В нашем случае воспользуемся Heroku. Работа с ним не должна вызвать никаких сложностей, (узнать о нём больше можно здесь).
Как опубликовать ваш API на Heroku
1. Устанавливаем Heroku.
Первым делом нужно зарегистрироваться и установить Heroku Command Line Interface (CLI). Это работает на Ubuntu 16+.
2. Добавляем необходимые файлы.
Теперь нужно добавить файлы для публикации в папку в нашем приложении:
Procfile будет содержать:
web: gunicorn app:app
Теперь, когда созданы файлы, давайте инициализируем git-репо и закоммитим:
3. Создаем новое Heroku-приложение.
Отправляем master branch в удаленный репо Heroku:
Теперь можно начать, открыв API Service при помощи команд:
Как добавить ваш Python API в маркетплейс RapidAPI
После того как API-сервис опубликован на Heroku, можно добавить его к Rapid API. Здесь подробная документация по этой теме.
1. Создаем аккаунт RapidAPI.
Регистрируем бесплатную учетную запись — это можно сделать при помощи Facebook, Google, GitHub.
2. Добавляем API в панель управления.
3. Далее вводим общую информацию о своем API.
4. После нажатия “Add API” появляется новая страничка, где можно ввести информацию о нашем API.
5. Теперь можно либо вручную ввести эндпоинты API, либо загрузить swagger-file при помощи OpenAPI.
Ну а теперь нужно задать эндпоинты нашего API на странице Endpoints. В нашем случае эндпоинты соответствуют концепции CRUD (get, post, put, delete).
Далее нужно создать эндпоинт GET AI Quote, который выводит случайную цитату (в том случае, если ID дефолтный) или цитату для указанного ID.
Для создания эндпоинта нужно нажать кнопку “Create Endpoint”.
Повторяем этот процесс для всех других эндпоинтов API. На этом всё! Поздравляю, вы опубликовали ваш API!
Если все хорошо, страничка API будет выглядеть как-то так:
Заключение
В этой статье мы изучили процесс создания собственного RESTful API Service на Python, вместе с процессом публикации API в облаке Heroku и добавлением его в каталог RapidAPI.
Но в тестовом варианте были показаны только базовые принципы разработки API — такие нюансы, как безопасность, отказоустойчивость и масштабируемость, не рассматривались.
При разработке реального API все это нужно учитывать.
Пишем веб сервис на Python с помощью FastAPI
Знаю, знаю, наверное вы сейчас думаете «что, опять?!».
Да, на Хабре уже неоднократно писали о фреймворке FastAPI. Но я предлагаю рассмотреть этот инструмент немного подробнее и написать API своего собственного мини Хабра без кармы и рейтингов, зато с блэкджеком и с тестами, аутентификацией, миграциями и асинхронной работой с БД.
Схема базы данных и миграции
Прежде всего, с помощью SQLAlchemy Expression Language, опишем схему базы данных. Создадим файл models/users.py:
И файл models/posts.py:
Чтобы автоматизировать миграции базы данных, установим alembic:
Для инициализации Alembic выполним:
Эта команда создаст в текущей директории файл alembic.ini и каталог migrations содержащий:
Формат %(variable_name)s позволяет нам устанавливать разные значения переменных в зависимости от среды окружения, переопределяя их в файле env.py например вот так:
Здесь мы берем значения DB_USER, DB_PASS, DB_NAME и DB_HOST из переменных окружения. Кроме этого, в файле env.py указываются метаданные нашей базы в атрибуте target_metadata, без этого Alembic не сможет определить какие изменения необходимо произвести в базе данных.
Все готово и мы можем сгенерировать миграции и обновить БД:
Запускаем приложение и подключаем БД
Создадим файл main.py:
И запустим приложение, выполнив команду:
Убедимся, что все работает как надо. Открываем в браузере http://127.0.0.1:8000/ и видим
Чтобы подключиться к базе данных, воспользуемся модулем databases, который позволяет выполнять запросы асинхронно.
Настроим startup и shutdhown события нашего сервиса, при которых будут происходить подключение и отключение от базы данных. Отредактируем файл main.py:
Открываем http://127.0.0.1:8000/ и если видим в ответе пустой список [], значит все прошло хорошо и можно двигаться дальше.
Валидация запроса и ответа
Реализуем возможность регистрации пользователей. Для этого нам понадобиться валидировать HTTP запросы и ответы. Для решения этой задачи воспользуемся библиотекой pydantic:
Создадим файл schemas/users.py и добавим модель, отвечающую за валидацию тела запроса:
Обратите внимание, что типы полей определяются с помощью аннотации типов. Помимо встроенных типов данных, таких как int и str, pydantic предлагает большое количество типов, обеспечивающих дополнительную проверку. Например, тип EmailStr проверяет, что полученное значение — корректный email. Для использования типа EmailStr необходимо установить модуль email-validator:
Тело ответа должно содержать свои собственные специфические поля, например id и access_token, поэтому добавим в файл schemas/users.py модели, отвечающие за формирование ответа:
Для каждого поля модели можно написать кастомный валидатор. Например, hexlify_token преобразует UUID значение в hex строку. Стоит отметить, что вы можете использовать класс Field, когда нужно переопределить стандартное поведение поля модели. Например, token: UUID4 = Field(. alias=«access_token») устанавливает псевдоним access_token для поля token. Для обозначения, что поле обязательно, в качестве первого параметра передается специальное значение — . (ellipsis).
Добавим файл utils/users.py, в котором создадим методы, необходимые для записи пользователя в БД:
Создадим файл routers/users.py и добавим sign-up роут, указав, что в запросе он ожидает модель CreateUser и возвращает модель User:
Осталось только подключить роуты из файла routers/users.py. Для этого добавим в main.py следующие строки:
Аутентификация и контроль доступа
Теперь, когда в нашей базе данных есть пользователи, все готово для того чтобы настроить аутентификацию приложения. Добавим эндпоинт, который принимает имя пользователя и пароль и возвращает токен. Обновим файл routers/users.py, добавив в него:
При этом, нам не нужно самостоятельно описывать модель запроса, Fastapi предоставляет специальный dependency класс OAuth2PasswordRequestForm, который заставляет роут ожидать два поля username и password.
Чтобы ограничить доступ к определенным роутам для неаутентифицированных пользователей, напишем метод-зависимость(dependency). Он проверит, что предоставленный токен принадлежит активному пользователю и вернет данные пользователя. Это позволит нам использовать информацию о пользователе во всех роутах, требующих аутентификации. Создадим файл utils/dependecies.py:
Обратите внимание, что зависимость может в свою очередь зависеть от другой зависимости. К пример OAuth2PasswordBearer — зависимость, которая дает понять FastAPI, что текущий роут требует аутентификации.
Чтобы проверить, что все работает как надо, добавим роут /users/me, возвращающий детали текущего пользователя. В файл routers/users.py добавим строки:
Теперь у нас есть роут /users/me к которому имеют доступ только аутентифицированные пользователи.
Все готово для того, чтобы наконец добавить возможность пользователям создавать и редактировать публикации:
Подключим новые роуты, добавив в main.py
Тестирование
Тесты мы будем писать на pytest:
Для тестирования эндпоинтов FastAPI предоставляет специальный инструмент TestClient.
Напишем тест для эндпоинта, который не требует подключения к базе данных:
Как видите, все достаточно просто. Необходимо инициализировать TestClient, и использовать его для тестирования HTTP запросов.
Для тестирования остальных эндпоинтов, необходимо создать тестовую БД. Отредактируем файл main.py, добавив в него конфигурацию тестовой базы:
Мы по-прежнему используем БД «async-blogs» для нашего приложения. Но если задано значение переменной окружение TESTING, тогда использовуется БД «async-blogs-temp-for-test».
Чтобы база «async-blogs-temp-for-test» автоматически создавалась при запуске тестов и удалялась после их выполнения, создадим фикстуру в файле tests/conftest.py:
Используя фикстуру temp_db в тестах, мы сможем протестировать все эндпоинты нашего приложения:
Проектирование RESTful API с помощью Python и Flask
В последние годы REST (REpresentational State Transfer) стала стандартной архитектурой при дизайне веб-сервисов и веб-API.
В этой статье я покажу вам как просто создавать RESTful веб-сервисы используя Python и микрофреймворк Flask.
Что такое REST?
Характеристика системы REST определяется шестью правилами дизайна:
Что такое RESTful веб-сервис?
Архитектура REST разработана чтобы соответствовать протоколу HTTP используемому в сети Интернет.
Центральное место в концепции RESTful веб-сервисов это понятие ресурсов. Ресурсы представлены URI. Клиенты отправляют запросы к этим URI используя методы представленные протоколом HTTP, и, возможно, изменяют состояние этих ресурсов.
Методы HTTP спроектированы для воздействия на ресурс стандартным способом:
Метод HTTP | Действие | Пример |
---|---|---|
GET | Получить информацию о ресурсе | example.com/api/orders (получить список заказов) |
GET | Получить информацию о ресурсе | example.com/api/orders/123 (получить заказ #123) |
POST | Создать новый ресурс | example.com/api/orders (создать новый заказ из данных переданных с запросом) |
PUT | Обновить ресурс | example.com/api/orders/123 (обновить заказ #123 данными переданными с запросом) |
DELETE | Удалить ресурс | example.com/api/orders/123 (удалить заказ #123) |
Дизайн REST не дает рекомендаций каким конкретно должен быть формат данных передаваемых с запросами. Данные переданные в теле запроса могут быть JSON blob, или с помощью аргументов в URL.
Проектируем простой веб-сервис
При проектировании веб-сервиса или API нужно определить ресурсы, которые будут доступны и запросы, с помощью которых эти данные будут доступны, согласно правил REST.
Допустим мы хотим написать приложение To Do List и мы должны спроектировать веб-сервис для него. Первое что мы должны сделать, это придумать кореневой URL для доступа к этому сервису. Например мы могли бы придумать в качестве корневого URL что-то типа:
Здесь я решил включить в URL имя приложения и версию API. Добавление имени приложения в URL это хороший способ разделить между собой сервисы запущенные на одном сервере. Добавление версии API в URL может помочь, если вы захотите сделать обновление в будущем и внедрить в новой версии несовместимые функции и не хотите ломать работающие приложения которые работают на старом API.
Следующим шагом мы должны выбрать ресурсы, которые будут доступны через наш сервис. У нас очень простое приложение, у нас есть только задачи, поэтому нашими ресурсами могут быть только задачи из нашего ToDo листа.
Для доступа к ресурсам будем использовать следующие методы HTTP:
Метод HTTP | URI | Действие |
---|---|---|
GET | http://[hostname]/todo/api/v1.0/tasks | Получить список задач |
GET | http://[hostname]/todo/api/v1.0/tasks/[task_id] | Получить задачу |
POST | http://[hostname]/todo/api/v1.0/tasks | Создать новую задачу |
PUT | http://[hostname]/todo/api/v1.0/tasks/[task_id] | Обновить существующую задачу |
DELETE | http://[hostname]/todo/api/v1.0/tasks/[task_id] | Удалить задачу |
Наша задача будет иметь следующие поля:
На этом мы заканчиваем часть посвященную дизайну нашего сервиса. Осталось только реализовать это!
Краткое введение в микрофреймворк Flask
Если вы читали серию Мега-Учебник Flask, вы знаете что Flask это простой и достаточно мощный веб-фреймворк на Python.
Прежде чем мы углубимся в специфику веб-сервисов, давайте рассмотрим как обычно реализованы приложения Flask.
Я предполагаю, что вы знакомы с основами работы с Python на вашей платформе. В примерах я буду использовать Unix-подобную операционную систему. Короче говоря, это озночает, что они будут работать на Linux, MacOS X и даже на Windows, если вы будете использовать Cygwin. Команды будут несколько отличаться, если вы будете использовать нативную версию Python для Windows.
Теперь, когда Flask установлен давайте создадим простое веб приложение, для этого поместим следующий код в app.py :
Чтобы запустить приложение, мы должны запустить app.py :
Теперь вы можете запустить веб-браузер из набрать http://localhost:5000 чтобы увидеть наше маленькое приложение в действии.
Просто, не так ли? Теперь мы будем конвертировать наше приложение в RESTful сервис!
Реализация RESTful сервиса на Python и Flask
Создание веб-сервиса на Flask удивительно просто, гораздо проще, чем строить полноценные серверные приложения, вроде того, которое мы делали в серии Мега-Туториал.
Есть пара хороших расширений для Flask, которые могут облегчить создание RESTful сервисов, но наша задача настолько просто, что использование расширений будет излишним.
Клиенты нашего веб-сервиса будут просить сервис добавлять, удалять и модифицировать задачи, поэтому нам нужен простой способ хранить задачи. Очевидный способ сделать это — сделать небольшую базу данных, но, поскольку база данных выходи за рамки темы статьи, мы сделаем всё гораздо проще. Чтобы больше узнать о правильном использовании БД с Flask я снова рекомендую почитать мой Мега-Туториал.
Вместо базы данных мы будем хранить список наших задач в памяти. Это сработает, только если мы будем работать с сервером в один поток и в один процесс. Хоть для development-сервера это нормально, то для production-сервера это будет очень плохой идеей и будет лучше подумать об использовании базы данных.
Сейчас мы готовы реализовать первую точку входа в наш веб-сервис:
Как вы можете видеть, изменилось немногое. Мы создали в памяти задачи, которые являются не более чем простым массивом словарей. Каждая запись в массиве имеет все поля, которые мы определили выше для наших задач.
Вместо текста наша функция отдает JSON, в который Flask с помощью метода jsonify кодирует нашу структуру данных.
Использование веб-браузера, для тестирования веб-сервиса, не самая лучшая идея, т.к. с помощью веб-браузера не так просто генерировать все типы HTTP-запросов. Вместо этого мы будем использовать curl. Если curl у вас не установлен, лучше сделать это прямо сейчас.
Мы просто вызвали функцию нашего RESTful сервиса!
Сейчас давайте напишем вторую версию метода GET для наших задач. Если вы взгляните на таблицу выше, то следующим будет метод, который возвращает данные из одной задачи:
С этим аргументом мы ищем нашу задачу в базе. Если полученный id не найдется в базе, мы вернем ошибку 404, которая по спецификации HTTP означает «Resource Not Found».
Если задача будет найдена, мы просто упакуем ее в JSON с помощью функции jsonify и отправим как ответ, так же как поступали раньше, отправляя коллекцию.
Вот так выглядит действие этой функции, когда мы вызываем ее с помощью curl :
Когда мы запросили ресурс с id #2 мы получили его, но вместо ресурса с id #3 мы получили ошибку 404. Такую странную ошибку внутри HTML вместо JSON мы получили, потому, что Flask по умолчанию генерирует страницу с ошибкой 404. Так как это клиентские приложения будут всегда ожидать он нашего сервера JSON, то нам нужно изменить это поведение:
Так мы получим более соответствующий нашему API ответ:
Чтобы протестировать новую функцию мы используем следующую команду curl :
Примечание: если у вас Windows и вы используете Cygwin версию curl из bash тогда вышеописанная команда сработает как надо. Если вы используете нативную версию curl из обычно командной строки, то придется немного подшаманить с двойными кавычками:
В Windows вы используете двойные кавычки чтобы отделить тело запроса, и внутри запроса двойные кавычки чтобы экранировать третю кавычку.
Конечно, после выполнения этого запроса мы можем получим обновленный список задач:
Оставшиеся две функции нашего веб-сервиса будут выглядеть так:
Функция delete_task без сюрпризов. Для функции update_task мы стараемся предотвратить ошибки делая тщательную проверку входных аргументов. Мы должны убедиться, что предоставленные клиентом данные в надлежащем формате, прежде чем запишем их в базу.
Вызов функци обновляющей задачу с id #2 будет выглядеть примерно так:
Улучшаем интерфейс нашего сервиса
Сейчас основная проблема дизайна нашего сервиса в том, что клиенты вынуждены строить URI самостоятельно исходя из ID задач. Этот легко, но дает знание клиенту как строятся URI для доступа к данным, что может помешать в будущем, если мы захотим внести изменения в URI.
Вместо id задачи мы вернем полный URI, через который будет осуществляться выполнение всех действий с задачей. Для этого мы напишем маленькую функцию-хелпер, которая будет генерировать «публичную» версию задачи, отправляемую клиенту:
Когда мы возвращаем список задач мы прогоняем все задачи через эту функцию, прежде чем отослать клиенту:
Теперь клиент получает вот такой список задач:
Применив эту технику к остальным функциям мы сможем гарантировать, что клиент всегда получит URI, вместо id.
Защита RESTful веб-сервиса
Вы думали мы уже закончили? Конечно, мы закончили с функциональностью нашего сервиса, но у нас есть проблема. Наш сервис открыт для всех, а это не очень хорошо.
У нас есть законченый веб-сервис, который управляет нашим списком дел, но сервис, в текущем его состоянии, доступен каждому. Если незнакомец выяснит как работает наше API он или она может написать новый клиент и навести беспорядок в наших данных.
Многие руководства для начинающих игнорируют безопасность и заканчиваются здесь. По-моему это серьезная проблема, которая всегда должна быть решена.
Простой путь защитить наш веб-сервис это пускать клиентов после авторизации по логину и паролю. В обычном веб-приложении вы должны сделать форму логина, которая отправляет данные авторизации, сервер обрабатывает их и делает новую сессию, а браузер пользователя получает куки с идентификатором сессии. К сожаление здесь мы такое сделать не можем, stateless — одно из правил построения REST веб-сервисов и мы должны просить клиентов отправлять свои регистрационные данные при каждом запросе.
С REST мы всегда стараемся придерживаться протокола HTTP настолько, насколько сможем. Сейчас нам нужно реализовать аутентификацию пользователя в контексте HTTP, который предоставляет нам 2 варианта — Basic и Digest.
Существует маленькое расширение Flask написанное вашим покорным слугой. Давайте установим Flask-HTTPAuth:
Функция get_password будет по имени пользователя возвращать пароль. В более сложных системах такая функцию должна будет лезть в базу, но для одного пользователя это не обязательно.
Функция error_handler будет использоваться чтобы отправить ошибку авторизации, при неправильных данных. Так же как мы поступили с другими ошибками мы должны настроить функцию на отправку JSON, вместо HTML.
После настройки системы аутентификаци, осталось только добавить декоратор @auth.login_required для всех функций, которые должны быть защищены. Например:
Если мы попробуем запросить эту функцию с помощью curl мы получим примерно следующее:
Для того, чтобы вызвать эту функцию, мы должны подтвердить наши полномочия:
Расширение с аутентификацией дает нам свободу выбирать какие функции будут в общем доступе, а какие защищены.
К сожалению веб-браузеры имеют дурную привычку показывать страшное диалоговое окно, когда запрос возвращается с ошибкой 401. Это происходит даже для фоновых запросов, так что если бы мы реализовали клиента для веб-браузера, нам пришлось бы прыгать через обручи, чтобы не давать браузеру показывать свои окна.
Простой путь обмануть браузер — возвращать любой другой код, вместо 401. Любимая всеми альтернатива это код 403, который означает ошибку «Forbidden». Хоть это достаточно близкая по смыслу ошибка, это нарушает стандарт HTTP, так что это неправильно. В частности будет хорошим решением не использовать веб-браузер в качестве клиентского приложения. Но в случаях, когда сервер и клиент разрабатываются совместно это спасает от многих неприятностей. Чтобы провернуть этот трюк нам нужно просто заменить код ошибки с 401 на 403:
В клиентском приложении нужно тоже отлавливать ошибку 403.
Возможные улучшения
Есть несколько возможностей улучшить разработанный нами сегодня веб-сервис.
Для начала, настоящий веб-сервис должен общаться с настоящей базой данных. Структура данных в памяти очень ограниченный способ хранения данных и он не должен использоваться в реальных приложениях.
Другой способ улучшить приложение это поддержка нескольких пользователей. Если система поддерживает несколько пользователей, то данные аутентификации могут использоваться чтобы возвращать персональные списки пользователям. В такой системе пользователи станут вторым ресурсом. Запрос POST будет регистрировать нового пользователя в системе. Запрос GET может возвращать информацию о пользователе. Запрос PUT может обновлять информацию о пользователе, например email. Запрос DELETE будет удалять пользователя из системы.
Вывод
Законченый код для веб-сервиса To Do List вы можете взять здесь: https://gist.github.com/miguelgrinberg/5614326.
Я верю что это было простое и дружелюбное введение в RESTful API. Если есть достаточный инетерес я мог бы написать вторую часть этой статьи, в которой мы разработаем простой веб-клиент для нашего сервиса.
Я сделал клиента для нашего сервиса:Writing a Javascript REST client.
Статья о таком же сервере, но с использованием Flask-RESTfulDesigning a RESTful API using Flask-RESTful.