google-adwords-api 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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