Skip to content

Commit 2c6d43b

Browse files
committed
Added ad customizer example.
1 parent f9b858d commit 2c6d43b

File tree

4 files changed

+275
-18
lines changed

4 files changed

+275
-18
lines changed
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
#!/usr/bin/python
2+
#
3+
# Copyright 2014 Google Inc. All Rights Reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
"""Adds an ad customizer feed.
18+
19+
Associates the feed with customer and adds an ad that
20+
uses the feed to populate dynamic data.
21+
22+
Tags: CustomerFeedService.mutate, FeedItemService.mutate
23+
Tags: FeedMappingService.mutate, FeedService.mutate
24+
Tags: AdGroupAdService.mutate
25+
"""
26+
27+
__author__ = ('[email protected] (Mark Saniscalchi)',
28+
'[email protected] (Yufeng Guo)')
29+
30+
# Import appropriate classes from the client library.
31+
from googleads import adwords
32+
33+
# See the Placeholder reference page for a list of all the placeholder types
34+
# and fields:
35+
# https://developers.google.com/adwords/api/docs/appendix/placeholders
36+
PLACEHOLDER_AD_CUSTOMIZER = '10'
37+
PLACEHOLDER_FIELD_INTEGER = '1'
38+
PLACEHOLDER_FIELD_FLOAT = '2'
39+
PLACEHOLDER_FIELD_PRICE = '3'
40+
PLACEHOLDER_FIELD_DATE = '4'
41+
PLACEHOLDER_FIELD_STRING = '5'
42+
43+
ADGROUPS = [
44+
'INSERT_ADGROUP_ID_HERE',
45+
'INSERT_ADGROUP_ID_HERE'
46+
]
47+
48+
FEEDNAME = 'INSERT_FEED_NAME_HERE'
49+
50+
51+
def main(client, adgroups):
52+
# Initialize appropriate services.
53+
ad_group_ad_service = client.GetService('AdGroupAdService', version='v201406')
54+
customer_feed_service = client.GetService(
55+
'CustomerFeedService', version='v201406')
56+
feed_item_service = client.GetService('FeedItemService', version='v201406')
57+
feed_mapping_service = client.GetService(
58+
'FeedMappingService', version='v201406')
59+
feed_service = client.GetService('FeedService', version='v201406')
60+
61+
# First, create a customizer feed. One feed per account can be used for all
62+
# ads.
63+
customizer_feed = {
64+
'name': FEEDNAME,
65+
'attributes': [
66+
{'type': 'STRING', 'name': 'Name'},
67+
{'type': 'STRING', 'name': 'Price'},
68+
{'type': 'DATE_TIME', 'name': 'Date'}
69+
]
70+
}
71+
72+
feed_service_operation = {
73+
'operator': 'ADD',
74+
'operand': customizer_feed
75+
}
76+
77+
response = feed_service.mutate([feed_service_operation])
78+
79+
if response and 'value' in response:
80+
feed = response['value'][0]
81+
feed_data = {
82+
'feedId': feed['id'],
83+
'nameId': feed['attributes'][0]['id'],
84+
'priceId': feed['attributes'][1]['id'],
85+
'dateId': feed['attributes'][2]['id']
86+
}
87+
print ('Feed with name \'%s\' and ID %s was added with:'
88+
'\tName attribute ID %s and price attribute ID %s and date attribute'
89+
'ID %s') % (feed['name'], feed['id'], feed_data['nameId'],
90+
feed_data['priceId'], feed_data['dateId'])
91+
else:
92+
raise Exception('No feeds were added')
93+
94+
# Creating feed mapping to map the fields with customizer IDs.
95+
feed_mapping = {
96+
'placeholderType': PLACEHOLDER_AD_CUSTOMIZER,
97+
'feedId': feed_data['feedId'],
98+
'attributeFieldMappings': [
99+
{
100+
'feedAttributeId': feed_data['nameId'],
101+
'fieldId': PLACEHOLDER_FIELD_STRING
102+
},
103+
{
104+
'feedAttributeId': feed_data['priceId'],
105+
'fieldId': PLACEHOLDER_FIELD_PRICE
106+
},
107+
{
108+
'feedAttributeId': feed_data['dateId'],
109+
'fieldId': PLACEHOLDER_FIELD_DATE
110+
}
111+
]
112+
}
113+
114+
feed_mapping_operation = {
115+
'operator': 'ADD',
116+
'operand': feed_mapping
117+
}
118+
119+
response = feed_mapping_service.mutate([feed_mapping_operation])
120+
121+
if response and 'value' in response:
122+
feed_mapping = response['value'][0]
123+
print ('Feed mapping with ID %s and placeholder type %s was saved for feed'
124+
' with ID %s.') % (feed_mapping['feedMappingId'],
125+
feed_mapping['placeholderType'],
126+
feed_mapping['feedId'])
127+
else:
128+
raise Exception('No feed mappings were added.')
129+
130+
# Now adding feed items -- the values we'd like to place.
131+
items_data = [
132+
{
133+
'name': 'Mars',
134+
'price': '$1234.56',
135+
'date': '20140601 000000',
136+
'adGroupId': adgroups[0]
137+
},
138+
{
139+
'name': 'Venus',
140+
'price': '$1450.00',
141+
'date': '20140615 120000',
142+
'adGroupId': adgroups[1]
143+
}
144+
]
145+
146+
feed_items = [{'feedId': feed_data['feedId'],
147+
'adGroupTargeting': {
148+
'TargetingAdGroupId': item['adGroupId']
149+
},
150+
'attributeValues': [
151+
{
152+
'feedAttributeId': feed_data['nameId'],
153+
'stringValue': item['name']
154+
},
155+
{
156+
'feedAttributeId': feed_data['priceId'],
157+
'stringValue': item['price']
158+
},
159+
{
160+
'feedAttributeId': feed_data['dateId'],
161+
'stringValue': item['date']
162+
}
163+
]} for item in items_data]
164+
165+
feed_item_operations = [{
166+
'operator': 'ADD',
167+
'operand': feed_item
168+
} for feed_item in feed_items]
169+
170+
response = feed_item_service.mutate(feed_item_operations)
171+
172+
if response and 'value' in response:
173+
for feed_item in response['value']:
174+
print 'Feed item with ID %s was added.' % feed_item['feedItemId']
175+
else:
176+
raise Exception('No feed items were added.')
177+
178+
# Finally, creating a customer (account-level) feed with a matching function
179+
# that determines when to use this feed. For this case we use the "IDENTITY"
180+
# matching function that is always 'true' just to associate this feed with
181+
# the customer. The targeting is done within the feed items using the
182+
# :campaign_targeting, :ad_group_targeting, or :keyword_targeting attributes.
183+
matching_function = {
184+
'operator': 'IDENTITY',
185+
'lhsOperand': [
186+
{
187+
'xsi_type': 'ConstantOperand',
188+
'type': 'BOOLEAN',
189+
'booleanValue': 'true'
190+
}
191+
]
192+
}
193+
194+
customer_feed = {
195+
'feedId': feed_data['feedId'],
196+
'matchingFunction': matching_function,
197+
'placeholderTypes': [PLACEHOLDER_AD_CUSTOMIZER]
198+
}
199+
200+
customer_feed_operation = {
201+
'operator': 'ADD',
202+
'operand': customer_feed
203+
}
204+
205+
response = customer_feed_service.mutate([customer_feed_operation])
206+
207+
if response and 'value' in response:
208+
feed = response['value'][0]
209+
print 'Customer feed with ID %s was added.' % feed['feedId']
210+
else:
211+
raise Exception('No customer feeds were added.')
212+
213+
# All set! We can now create ads with customizations.
214+
text_ad = {
215+
'xsi_type': 'TextAd',
216+
'headline': 'Luxury Cruise to {=%s.Name}' % FEEDNAME,
217+
'description1': 'Only {=%s.Price}' % FEEDNAME,
218+
'description2': 'Offer ends in {=countdown(%s.Date)}!' % FEEDNAME,
219+
'url': 'http://www.example.com',
220+
'displayUrl': 'www.example.com'
221+
}
222+
223+
# We add the same ad to both ad groups. When they serve, they will show
224+
# different values, since they match different feed items.
225+
operations = [{
226+
'operator': 'ADD',
227+
'operand': {
228+
'adGroupId': adgroup,
229+
'ad': text_ad
230+
}
231+
} for adgroup in adgroups]
232+
233+
print operations
234+
235+
response = ad_group_ad_service.mutate(operations)
236+
237+
print '===ad group ad service==='
238+
print response
239+
240+
if response and 'value' in response:
241+
for ad in response['value']:
242+
print ('\tCreated an ad with ID \'%s\', type \'%s\', and status \'%s\'.'
243+
% (ad['ad']['id'], ad['ad']['Ad.Type'], ad['status']))
244+
else:
245+
raise Exception('No ads were added.')
246+
247+
248+
if __name__ == '__main__':
249+
# Initialize client object.
250+
adwords_client = adwords.AdWordsClient.LoadFromStorage()
251+
main(adwords_client, ADGROUPS)

googleads/adwords.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,13 @@ def __init__(self, adwords_client, version=sorted(_SERVICE_MAP.keys())[-1],
402402
(self._REPORT_DEFINITION_NAME, self._namespace)]
403403
self._marshaller = suds.mx.literal.Literal(schema)
404404

405+
def _DownloadReportCheckFormat(self, file_format, output):
406+
if(file_format.startswith('GZIPPED_')
407+
and not 'b' in getattr(output, 'mode', 'w')):
408+
raise googleads.errors.GoogleAdsValueError('Need to specify a binary'
409+
' output for GZIPPED formats.')
410+
411+
405412
def DownloadReport(self, report_definition, output=sys.stdout,
406413
return_money_in_micros=None):
407414
"""Downloads an AdWords report using a report definition.
@@ -428,11 +435,7 @@ def DownloadReport(self, report_definition, output=sys.stdout,
428435
AdWordsReportError: if the request fails for any other reason; e.g. a
429436
network error.
430437
"""
431-
if (report_definition['downloadFormat'].startswith('GZIPPED_')
432-
and getattr(output, 'mode', 'w') != 'wb'):
433-
raise googleads.errors.GoogleAdsValueError('Need to specify a binary'
434-
' output for GZIPPED formats.')
435-
438+
self._DownloadReportCheckFormat(report_definition['downloadFormat'], output)
436439
self._DownloadReport(self._SerializeReportDefinition(report_definition),
437440
output, return_money_in_micros)
438441

@@ -464,11 +467,7 @@ def DownloadReportWithAwql(self, query, file_format, output=sys.stdout,
464467
AdWordsReportError: if the request fails for any other reason; e.g. a
465468
network error.
466469
"""
467-
if (file_format.startswith('GZIPPED_')
468-
and getattr(output, 'mode', 'w') != 'wb'):
469-
raise googleads.errors.GoogleAdsValueError('Need to specify a binary'
470-
' output for GZIPPED formats.')
471-
470+
self._DownloadReportCheckFormat(file_format, output)
472471
self._DownloadReport(self._SerializeAwql(query, file_format), output,
473472
return_money_in_micros)
474473

googleads/oauth2.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,15 @@ def CreateHttpHeader(self):
113113
try:
114114
request = urllib2.Request(self._GOOGLE_OAUTH2_ENDPOINT, post_body,
115115
self._OAUTH2_REFRESH_HEADERS)
116+
116117
if self.https_proxy:
117-
request.set_proxy(self.https_proxy, 'https')
118+
proxy_support = urllib2.ProxyHandler({'https': self.https_proxy})
119+
opener = urllib2.build_opener(proxy_support)
120+
else:
121+
opener = urllib2.build_opener()
122+
118123
# Need to decode the content - in Python 3 it's bytes, not a string.
119-
content = urllib2.urlopen(request).read().decode()
124+
content = opener.open(request).read().decode()
120125
self._oauthlib_client.parse_request_body_response(content)
121126
_, oauth2_header, _ = self._oauthlib_client.add_token(self._TOKEN_URL)
122127
except Exception, e:

tests/oauth2_test.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,16 @@ def testCreateHttpHeader_refresh(self):
7373
('unused', header, 'unusued')]
7474
self.oauthlib_client.prepare_refresh_body.return_value = post_body
7575

76-
with mock.patch(URL_REQUEST_PATH + '.urlopen') as mock_urlopen:
76+
with mock.patch(URL_REQUEST_PATH + '.build_opener') as mock_opener:
7777
with mock.patch(URL_REQUEST_PATH + '.Request') as mock_request:
78-
mock_urlopen.return_value = fake_request
78+
mock_opener.return_value.open.return_value = fake_request
7979
returned_header = self.googleads_client.CreateHttpHeader()
8080

8181
mock_request.assert_called_once_with(
8282
mock.ANY, post_body if PYTHON2 else bytes(post_body, 'utf-8'),
8383
mock.ANY)
84-
mock_urlopen.assert_called_once_with(mock_request.return_value)
84+
mock_opener.return_value.open.assert_called_once_with(
85+
mock_request.return_value)
8586
self.assertEqual(header, returned_header)
8687
self.oauthlib_client.parse_request_body_response.assert_called_once_with(
8788
content)
@@ -95,15 +96,16 @@ def testCreateHttpHeader_refreshFails(self):
9596
self.oauthlib_client.add_token.side_effect = oauth2.TokenExpiredError()
9697
self.oauthlib_client.prepare_refresh_body.return_value = post_body
9798

98-
with mock.patch(URL_REQUEST_PATH + '.urlopen') as mock_urlopen:
99+
with mock.patch(URL_REQUEST_PATH + '.build_opener') as mock_opener:
99100
with mock.patch(URL_REQUEST_PATH + '.Request') as mock_request:
100-
mock_urlopen.side_effect = error
101+
mock_opener.return_value.open.side_effect = error
101102
self.assertEqual({}, self.googleads_client.CreateHttpHeader())
102103

103104
mock_request.assert_called_once_with(
104105
mock.ANY, post_body if PYTHON2 else bytes(post_body, 'utf-8'),
105106
mock.ANY)
106-
mock_urlopen.assert_called_once_with(mock_request.return_value)
107+
mock_opener.return_value.open.assert_called_once_with(
108+
mock_request.return_value)
107109
self.assertFalse(self.oauthlib_client.parse_request_body_response.called)
108110
self.oauthlib_client.add_token.assert_called_once_with(mock.ANY)
109111

0 commit comments

Comments
 (0)