Урок 13 Разработка пакета обёртки для Google API (пакет gargle)


Компания Google разработала сотни полезных сервисов, которыми пользуются миллионы людей по всему миру, большинство этих сервисов предоставляют API, и в этом уроке мы разберёмся как обёртывать эти API в пакет. В ходе урока мы разберёмся со специальным пакетом gargle, который очень упрощает разработку пакетов взаимодействующих с Google API.


Данный урок основан на статье "How to use gargle for auth in a client package" под автортством Дженни Брайан.


13.1 Видео

13.1.1 Тайм коды

00:00 Вступление
00:55 Создание учётных данных в Google Cloud
08:52 Функционал пакета gargle
09:50 Пример работы с пакетом gargle
17:08 Обзор рабочего процесса создания пакета обёртки над Google API
17:34 Интерфейс авторизации
26:08 Объект отвечающий за состояние авторизации в вашем пакете
27:38 Как обращаться к токену для подписи HTTP запросов
29:54 Заключение

13.2 Презентация

13.3 Конспект

13.3.1 Создание учётных данных

Для работы с любым Google API изначально вам необходимо создать учётные данные, делается это в 4 этапа:

  1. Создайте проект в Google Cloud
  2. Настройте экран авторизации (основное меню -> APIs & Sevices -> OAuth Consent Screen)
  3. Создайте учётные данных (основное меню -> APIs & Sevices -> Credentials)
  4. Активируйте нужные вам API (основное меню -> APIs & Sevices -> Library)

Большинство Google API требуют для авторизации OAuth клиент, поэтому зачастую именно тип учётных данных вам надо будет создавать на шаге 3. Для использрвания в вашем коде созданного OAuth клиента скопируйте его Client ID и Client Secret.

Некоторые API позволяют подписывать запросы с помощью API ключа, например Google Maps API, или даже Google Spreadsheets API, если вы хотите получить данные из опубликованной с отрытым доступом таблицы.

13.3.2 Основные функции пакета gargle

  • Авторизация:
  • Компоновка и отправка HTTP запроса:
  • Парсинг полученного ответа:
  • response_process() – парсинг ответа.

Ниже пример кода работы с Google Ads API с помощью пакета gargle:

library(gargle)
library(httr)

# OAuth клиент
app <- oauth_app(
  appname = 'myapp',
  key = 'CLIENT_ID.apps.googleusercontent.com',
  secret = 'CLIENT_SECRET'
)

# Авторизация
cred <- token_fetch(
  scopes  = 'https://www.googleapis.com/auth/adwords',
  app     = app,
  email   = 'me@gmail.com',
  cache   = TRUE
)

# Создаём запрос
req <- request_build(
  method   = "GET",
  path     = 'v14/customers:listAccessibleCustomers',
  token    = cred,
  base_url = "https://googleads.googleapis.com/"
)

# Отправляем запрос
resp <- request_retry(
  req,
  add_headers(
    `developer-token`= "DEVELOPER_TOKEN"
  ), 
  max_tries_total = 5, 
  max_total_wait_time_in_seconds = 10
)

# Парсим ответ
result <- response_process(resp)

В приведённом выше коде вам необходимо подставить полученные ранее учётные данные, т.е. CLIENT_ID и CLIENT_SECRET.

Некоторые Google API, например Google Ads API, требуют от вас дополнительные авторизационные данные, в данном случае developer-token, о таких особенностях вы узнаете из раздела авторизации в справке нужного вам API. В данном случае мы прокидываем токен разработчика в специальном заголовке developer-token внутри функции request_retry().

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

13.3.3 Процесс создания пакета на основе gargle

  1. Добавьте gargle в зависимости вашего пакета в поле Imports.
  2. Создайте файл R/YOURPKG_auth.R.
  3. Создайте внутренний gargle::AuthClass объект для хранения состояния аутентификации.
  4. Определите стандартные функции для интерфейса аутентификации между gargle и вашим пакетом; сделать это в R/YOURPKG_auth.R.
  5. Используйте помощники roxygen от gargle для создания документации для ваших функций аутентификации. Это избавляет вас от необходимости писать документы и вы 6. наследуете стандартные формулировки.
  6. Используйте функции YOURPKG_token() и YOURPKG_api_key() (определенные в стандартном интерфейсе аутентификации), чтобы вставить токен или ключ API в запросы вашего пакета.

Файл R/YOURPKG_auth.R будет содержать интерфейс авторизации вашего пакета, в основе этого интерйеса, как вы уже убедились из первого примера работы с пакетом gargle, будет функция token_fetch(), ниже упрощённый пример её использования внутри пакета googledrive:

# googledrive::
drive_auth <- function(email = gargle::gargle_oauth_email(),
                       path = NULL,
                       scopes = "https://www.googleapis.com/auth/drive",
                       cache = gargle::gargle_oauth_cache(),
                       use_oob = gargle::gargle_oob_default(),
                       token = NULL) {
  # this catches a common error, where the user passes JSON for an OAuth client
  # to the `path` argument, which only expects a service account token
  gargle::check_is_service_account(path, hint = "drive_auth_configure")

  cred <- gargle::token_fetch(
    scopes = scopes,
    client = drive_oauth_client() %||% <BUILT_IN_DEFAULT_CLIENT>,
    email = email,
    path = path,
    package = "googledrive",
    cache = cache,
    use_oob = use_oob,
    token = token
  )
  if (!inherits(cred, "Token2.0")) {
    # throw an informative error here
  }
  .auth$set_cred(cred)
  .auth$set_auth_active(TRUE)

  invisible()
}

13.3.4 Интерфейс авторизации

Наиболее простым способом добавить интерфейс авторизации в свой пакет - просто скопировать его из файла drive_auth.R и немного доработать его под ваш пакет, переназвав функции, и изменив дефолтные значения аргументов package и scopes в функции gargle::token_fetch().

Scopes это набор разрешений, т.е. доступов, которые пользователь будет предоставлять вашего OAuth клиенту. информацию о том, какие именно разрешения нужны будут вашему пакету вы найдёте в справке к нужному вам API, в разделе Scopes. Для примера Google Ads API требует разрешение https://www.googleapis.com/auth/adwords, Google Drive API ttps://www.googleapis.com/auth/drive.

Сам интерфейс автоизации будет состоять из следующих функций:

  • drive_token() - извлекает текущие учетные данные в форме, готовой для включения в HTTP-запросы.
  • drive_auth() - при первом запуске инициирует процесс авторизации через браузер, кешрует полученные учётные данные в файл (cache = TRUE), при последующих запусках получает учётные данные из кеша.
  • drive_deauth() - очищает текущий токен.
  • drive_oauth_client() - возвращается .auth$client.
  • drive_api_key() - возвращается .auth$api_key.
  • drive_auth_configure() - может использоваться для настройки аутентификации.
  • drive_user() - сообщает некоторую информацию о пользователе, связанном с текущим токеном.

13.3.5 Состояние авторизации в ходе сеанса

Авторизация это динамическая сущность, поэтому в вашем пакете должен быть создан объект gargle::AuthState, который будет отвечать за изменения состояния авторизации. Создавать его надо внутри функции .OnLoad() в файле zzz.R:

.onLoad <- function(libname, pkgname) {
  utils::assignInMyNamespace(
    ".auth",
    gargle::init_AuthState(package = "googledrive", auth_active = TRUE)
  )
  
  # other stuff
}

13.3.6 Подпись запросов токеном авторизации

После того, как вы прописали в своём пакете интерфейс авторизации, и добавили объект .auth, для получения из этого объекта самого токена, и подписи запросов используйте функцию типа drive_token(), передав её в аргумент token при компоновке самого запроса с помозью функции request_build(). Ниже пример основной функции компонубщей и отправляющей запросы из пакета googledrive:

# googledrive::
request_generate <- function(endpoint = character(),
                             params = list(),
                             key = NULL,
                             token = drive_token()) {
  ept <- drive_endpoint(endpoint)
  if (is.null(ept)) {
    # throw error about unrecognized endpoint
  }

  ## modifications specific to googledrive package
  params$key <- key %||% params$key %||%
    drive_api_key() %||% <BUILT_IN_DEFAULT_API_KEY>
  if (!is.null(ept$parameters$supportsAllDrives)) {
    params$supportsAllDrives <- TRUE
  }

  req <- gargle::request_develop(endpoint = ept, params = params)
  gargle::request_build(
    path = req$path,
    method = req$method,
    params = req$params,
    body = req$body,
    token = token
  )
}

13.4 Тест