google-adwords-api 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,3 +1,6 @@
1
+ 0.4.3:
2
+ - Added example for client-side cross-client reporting.
3
+
1
4
  0.4.2:
2
5
  - Updated Reporting error codes handling.
3
6
  - Fix for old v13 services compatibility.
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Author:: api.dklimkin@gmail.com (Danial Klimkin)
4
+ #
5
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
6
+ #
7
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16
+ # implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+ # This example gets and downloads an Ad Hoc report from a XML report definition.
21
+
22
+ require 'rubygems'
23
+ require 'thread'
24
+
25
+ require 'adwords_api'
26
+ require 'adwords_api/utils'
27
+
28
+ API_VERSION = :v201109
29
+ # Number of parallel threads to spawn.
30
+ THREADS = 10
31
+ # Maximum number of retries for 500 errors.
32
+ MAX_RETRIES = 5
33
+ # Timeout between retries in seconds.
34
+ BACKOFF_FACTOR = 5
35
+
36
+ def parallel_report_download()
37
+ # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
38
+ # when called without parameters.
39
+ adwords = AdwordsApi::Api.new
40
+
41
+ # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
42
+ # the configuration file or provide your own logger:
43
+ # adwords.logger = Logger.new('adwords_xml.log')
44
+
45
+ # Determine list of customer IDs to retrieve report for. For this example we
46
+ # will use ServicedAccountService to get all IDs in hierarchy.
47
+
48
+ serviced_account_srv = adwords.service(:ServicedAccountService, API_VERSION)
49
+
50
+ # Get the account hierarchy for this account.
51
+ selector = {:enable_paging => false}
52
+
53
+ # Run the request at the MCC level.
54
+ graph = adwords.use_mcc() {serviced_account_srv.get(selector)}
55
+
56
+ # Using queue to balance load between threads.
57
+ queue = Queue.new()
58
+
59
+ if graph and graph[:accounts] and !graph[:accounts].empty?
60
+ graph[:accounts].each {|account| queue << account[:customer_id]}
61
+ else
62
+ raise StandardError, 'Can not retrieve any customer ID'
63
+ end
64
+
65
+ # Get report utilities for the version.
66
+ report_utils = adwords.report_utils(API_VERSION)
67
+
68
+ # Define report definition. You can also pass your own XML text as a string.
69
+ report_definition = {
70
+ :selector => {
71
+ :fields => ['CampaignId', 'Id', 'Impressions', 'Clicks', 'Cost'],
72
+ # Predicates are optional.
73
+ :predicates => {
74
+ :field => 'Status',
75
+ :operator => 'IN',
76
+ :values => ['ENABLED', 'PAUSED']
77
+ }
78
+ },
79
+ :report_name => 'Custom ADGROUP_PERFORMANCE_REPORT',
80
+ :report_type => 'ADGROUP_PERFORMANCE_REPORT',
81
+ :download_format => 'CSV',
82
+ :date_range_type => 'LAST_7_DAYS',
83
+ # Enable to get rows with zero impressions.
84
+ :include_zero_impressions => false
85
+ }
86
+
87
+ puts "Retrieving %d reports with %d threads:" % [queue.size, THREADS]
88
+
89
+ reports_succeeded = Queue.new()
90
+ reports_failed = Queue.new()
91
+
92
+ # Creating a mutex to control access to the queue.
93
+ queue_mutex = Mutex.new
94
+
95
+ # Start all the threads.
96
+ threads = (1..THREADS).map do |thread_id|
97
+ Thread.new(report_definition) do |local_def|
98
+ cid = nil
99
+ begin
100
+ cid = queue_mutex.synchronize {(queue.empty?) ? nil : queue.pop(true)}
101
+ if cid
102
+ retry_count = 0
103
+ file_name = "adgroup_%010d.csv" % cid
104
+ puts "[%2d/%d] Loading report for customer ID %s into '%s'..." %
105
+ [thread_id, retry_count,
106
+ AdwordsApi::Utils.format_id(cid), file_name]
107
+ begin
108
+ report_utils.download_report_as_file(local_def, file_name, cid)
109
+ reports_succeeded << {:cid => cid, :file_name => file_name}
110
+ rescue AdwordsApi::Errors::ReportError => e
111
+ if e.http_code == 500 && retry_count < MAX_RETRIES
112
+ retry_count += 1
113
+ sleep(retry_count * BACKOFF_FACTOR)
114
+ retry
115
+ else
116
+ puts(("Report failed for customer ID %s with code %d after %d " +
117
+ "retries.") % [cid, e.http_code, retry_count + 1])
118
+ reports_failed <<
119
+ {:cid => cid, :http_code => e.http_code, :message => e.message}
120
+ end
121
+ end
122
+ end
123
+ end while (cid != nil)
124
+ end
125
+ end
126
+
127
+ # Wait for all threads to finish.
128
+ threads.each { |aThread| aThread.join }
129
+
130
+ puts 'Download completed, results:'
131
+ puts 'Successful reports:'
132
+ while !reports_succeeded.empty? do
133
+ result = reports_succeeded.pop()
134
+ puts "\tClient ID %s => '%s'" %
135
+ [AdwordsApi::Utils.format_id(result[:cid]), result[:file_name]]
136
+ end
137
+ puts 'Failed reports:'
138
+ while !reports_failed.empty? do
139
+ result = reports_failed.pop()
140
+ puts "\tClient ID %s => Code: %d, Message: '%s'" %
141
+ [AdwordsApi::Utils.format_id(result[:cid]),
142
+ result[:http_code], result[:message]]
143
+ end
144
+ puts 'End of results.'
145
+ end
146
+
147
+ if __FILE__ == $0
148
+ begin
149
+ parallel_report_download()
150
+
151
+ # HTTP errors.
152
+ rescue AdsCommon::Errors::HttpError => e
153
+ puts "HTTP Error: %s" % e
154
+
155
+ # API errors.
156
+ rescue AdwordsApi::Errors::ReportError => e
157
+ puts "Reporting Error: %s" % e.message
158
+ end
159
+ end
@@ -41,7 +41,7 @@ module AdwordsApi
41
41
 
42
42
  # Set other constants
43
43
  API_NAME = 'AdwordsApi'
44
- CLIENT_LIB_VERSION = '0.4.2'
44
+ CLIENT_LIB_VERSION = '0.4.3'
45
45
  DEFAULT_CONFIG_FILENAME = 'adwords_api.yml'
46
46
 
47
47
  # Configure the services available to each version
@@ -40,6 +40,7 @@ module AdwordsApi
40
40
  #
41
41
  # Args:
42
42
  # - report_definition: definition of the report in XML text or hash
43
+ # - cid: optional customer ID to run against
43
44
  #
44
45
  # Returns:
45
46
  # - report body
@@ -49,8 +50,8 @@ module AdwordsApi
49
50
  # definition is invalid
50
51
  # - AdwordsApi::Errors::ReportError if server-side error occurred
51
52
  #
52
- def download_report(report_definition)
53
- return get_report_response(report_definition).body
53
+ def download_report(report_definition, cid = nil)
54
+ return get_report_response(report_definition, cid).body
54
55
  end
55
56
 
56
57
  # Downloads a report and saves it to a file.
@@ -58,6 +59,7 @@ module AdwordsApi
58
59
  # Args:
59
60
  # - report_definition: definition of the report in XML text or hash
60
61
  # - path: path to save report to
62
+ # - cid: optional customer ID to run against
61
63
  #
62
64
  # Returns:
63
65
  # - nil
@@ -67,8 +69,8 @@ module AdwordsApi
67
69
  # definition is invalid
68
70
  # - AdwordsApi::Errors::ReportError if server-side error occurred
69
71
  #
70
- def download_report_as_file(report_definition, path)
71
- report_body = download_report(report_definition)
72
+ def download_report_as_file(report_definition, path, cid = nil)
73
+ report_body = download_report(report_definition, cid)
72
74
  save_to_file(report_body, path)
73
75
  return nil
74
76
  end
@@ -83,17 +85,19 @@ module AdwordsApi
83
85
  REPORT_DEFINITION_ORDER = {
84
86
  :root => [:selector, :report_name, :report_type, :date_range_type,
85
87
  :download_format, :include_zero_impressions],
86
- :selector => [:fields, :predicates, :date_range],
87
- :predicates => [:field, :operator, :values]
88
+ :selector => [:fields, :predicates, :date_range, :ordering, :paging],
89
+ :predicates => [:field, :operator, :values],
90
+ :ordering => [:field, :sort_order],
91
+ :paging => [:start_index, :number_results]
88
92
  }
89
93
 
90
94
  # Send POST request for a report and returns Response object.
91
- def get_report_response(report_definition)
95
+ def get_report_response(report_definition, cid = nil)
92
96
  definition_text = get_report_definition_text(report_definition)
93
97
  data = {'__rdxml' => definition_text}
94
98
  url = @api.api_config.adhoc_report_download_url(
95
99
  @api.config.read('service.environment'), @version)
96
- headers = get_report_request_headers(url)
100
+ headers = get_report_request_headers(url, cid)
97
101
  response = AdsCommon::Http.post_response(url, data, @api.config, headers)
98
102
  check_for_errors(response)
99
103
  return response
@@ -113,15 +117,16 @@ module AdwordsApi
113
117
  end
114
118
 
115
119
  # Prepares headers for report request.
116
- def get_report_request_headers(url)
120
+ def get_report_request_headers(url, cid = nil)
117
121
  credentials = @api.credential_handler.credentials
118
122
  auth_handler = @api.get_auth_handler(
119
123
  @api.config.read('service.environment'), @version)
120
124
  auth_string = auth_handler.auth_string(
121
125
  credentials, HTTPI::Request.new(url))
126
+ customer_id = validate_cid(cid || credentials[:clientCustomerId])
122
127
  headers = {
123
128
  'Authorization' => auth_string,
124
- 'ClientCustomerId' => credentials[:clientCustomerId],
129
+ 'ClientCustomerId' => customer_id,
125
130
  'Content-Type' => 'multipart/form-data',
126
131
  'developerToken' => credentials[:developerToken]
127
132
  }
@@ -133,6 +138,20 @@ module AdwordsApi
133
138
  open(path, 'wb') { |file| file.puts(data) } if path
134
139
  end
135
140
 
141
+ # Validates the customer ID specified is correct.
142
+ def validate_cid(cid)
143
+ if (cid.kind_of?(Integer) || (
144
+ cid.kind_of?(String) && (
145
+ ((/\d{3}-\d{3}-\d{4}/ =~ cid) == 0) ||
146
+ ((/\d{10}/ =~ cid) == 0)
147
+ )))
148
+ return cid
149
+ else
150
+ raise AdwordsApi::Errors::BadCredentialsError,
151
+ "Invalid client customer ID: %s" % cid
152
+ end
153
+ end
154
+
136
155
  # Checks downloaded data for error signature. Raises ReportError if it
137
156
  # detects an error.
138
157
  def check_for_errors(response)
@@ -195,8 +214,15 @@ module AdwordsApi
195
214
  def_order = REPORT_DEFINITION_ORDER[name]
196
215
  var_order = def_order.reject {|field| !node.include?(field)}
197
216
  node.keys.each do |key|
198
- if node[key].is_a?(Hash) and REPORT_DEFINITION_ORDER.include?(key)
199
- add_report_definition_hash_order(node[key], key)
217
+ if REPORT_DEFINITION_ORDER.include?(key)
218
+ case node[key]
219
+ when Hash
220
+ add_report_definition_hash_order(node[key], key)
221
+ when Array
222
+ node[key].each do |item|
223
+ add_report_definition_hash_order(item, key)
224
+ end
225
+ end
200
226
  end
201
227
  end
202
228
  node[:order!] = var_order
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 4
8
- - 2
9
- version: 0.4.2
8
+ - 3
9
+ version: 0.4.3
10
10
  platform: ruby
11
11
  authors:
12
12
  - Danial Klimkin
@@ -535,6 +535,7 @@ files:
535
535
  - examples/v201109/get_all_paused_campaigns.rb
536
536
  - examples/v201109/handle_partial_failures.rb
537
537
  - examples/v201109/get_related_keywords.rb
538
+ - examples/v201109/parallel_report_download.rb
538
539
  - examples/v201109/oauth_handling.rb
539
540
  - examples/v201109/add_ads.rb
540
541
  - examples/v201109/get_campaign_alerts.rb