Обработка ошибки 152 «Недостаточно баллов»

Python версии 2 или 3, формат JSON, используется библиотека requests

Баллы API Директа версии 5 начисляются всем рекламодателям один раз в 60 минут, но для каждого рекламодателя используется собственное расписание начисления баллов без привязки к началу астрономического часа. Пример демонстрирует логику повторного выполнения запросов, если первый запрос завершается ошибкой с кодом 152.

Для демонстрации используется функция sleep, которая позволяет устанавливать задержки выполнения скрипта. Задержки задаются в массиве delays в секундах, так чтобы с момента первого запуска было выполнено не более 5 повторных запросов с увеличивающимися интервалами, при этом были покрыты ближайшие 60 минут с момента первого получения ошибки.

# -*- coding: utf-8 -*-
import sys
from time import sleep

import json
import requests

if sys.version_info < (3,):
    def u(x):
        try:
            return x.encode("utf8")
        except UnicodeDecodeError:
            return x
else:
    def u(x):
        if type(x) == type(b''):
            return x.decode('utf8')
        else:
            return x

# --- Настройки ---
# Вывод отладочной информации
debug = True

# Массив интервалов задержек
delays = [360, 540, 720, 900, 1080]

# Ошибка "Недостаточно баллов"
notEnoughUnitsError = "152"

# --- Входные данные запроса ---
# Адрес сервиса Campaigns для отправки JSON-запросов (регистрозависимый)
CampaignsURL = 'https://api.direct.yandex.com/json/v5/campaigns'

# OAuth-токен пользователя, от имени которого будут выполняться запросы
token = 'ТОКЕН'

# Логин клиента рекламного агентства
# Обязательный параметр, если запросы выполняются от имени рекламного агентства
clientLogin = 'ЛОГИН_КЛИЕНТА'

# --- Подготовка запроса ---
# Создание HTTP-заголовков запроса
headers = {
    "Authorization": "Bearer " + token,     # OAuth-токен. Использование слова Bearer обязательно
    "Client-Login": clientLogin,            # Логин клиента рекламного агентства
    "Accept-Language": "ru",                # Язык ответных сообщений
}

# Создание тела запроса
body = {
    "method": "get",                        # Используемый метод
    "params": {
        "SelectionCriteria": {},            # Критерий отбора кампаний. Для получения всех кампаний должен быть пустым
        "FieldNames": ["Id", "Name"]        # Имена параметров, которые требуется получить
    }
}

# Преобразование входных параметров запроса в формат JSON
jsonBody = json.dumps(body, ensure_ascii=False).encode('utf8')

# --- Выполнение задачи ---
# Запуск цикла для выполнения запросов. Если первый запрос завершен успешно, т.е. не привел к возникновению ошибки 152,
# то выводится список кампаний.
# Если же первый запрос завершен с этой ошибкой, то выполняются повторные запросы с задержками,
# заданными в массиве delays.
for delay in delays:
    try:
        # Выполнение запроса, получение результата
        result = requests.post(CampaignsURL, jsonBody, headers=headers)

        # Отладочная информация
        if debug:
            print("Заголовки запроса: {}".format(result.request.headers))
            print("Запрос: {}".format(u(result.request.body)))
            print("Заголовки ответа: {}".format(result.headers))
            print("Ответ: {}".format(u(result.text)))
            print("\n")

        # Обработка запроса
        if result.status_code == 200:
            # Если получен HTTP-код 200, то обрабатываем тело ответа

            # Вывод RequestId и информации о баллах
            print("RequestId: {}".format(result.headers.get("RequestId", False)))
            print("Информация о баллах: {}".format(result.headers.get("Units", False)))

            # Если в результирующих данных не содержится первичного ключа error, значит, запрос был выполнен успешно
            if not result.json().get("error", False):
                # Вывод списка кампаний
                for campaign in result.json()["result"]["Campaigns"]:
                    print("Рекламная кампания: {} №{}".format(u(campaign['Name']), campaign['Id']))

                # Если ответ содержит параметр LimitedBy, значит, были получены не все доступные объекты.
                if result.json()['result'].get('LimitedBy', False):
                    # В этом случае следует выполнить дополнительные запросы для получения всех объектов.
                    # Подробное описание постраничной выборки - https://tech.yandex.ru/direct/doc/dg/best-practice/get-docpage/#page
                    print("Получены не все доступные объекты.")

                # Принудительный выход из цикла
                break

            # Обработка ошибок запроса к серверу API Директа
            elif result.json().get("error", False):
                print("Произошла ошибка при обращении к серверу API Директа.")
                print("Код ошибки: {}".format(result.json()["error"]["error_code"]))
                print("Описание ошибки: {}".format(u(result.json()["error"]["error_detail"])))
                if result.json()['error'].get('error_code', 0) == notEnoughUnitsError:
                    # Недостаточно баллов для выполнения запроса
                    print("Повторный запрос через {} секунд".format(delay))
                    # Задержка перед выполнением следующего запроса
                    sleep(delay)

        # Обработка других ошибок
        else:
            print("Произошла ошибка при обращении к серверу API Директа.")
            print("HTTP-код ошибки: {}".format(u(result.status_code)))
            # Здесь вы можете описать действия, которые следует выполнить при возникновении ошибки HTTP-запроса

    # Обработка ошибки, если не удалось соединиться с сервером API Директа
    except ConnectionError:
        # В данном случае мы рекомендуем повторить запрос позднее
        print("Произошла ошибка соединения с сервером API.")
        # Принудительный выход из цикла
        break


    # Если возникла какая-либо другая ошибка
    except:
        # В данном случае мы рекомендуем проанализировать действия приложения
        print("Произошла непредвиденная ошибка.")
        # Принудительный выход из цикла
        break