SOAP (Suds library)
Creating and modifying campaigns, ads and keywords in Python 2.7 using the Suds 0.4 GA library.
The Suds library analyzes the WSDL description and provides an interface based on the SOAP protocol. The library creates input data structures that the application then fills in and passes to the server.
Sample application: app-python-oauth.py. A step-by-step tour of the application is provided below.
1. Connect Suds and configure logging
# -*- coding: utf_8 -*-
from suds.client import Client
from suds.cache import DocumentCache
from suds.sax.element import Element
from suds import WebFault
The Client
class (suds.client
module) analyzes the WSDL description, creates data structures, sends SOAP packets and parses responses. This is the main class for interacting with the API. The DocumentCache
class (suds.cache
module) provides caching for the WSDL description.
The Element
class (suds.sax.element
module) is for adding arbitrary headers to SOAP packets. SOAP headers are used for passing metadata and data to the server for OAuth authorization.
The WebFault
class is an exception that is generated when errors occur on the side of the API server. Catching this exception allows you to distinguish between server errors and errors on the application side.
Logging parameters are shown below.
import logging
logging.basicConfig(level=logging.INFO)
if __debug__:
logging.getLogger('suds.client').setLevel(logging.DEBUG)
else:
logging.getLogger('suds.client').setLevel(logging.CRITICAL)
When running the Python interpreter with the -O flag (optimized mode), only messages about critical errors are output. When running it without this flag, debugging messages about sent and received SOAP packets are output, including the packet texts.
2. Plugin for correcting responses
The technique shown below fixes a known API error that causes the data types in a response to be associated with the namespace http://namespaces.soaplite.com/perl instead of the API namespace. Because of this error, Suds creates incorrect output data structures that cannot be reused in API requests. In the next version of the API, the error will be fixed and this technique will no longer be necessary.
from suds.plugin import *
class NamespaceCorrectionPlugin(MessagePlugin):
def received(self, context):
context.reply = context.reply.replace('"http://namespaces.soaplite.com/perl"','"API"')
The received
user function corrects the namespace in API responses before Suds analyzes the responses. The response is passed in the context
parameter as a Unicode string. The response consists of a SOAP message in the same format as it arrived via HTTP.
3. Instance of the suds.Client class
An instance of the suds.Client
class is used for interacting with the API. When creating it, specify the URL of the WSDL description.
api = Client('https://api.direct.yandex.ru/v4/wsdl/', plugins = [NamespaceCorrectionPlugin()])
api.set_options(cache=DocumentCache())
The code shown also connects the plugin that was created in the previous step, and in the last line it includes caching for the WSDL description.
4. Metadata in SOAP packet headers
SOAP packets contain the <Header>
element, which is used for passing metadata to the server. The metadata relates to the request in general, not to the particular method invoked. The following example shows how to pass the locale
parameter to specify the language for response messages.
locale = Element('locale').setText('en')
api.set_options(soapheaders=(locale))
SOAP headers are automatically included in all SOAP packets.
5. OAuth authorization
For OAuth authorization, you must know the access token (token
). It is specified in the SOAP packet headers as metadata.
token = Element('token').setText('e4d3b3d2a74e4fa387a18dda5cd1c8d9')
locale = Element('locale').setText('en')
api.set_options(soapheaders=(token, locale))
6. Function for invoking API methods
The following function calls the specified API method with input parameters.
def directRequest(methodName, params):
'''
Calling a Yandex Direct API method:
api - instance of the suds.Client class
methodName - method name
params - input parameters
If an error occurs the program ends,
otherwise it returns the result of calling the method
'''
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)
If the method is executed successfully, the function returns the data received from the server. The data is presented as a hierarchy of objects, which is created by Suds based on an analysis of the SOAP response. Now we will examine techniques for working with output data.
If an error occurs, the function analyzes the exception object. If it belongs to the WebFault
class, an error message from the server is output. In all other cases, the error is on the application side, and the error message is output with the “Other error:” prefix. For any error, the application exits with the return code -1.
7. Create a campaign
The following code demonstrates how to use a dictionary to formulate input parameters. The params
dictionary contains the minimum required parameters for creating a campaign.
params = {
'CampaignID': 0,
'Login': 'agrom',
'Name': u'Campaign created via the API',
'FIO': 'Alex Gromov',
'Strategy':{
'StrategyName': 'WeeklyBudget',
'WeeklySumLimit': 400,
'MaxPrice': 8,
},
'EmailNotification':{
'MoneyWarningValue':20,
'SendAccNews':'Yes',
'WarnPlaceInterval':60,
'SendWarn':'Yes',
'Email':'agrom@yandex.ru'
},
}
Shown below is a CreateOrUpdateCampaign method call using the directRequest
function that was created earlier. The ID of the created campaign is put in the campaignId
output parameter.
campaignId = directRequest('CreateOrUpdateCampaign', params)
print u'Created campaign ID=' + str(campaignId)
8. Edit the campaign
To edit a campaign, it is a good idea to get its parameters, make the necessary changes, then save the parameters in the API. The GetCampaignsParams method accepts an array of campaign IDs and returns their parameters.
params = {'CampaignIDS': [campaignId]}
campaignsParams = directRequest('GetCampaignsParams', params)
Ways to access output data are shown below.
params = campaignsParams[0]
params['EmailNotification']['Email'] = 'new@email.ru'
params.MinusKeywords = [u'car cooler', u'refrigerator truck']
params.TimeTarget.DaysHours[0].Hours = [6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
The campaignsParams
array contains CampaignInfo objects. Since we have requested parameters for a single campaign, we will be working with the first element of the array, setting its variable with params
.
The second line of code shows that parameters can be accessed in dictionary notation. To do this, specify parameter names in quotation marks inside square brackets. The third and fourth lines demonstrate how to access parameters in object notation, where parameter names are separated by dots.
After making the necessary changes, we save the parameters using theCreateOrUpdateCampaign method.
result = directRequest('CreateOrUpdateCampaign', params)
print u'Modified parameters of the campaign ID=' + str(result)
9. Create an ad and a keyword
To create an ad and a keyword, we will use the Factory
class from the suds
module. It will help us create an input data structure, then we will populate it with values. This method is an alternative to creating dictionaries.
banner = api.factory.create('BannerInfo')
banner.CampaignID = campaignId
banner.BannerID = 0
banner.Title = Refrigerators'
banner.Text = u'Refrigerator sales and repair'
banner.Href = 'http://www.api.ru/banner{param1}?page={param2}'
banner.Geo = '1,10174'
In the first line, the BannerInfo input data structure is created, which contains all the supported parameters. In the following lines, mandatory parameters are set (the keyword can't be created without them).
The input structure created using Factory
should not contain any uninitialized arrays. For this reason, parameters that contain arrays should either be explicitly initialized (for example, by assigning an empty array), or they should be deleted, as shown below.
banner.Sitelinks = []
del(banner.ContactInfo)
del(banner.MinusKeywords)
For the BannerInfo structure, there is a mandatory Phrases
array, which must contain at least one keyword. We will create one and place it in the Phrases
array.
phrase = api.factory.create('BannerPhraseInfo')
phrase.PhraseID = 0
phrase.Phrase = u'refrigerator'
phrase.Price = 1.99
banner.Phrases = [phrase]
In the first line, the BannerPhraseInfo structure is created. In the following lines, mandatory parameters are set for the keyword.
The API lets you set variables for the keyword, which can be automatically substituted in the link to the site if the ad was found using this keyword. How to set these variables is shown below.
userParams = api.factory.create('PhraseUserParams')
userParams.Param1 = ''
userParams.Param2 = 'freezer'
banner.Phrases[0].UserParams = userParams
Now, if the ad is found using the keyword [refrigerator], the link to the site http://www.api.ru/banner{param1}?page={param2} will appear as http://www.api.ru/banner?page=freezer.
Save the ad parameters using the CreateOrUpdateBanners method.
params = [banner]
bannerID = directRequest('CreateOrUpdateBanners', params)[0]
print u'Created ad ID=' + str(bannerID)
If successful, the output parameter bannerId
contains the ID of the created ad.
10. Add a keyword to the ad
To add keywords, it is a good practice to get the current parameters for the ad, including parameters for keywords, then make the necessary changes and save the parameters in the API.
How to get ad parameters is shown below.
params = {'BannerIDS': [bannerID]}
bannerParams = directRequest('GetBanners', params)[0]
The GetBanners method returns an array of BannerInfo objects, each of which contains information about an ad. In our example, we requested information that is located in the bannerParams
variable for a single ad.
Now we create the BannerPhraseInfo object and set the keyword parameters.
phrase = api.factory.create('BannerPhraseInfo')
phrase.PhraseID = 0
phrase.Phrase = u'freezer'
phrase.Price = 1.44
We create user variables for substituting in the link to the site.
phrase.UserParams = api.factory.create('PhraseUserParams')
phrase.UserParams.Param1 = '_2'
phrase.UserParams.Param2 = 'deepfreeze'
Now, if the ad is found using the keyword [freezer], the link to the site http://www.api.ru/banner{param1}?page={param2} will appear as http://www.api.ru/banner_2?page=deepfreeze.
Let's add the keyword to the Phrases
array and save the ad parameters using the CreateOrUpdateBanners method.
bannerParams.Phrases.append(phrase)
params = [bannerParams]
bannerIDS = directRequest('CreateOrUpdateBanners', params)
print u'Modified ad ID=' + str(bannerIDS[0])
11. Call finance methods
When calling the finance method, you must additionally specify the finance token and the transaction number (see Accessing finance methods).
As an example, we will call the CreateInvoice method, which creates an invoice. Below you can see how the finance token is formed. The input data are the master token (obtained from the Yandex Direct interface), the transaction number, the method name, and the username.
import hashlib
masterToken = 'AEgchkX2M3FBL8lU'
operationNum = 119
usedMethod = 'CreateInvoice'
login = 'agrom'
financeToken = hashlib.sha256(masterToken + str(operationNum) + usedMethod + login).hexdigest()
This creates a single-use finance token for calling the CreateInvoice method for the user agrom. An example of the token is shown below.
7215f95e84a766971d8ec4eb5a39ae96505b3a5529a91e5a03e5943565a6e6c7
The finance token and transaction number are passed in the header of the SOAP packet. Below you can see how the parameters are set in the header.
finance_token = Element('finance_token').setText(financeToken)
operation_num = Element('operation_num').setText(operationNum)
api.set_options(soapheaders=(finance_token, operation_num))
Let's create an input data structure and call the CreateInvoice method.
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)
If successful, the method returns the URL to use to get the printable format of the invoice. The only person who can access the invoice is the authorized Yandex user that the request was made on behalf of (in this example, the user "agrom").