SOAP (библиотека Suds)

Создание и редактирование кампаний, объявлений и фраз на Python 2.7 с применением библиотеки Suds 0.4 GA.

Библиотека Suds анализирует WSDL-описание и обеспечивает взаимодействие по протоколу SOAP. Библиотека создает входные структуры данных, которые приложению остается заполнить и передать на сервер.

Важно. Подсистема сокетов Python должна поддерживать SSL (дистрибутив Python содержит модуль ssl.py).

Пример приложения: app-python-oauth.py. Ниже приведено пошаговое описание приложения.

1. Подключение Suds и настройка логирования

# -*- coding: utf_8 -*-
from suds.client import Client
from suds.cache import DocumentCache
from suds.sax.element import Element
from suds import WebFault

Класс Client (модуль suds.client) анализирует WSDL-описание, создает структуры данных, отправляет SOAP-пакеты и разбирает ответы. Это основной класс для взаимодействия с API. Класс DocumentCache (модуль suds.cache) обеспечивает кеширование WSDL-описания.

Класс Element (модуль suds.sax.element) служит для добавления произвольных заголовков в SOAP-пакеты. С помощью SOAP-заголовков на сервер передаются метаданные и данные для OAuth-авторизации.

Класс WebFault является исключением (exception), генерируемым при ошибках на стороне сервера API. Перехват этого исключения позволяет отличать ошибки на сервере от ошибок на стороне приложения.

Ниже показаны параметры логирования.

import logging
logging.basicConfig(level=logging.INFO)
if __debug__:
    logging.getLogger('suds.client').setLevel(logging.DEBUG)
else:
    logging.getLogger('suds.client').setLevel(logging.CRITICAL)

При запуске интерпретатора Python с ключом -O (оптимизированный режим) в выходной поток помещаются сообщения только о критических ошибках. При запуске без этого ключа выводятся отладочные сообщения об отправке и получении SOAP-пакетов, включая текст пакетов.

2. Плагин для коррекции ответов

Показанная ниже техника исправляет известную ошибку API, в следствие которой типы данных в ответе принадлежат пространству имен http://namespaces.soaplite.com/perl вместо пространства имен API. Из-за этого Suds создает некорректные структуры результирующих данных, которые не могут повторно использоваться в запросах к API. В следующей версии API ошибка будет исправлена и потребность в данной технике исчезнет.

from suds.plugin import *
class NamespaceCorrectionPlugin(MessagePlugin):
    def received(self, context):
        context.reply = context.reply.replace('"http://namespaces.soaplite.com/perl"','"API"')

Пользовательская функция received корректирует пространство имен в ответах API, прежде чем Suds проанализирует ответы. Ответ передается в параметре context как строка unicode. Ответ представляет собой SOAP-сообщение в том виде, как оно пришло по HTTP.

3. Экземпляр класса suds.Client

Экземпляр класса suds.Client служит для взаимодействия с API. При его создании указывают URL, по которому находится WSDL-описание.

api = Client('https://api.direct.yandex.ru/v4/wsdl/', plugins = [NamespaceCorrectionPlugin()])
api.set_options(cache=DocumentCache())

Показанный код также подключает плагин, созданный на предыдущем шаге, а последней строкой включает кеширование WSDL-описания.

4. Метаданные в заголовках SOAP-пакетов

SOAP-пакеты содержат элемент <Header>, в котором на сервер передаются метаданные. Метаданные относятся к запросу в целом, а не к вызываемому методу. Ниже показано, как передать параметр locale, указывающий язык ответных сообщений.

locale = Element('locale').setText('en')
api.set_options(soapheaders=(locale))

SOAP-заголовки автоматически включаются во все SOAP-пакеты.

5. OAuth-авторизация

Для OAuth-авторизации необходимо знать авторизационный токен (token). Его указывают в заголовках SOAP-пакетов как метаданные.

token = Element('token').setText('e4d3b3d2a74e4fa387a18dda5cd1c8d9')
locale = Element('locale').setText('en')
api.set_options(soapheaders=(token, locale))

6. Функция для вызова методов API

Следующая функция вызывает указанный метод API с входными параметрами.

def directRequest(methodName, params):
    '''
    Вызов метода API Яндекс Директа:
       api - экземпляр класса suds.Client
       methodName - имя метода
       params - входные параметры
    В случае ошибки программа завершается,
    иначе возвращается результат вызова метода
    '''
    try:
        result = api.service['APIPort'][methodName](params)
        return result
    except WebFault, err:
        print unicode(err)
    except:
        err = sys.exc_info()[1]
        print 'Other error: ' + str(err)
    exit(-1)

При успешном выполнении метода, функция возвращает полученные с сервера данные. Они имеют вид иерархии объектов, которую создал Suds на основе анализа SOAP-ответа. Далее будут рассмотрены техники работы с результирующими данными.

В случае ошибки функция анализирует объект-исключение. Если он принадлежит классу WebFault, выводится сообщение об ошибке, полученное от сервера. В других случаях ошибка произошла на стороне приложения, и сообщение об ошибке выводится с префиксом «Other error:» При любой ошибке приложение завершается с кодом возврата -1.

7. Создать кампанию

Следующий код демонстрирует использование словарей для формирования входных параметров. Словарь params содержит минимально необходимые параметры для создания кампании.

params = {
   'CampaignID': 0,
   'Login': 'agrom',
   'Name': u'Кампания создана через API',
   'FIO': 'Alex Gromov',
   'Strategy':{
      'StrategyName': 'WeeklyBudget',
      'WeeklySumLimit': 400,
      'MaxPrice': 8,
   },
   'EmailNotification':{
      'MoneyWarningValue':20,
      'SendAccNews':'Yes',
      'WarnPlaceInterval':60,
      'SendWarn':'Yes',
      'Email':'agrom@yandex.ru'
   },
}

Ниже показан вызов метода CreateOrUpdateCampaign с помощью ранее созданной функции directRequest. В результирующий параметр campaignId помещается идентификатор созданной кампании.

campaignId = directRequest('CreateOrUpdateCampaign', params)
print u'Создана кампания ID=' + str(campaignId)

8. Редактировать кампанию

Для редактирование кампании целесообразно получать ее параметры, вносить необходимые изменения и сохранять параметры в API. Метод GetCampaignsParams принимает массив идентификаторов кампаний и возвращает их параметры.

params = {'CampaignIDS': [campaignId]}
campaignsParams = directRequest('GetCampaignsParams', params)

Ниже показаны способы доступа к результирующим данным.

params = campaignsParams[0]
params['EmailNotification']['Email'] = 'new@email.ru'
params.MinusKeywords = [u'автохолодильник', u'рефрижератор'] 
params.TimeTarget.DaysHours[0].Hours = [6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]

Массив campaignsParams содержит объекты CampaignInfo. Поскольку запрошены параметры одной кампании, далее работаем с первым элементом массива, присвоив его переменной params.

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

После внесения необходимых изменений сохраняем параметры с помощью метода CreateOrUpdateCampaign.

result = directRequest('CreateOrUpdateCampaign', params)
print u'Изменены параметры кампании ID=' + str(result)

9. Создать объявление и фразу

Для создания объявления и фразы воспользуемся классом Factory модуля suds. С его помощью создадим структуру входных данных, а затем заполним ее значениями. Этот способ является альтернативой созданию словарей.

banner = api.factory.create('BannerInfo')
banner.CampaignID = campaignId
banner.BannerID = 0
banner.Title = u'Холодильники'
banner.Text = u'Продажа и ремонт бытовых холодильников'
banner.Href = 'http://www.api.ru/banner{param1}?page={param2}'
banner.Geo = '1,10174'

В первой строке создается структура входных данных BannerInfo, которая содержит все предусмотренные параметры. В следующих строках задаются обязательные параметры, без которых фраза не будет создана.

Входная структура, созданная с помощью Factory, не должна содержать неинициализированные массивы. Поэтому параметры, содержащие массивы, нужно либо явно инициализировать, например присвоить пустой массив, либо удалить, как показано ниже.

banner.Sitelinks = []
del(banner.ContactInfo) 
del(banner.MinusKeywords)

Для структуры BannerInfo предусмотрен обязательный массив Phrases, содержащий по меньшей мере одну фразу. Создадим ее и поместим в массив Phrases.

phrase = api.factory.create('BannerPhraseInfo')
phrase.PhraseID = 0
phrase.Phrase = u'холодильник'
phrase.Price = 1.99
banner.Phrases = [phrase]

В первой строке создается структура BannerPhraseInfo. В следующих строках задаются обязательные параметры фразы.

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

userParams = api.factory.create('PhraseUserParams')
userParams.Param1 = ''
userParams.Param2 = 'freezer'
banner.Phrases[0].UserParams = userParams

Теперь, если объявление найдено по фразе [холодильник], ссылка на сайт http://www.api.ru/banner{param1}?page={param2} примет вид http://www.api.ru/banner?page=freezer

Сохраняем параметры объявления с помощью метода CreateOrUpdateBanners.

params = [banner]
bannerID = directRequest('CreateOrUpdateBanners', params)[0]
print u'Создано объявление ID=' + str(bannerID)

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

10. Добавить фразу к объявлению

Для добавления фраз целесообразно получать текущие параметры объявлений, включая параметры фраз, вносить необходимые изменения и сохранять параметры в API.

Ниже показано, как получить параметры объявления.

params = {'BannerIDS': [bannerID]}
bannerParams = directRequest('GetBanners', params)[0]

Метод GetBanners возвращает массив объектов BannerInfo, каждый из которых содержит параметры объявления. В нашем примере запрошены данные одного объявления, которые помещены в переменную bannerParams.

Создаем объект BannerPhraseInfo и задаем параметры фразы.

phrase = api.factory.create('BannerPhraseInfo')
phrase.PhraseID = 0
phrase.Phrase = u'морозильник'
phrase.Price = 1.44

Создаем пользовательские переменные для подстановки в ссылку на сайт.

phrase.UserParams = api.factory.create('PhraseUserParams')
phrase.UserParams.Param1 = '_2'
phrase.UserParams.Param2 = 'deepfreeze'

Теперь, если объявление найдено по фразе [морозильник], ссылка на сайт http://www.api.ru/banner{param1}?page={param2} примет вид http://www.api.ru/banner_2?page=deepfreeze

Добавляем фразу в массив Phrases и сохраняем параметры объявления с помощью метода CreateOrUpdateBanners.

bannerParams.Phrases.append(phrase)
params = [bannerParams]
bannerIDS = directRequest('CreateOrUpdateBanners', params)
print u'Изменено объявление ID=' + str(bannerIDS[0])

11. Вызов финансовых методов

При вызове финансовых методов необходимо дополнительно указывать финансовый токен и номер финансовой операции (см. Доступ к финансовым методам).

Для примера вызовем метод CreateInvoice, который создает счет на оплату. Ниже показано формирование финансового токена. Исходными данными являются: мастер-токен (получают в интерфейсе Яндекс Директа), номер финансовой операции, название метода, логин пользователя.

import hashlib

masterToken  = 'AEgchkX2M3FBL8lU'
operationNum = 119
usedMethod   = 'CreateInvoice'
login        = 'agrom'

financeToken = hashlib.sha256(masterToken + str(operationNum) + usedMethod + login).hexdigest()

Создается финансовый токен для однократного вызова метода CreateInvoice от имени пользователя agrom. Пример токена показан ниже.

7215f95e84a766971d8ec4eb5a39ae96505b3a5529a91e5a03e5943565a6e6c7

Финансовый токен и номер финансовой операции передают в заголовке SOAP-пакета. Ниже показана установка параметров в заголовке.

finance_token = Element('finance_token').setText(financeToken)
operation_num = Element('operation_num').setText(operationNum)
api.set_options(soapheaders=(finance_token, operation_num))
Внимание. При использовании OAuth-авторизации также требуется указать авторизационный токен (token) в заголовке SOAP-пакета.

Создаем структуру входных данных и вызываем метод CreateInvoice.

invoice = api.factory.create('PayCampElement')
invoice.CampaignID = campaignId
invoice.Sum = 150
params = api.factory.create('CreateInvoiceInfo')
params.Payments = []
params.Payments.append(invoice)
url = directRequest('CreateInvoice', params)

В случае успеха метод возвращает URL, по которому можно получить печатную форму счета на оплату. Сделать это может только авторизованный пользователь Яндекса, от лица которого выполнен запрос (в примере — пользователь agrom).