Лучшие практики работы с API в реальном мире
Делать запросы к API в Python очень просто. Сделать это надежным, чистым и масштабируемым способом — совсем другая история.
вступление
Python прославился за последние 5–10 лет и в настоящее время является одним из самых популярных языков программирования в мире. Его легко освоить из-за его высокоуровневого (в стиле псевдокода) синтаксиса, и это позволяет легко начать писать действительно полезные скрипты. Для новичка это действительно мощно и заставляет вас чувствовать, что вы создаете ценность на раннем этапе своего учебного процесса.
Одна большая проблема, которую я вижу в этом, заключается в том, что передовой опыт часто упускается из виду вместо того, чтобы просто создавать что-то, что «работает».
Конечно, вы можете запустить свой скрипт в изолированной среде (на вашем ноутбуке), но что произойдет, когда он разрастется, и вы захотите начать его развертывание в облачной функции или в виде контейнера Docker?
Если мы не думаем о том, как создавать «хороший» (субъективный) код с самого начала, мы создаем головную боль для себя в будущем или других, которые должны взять на себя ответственность.
Одна вещь, которую я заметил во время своего опыта изучения Python, заключается в том, что очень немногие ресурсы проходят через «реальные» приложения использования языка в Data Engineering. Существует множество ресурсов для изучения основ (условия, классы, функции, циклы и т. д.). Есть и другие, которые показывают, как вы применяете их в действии, на базовых примерах (создание класса Animal или Vehicle и создание некоторых экземпляров). Некоторые более продвинутые показывают, как использовать Python для создания веб-приложений или игр или множества других забавных вещей. Но ни один из них не является особенно полезным с точки зрения Data Engineering.
Это первая статья из довольно забавной серии реальных примеров приложений Python, вы можете увидеть их все здесь:
Фон
Хорошее место для начала — работа с API. Это основа большей части того, что мы делаем в Data Engineering. Есть некоторые данные, которые существуют для части программного обеспечения или третьей стороны, которые нам нужно извлечь, и нам предоставляется документация о том, как их использовать.
В этом примере мы будем использовать API Рика и Морти…
Большинство «хороших» API для извлечения данных имеют общий набор функций:
- Документация, чтобы рассказать вам, как с ним взаимодействовать — Документация Rick and Morty API
- Определенная схема для результатов
- Общее количество записей для объекта или конечной точки
- Способ постраничного просмотра результатов
- Метод фильтрации результатов
- Аутентификация — мы не будем рассматривать это в этом руководстве, потому что API Рика и Морти открыто, я оставлю это для другого поста.
Методология
Давайте разберем процесс, который мы будем использовать для успешного завершения этого проекта:
- Используйте классы данных для обработки схемы возвращаемых значений.
- Используйте классы данных для обработки схемы ввода параметров для API.
- Создайте функцию для взаимодействия с API
- Создайте функцию для разбиения на страницы всех результатов API.
Перед тем, как мы начнем, немного подготовим настройки среды:
- Я использую WSL2 в Windows 22H2.
- Я буду реализовывать это в Python 3.8.
- Управление зависимостями будет осуществляться через
pipenv
, единственное требование для этого —requests
иblack
как зависимость разработчика для автоформатирования (еще одна тема для обсуждения).
Вы можете прочитать больше о Pipenv и его настройке здесь:
Теперь давайте начнем! 🙌
I. Классы данных для результатов API
Я буду использовать конечную точку «Персонаж», чтобы продемонстрировать все в этом руководстве, которое содержит 826 записей на 42 страницах.
Первое, что мы сделаем, это обработаем структуру API.
Нам говорят, что общая структура ответа (ApiResponse) имеет два поля: info
(ApiInfo) и results
. Это список символов, и эта схема нам дана в документации (CharacterSchema).
Давайте продолжим и превратим всю эту информацию в классы данных в Python.
Мы создадим файл models.py
, чтобы отделить определения нашей модели от остальной части проекта.
Этот сегмент кода показывает импорт, который мы будем использовать для всего файла (Optional
не используется в CharacterSchema
, но нам понадобится позже).
Мы преобразовали схему документации в класс данных Python.
Теперь давайте обработаем сам ответ API (все еще в пределах models.py
).
Порядок, в котором мы указываем классы данных, здесь важен, потому что нам нужно ссылаться на ApiInfo
внутри ApiResponse
, если мы поместим ApiInfo
последним, мы получим сообщение об ошибке, сообщающее нам, что он не определен.
Метод __post_init__
— это удобный способ применить постобработку к объекту при его первом создании.
В этом случае мы делаем две вещи. После создания объекта ApiResponse
:
- Распакуйте значения, предоставленные для
info
(которые, как мы знаем, будут словарем), чтобы создать объектApiInfo
, и перезапишите значениеinfo
внутриApiResponse
. - Используйте генератор списков, чтобы создать список объектов
CharacterSchema
из исходного спискаresults
и перезаписать значениеresults
в пределахApiResponse
.
II. Классы данных для ввода API
Нам дан список полей, которые мы можем использовать для фильтрации результатов API. Если мы превратим это в другой класс данных, то у нас будет все в удобном виде, если мы решим их использовать.
В дополнение к фильтруемым полям есть дополнительный параметр page
, который используется для нумерации страниц.
Это последний раздел models.py
.
Все эти параметры установлены как Optional
(это означает, что ни один из них или все они могут использоваться в одном запросе) со значением по умолчанию None
.
Например, даже с page
для первоначального запроса к конечной точке нам не нужно указывать страницу, но когда мы начнем разбивку на страницы, мы предоставим значение.
III. Разбивка на страницы через данные API
Первое, что нам нужно настроить, — это способ отправки запроса данных API.
Давайте создадим файл с именем ramapi.py
, который выглядит так:
Это простая, но изящная функция, которую мы можем использовать, чтобы делать все, что нам нужно, с Rick and Morty API. Он принимает значение endpoint
(мы используем «Character» для этого урока, но мы можем расширить его на любой другой, который работает таким же образом) и значение params
, которое является экземпляром нашего объекта ApiParameters
.
Несмотря на то, что параметры не являются обязательными, вместо того, чтобы сделать их необязательными в функции, а затем обрабатывать логику того, что мы делаем, когда они предоставлены/не предоставлены, я сделал его обязательным аргументом, который будет иметь больше смысла, когда мы посмотрим в следующей части кода.
Вызов функции raise_for_status()
означает, что если произойдет что-либо, кроме успешного запроса, у нас будет возбуждено исключение.
Это заключительная часть проекта, которая объединяет все вместе, main.py
.
Здесь у нас есть одна функция с именем get_all_paginated_results()
и блок выполнения внизу, который запускается, когда мы вызываем файл. Вы можете видеть, что эта функция создает пустой список, перебирает все страницы, устанавливает значение params.page
в номер страницы, а затем делает запрос и сохраняет результаты в списке. Наконец, он возвращает все результаты.
Порядок обработки такой:
- Создайте объект параметров — это может показаться излишним в этом примере, потому что он пустой, но, указав его здесь, он распространится на случай, когда требуется ввод параметра без какого-либо изменения порядка обработки. Кроме того, нам нужны параметры для функции разбивки на страницы, поэтому имеет смысл иметь ее здесь.
- Отправьте первоначальный запрос и верните ответ — это используется, чтобы мы могли получить общее количество страниц перед разбиением на страницы.
- Запустите функцию пагинации — используя количество страниц из предыдущего вызова и передав параметры.
Когда мы запускаем файл main.py
, вывод показан ниже, поэтому мы можем видеть, что мы правильно вернули точное количество страниц и общее количество записей, которые мы видели ранее.
Заключительные мысли 💭
Итак, у нас есть полный рабочий пример того, как вы можете работать с REST API для получения данных, используя классы данных и разбивку на страницы.
Я ясно дал понять, что мы обрабатываем только первую часть процесса — получение данных. В реальном мире вы хотите что-то с этим сделать. Это может быть сохранение в файл или, в более реалистичном сценарии, сохранение в базе данных (или в Google Cloud Storage в виде BLOB).
В следующей части этой серии будет рассмотрен практический пример того, как именно вы могли бы это сделать! Прочтите это здесь:
Не стесняйтесь подписаться на Medium и LinkedIn, чтобы получить больше похожего контента, и обращайтесь с любыми вопросами, я люблю общаться с новыми людьми :)
Наконец, если у вас есть какие-либо пожелания или идеи о других вещах, которые я мог бы выполнить в аналогичном формате учебника, напишите их в комментариях ниже, и я решу, какие темы я могу осветить!