google-adwords-api 0.17.0 → 0.18.0

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.
Files changed (170) hide show
  1. checksums.yaml +5 -13
  2. data/ChangeLog +5 -0
  3. data/README.md +3 -3
  4. data/adwords_api.yml +3 -0
  5. data/examples/v201509/{campaign_management → advanced_operations}/add_complete_campaigns_using_batch_job.rb +10 -16
  6. data/examples/v201509/advanced_operations/add_keywords_using_incremental_batch_job.rb +179 -0
  7. data/examples/v201509/reporting/stream_criteria_report_results.rb +97 -0
  8. data/lib/adwords_api/api_config.rb +0 -86
  9. data/lib/adwords_api/batch_job_utils.rb +124 -51
  10. data/lib/adwords_api/incremental_upload_helper.rb +71 -0
  11. data/lib/adwords_api/report_header_handler.rb +1 -1
  12. data/lib/adwords_api/report_stream.rb +64 -0
  13. data/lib/adwords_api/report_utils.rb +88 -8
  14. data/lib/adwords_api/version.rb +1 -1
  15. data/test/adwords_api/test_batch_job_utils.rb +15 -0
  16. metadata +18 -168
  17. data/examples/v201502/account_management/create_account.rb +0 -88
  18. data/examples/v201502/account_management/get_account_changes.rb +0 -139
  19. data/examples/v201502/account_management/get_account_hierarchy.rb +0 -94
  20. data/examples/v201502/advanced_operations/add_ad_customizers.rb +0 -184
  21. data/examples/v201502/advanced_operations/add_ad_group_bid_modifier.rb +0 -101
  22. data/examples/v201502/advanced_operations/add_click_to_download_ad.rb +0 -133
  23. data/examples/v201502/advanced_operations/add_text_ad_with_upgraded_urls.rb +0 -134
  24. data/examples/v201502/advanced_operations/create_and_attach_shared_keyword_set.rb +0 -133
  25. data/examples/v201502/advanced_operations/find_and_remove_criteria_from_shared_set.rb +0 -166
  26. data/examples/v201502/advanced_operations/get_ad_group_bid_modifiers.rb +0 -102
  27. data/examples/v201502/advanced_operations/upload_offline_conversions.rb +0 -113
  28. data/examples/v201502/advanced_operations/use_shared_bidding_strategy.rb +0 -147
  29. data/examples/v201502/basic_operations/add_ad_groups.rb +0 -140
  30. data/examples/v201502/basic_operations/add_campaigns.rb +0 -139
  31. data/examples/v201502/basic_operations/add_keywords.rb +0 -114
  32. data/examples/v201502/basic_operations/add_text_ads.rb +0 -109
  33. data/examples/v201502/basic_operations/get_ad_groups.rb +0 -102
  34. data/examples/v201502/basic_operations/get_campaigns.rb +0 -97
  35. data/examples/v201502/basic_operations/get_campaigns_with_awql.rb +0 -89
  36. data/examples/v201502/basic_operations/get_keywords.rb +0 -108
  37. data/examples/v201502/basic_operations/get_text_ads.rb +0 -110
  38. data/examples/v201502/basic_operations/pause_ad.rb +0 -88
  39. data/examples/v201502/basic_operations/remove_ad.rb +0 -89
  40. data/examples/v201502/basic_operations/remove_ad_group.rb +0 -85
  41. data/examples/v201502/basic_operations/remove_campaign.rb +0 -87
  42. data/examples/v201502/basic_operations/remove_keyword.rb +0 -94
  43. data/examples/v201502/basic_operations/update_ad_group.rb +0 -85
  44. data/examples/v201502/basic_operations/update_campaign.rb +0 -86
  45. data/examples/v201502/basic_operations/update_keyword.rb +0 -106
  46. data/examples/v201502/campaign_management/add_campaign_labels.rb +0 -82
  47. data/examples/v201502/campaign_management/add_experiment.rb +0 -162
  48. data/examples/v201502/campaign_management/add_keywords_in_bulk.rb +0 -153
  49. data/examples/v201502/campaign_management/get_all_disapproved_ads.rb +0 -97
  50. data/examples/v201502/campaign_management/get_all_disapproved_ads_with_awql.rb +0 -89
  51. data/examples/v201502/campaign_management/get_campaigns_by_label.rb +0 -108
  52. data/examples/v201502/campaign_management/promote_experiment.rb +0 -81
  53. data/examples/v201502/campaign_management/set_ad_parameters.rb +0 -118
  54. data/examples/v201502/campaign_management/set_criterion_bid_modifier.rb +0 -104
  55. data/examples/v201502/campaign_management/validate_text_ad.rb +0 -110
  56. data/examples/v201502/error_handling/handle_partial_failures.rb +0 -130
  57. data/examples/v201502/error_handling/handle_policy_violation_error.rb +0 -141
  58. data/examples/v201502/extensions/add_google_my_business_location_extensions.rb +0 -179
  59. data/examples/v201502/extensions/add_site_links.rb +0 -164
  60. data/examples/v201502/extensions/add_site_links_using_feeds.rb +0 -271
  61. data/examples/v201502/migration/migrate_to_extension_settings.rb +0 -386
  62. data/examples/v201502/migration/upgrade_ad_url.rb +0 -93
  63. data/examples/v201502/misc/create_ad_words_session_without_properties_file.rb +0 -92
  64. data/examples/v201502/misc/get_all_images_and_videos.rb +0 -104
  65. data/examples/v201502/misc/setup_oauth2.rb +0 -84
  66. data/examples/v201502/misc/upload_image.rb +0 -93
  67. data/examples/v201502/misc/use_oauth2_jwt.rb +0 -93
  68. data/examples/v201502/optimization/estimate_keyword_traffic.rb +0 -146
  69. data/examples/v201502/optimization/get_keyword_bid_simulations.rb +0 -95
  70. data/examples/v201502/optimization/get_keyword_ideas.rb +0 -126
  71. data/examples/v201502/remarketing/add_audience.rb +0 -118
  72. data/examples/v201502/remarketing/add_conversion_tracker.rb +0 -100
  73. data/examples/v201502/remarketing/add_rule_based_user_lists.rb +0 -167
  74. data/examples/v201502/reporting/download_criteria_report.rb +0 -85
  75. data/examples/v201502/reporting/download_criteria_report_with_awql.rb +0 -84
  76. data/examples/v201502/reporting/get_report_fields.rb +0 -75
  77. data/examples/v201502/reporting/parallel_report_download.rb +0 -166
  78. data/examples/v201502/shopping_campaigns/add_product_partition_tree.rb +0 -267
  79. data/examples/v201502/shopping_campaigns/add_product_scope.rb +0 -129
  80. data/examples/v201502/shopping_campaigns/add_shopping_campaign.rb +0 -129
  81. data/examples/v201502/shopping_campaigns/get_product_category_taxonomy.rb +0 -115
  82. data/examples/v201502/targeting/add_campaign_targeting_criteria.rb +0 -169
  83. data/examples/v201502/targeting/add_demographic_targeting_criteria.rb +0 -112
  84. data/examples/v201502/targeting/get_campaign_targeting_criteria.rb +0 -106
  85. data/examples/v201502/targeting/get_targetable_languages_and_carriers.rb +0 -89
  86. data/examples/v201502/targeting/lookup_location.rb +0 -108
  87. data/lib/adwords_api/v201502/account_label_service.rb +0 -46
  88. data/lib/adwords_api/v201502/account_label_service_registry.rb +0 -46
  89. data/lib/adwords_api/v201502/ad_customizer_feed_service.rb +0 -46
  90. data/lib/adwords_api/v201502/ad_customizer_feed_service_registry.rb +0 -46
  91. data/lib/adwords_api/v201502/ad_group_ad_service.rb +0 -70
  92. data/lib/adwords_api/v201502/ad_group_ad_service_registry.rb +0 -46
  93. data/lib/adwords_api/v201502/ad_group_bid_modifier_service.rb +0 -54
  94. data/lib/adwords_api/v201502/ad_group_bid_modifier_service_registry.rb +0 -46
  95. data/lib/adwords_api/v201502/ad_group_criterion_service.rb +0 -62
  96. data/lib/adwords_api/v201502/ad_group_criterion_service_registry.rb +0 -46
  97. data/lib/adwords_api/v201502/ad_group_extension_setting_service.rb +0 -54
  98. data/lib/adwords_api/v201502/ad_group_extension_setting_service_registry.rb +0 -46
  99. data/lib/adwords_api/v201502/ad_group_feed_service.rb +0 -54
  100. data/lib/adwords_api/v201502/ad_group_feed_service_registry.rb +0 -46
  101. data/lib/adwords_api/v201502/ad_group_service.rb +0 -62
  102. data/lib/adwords_api/v201502/ad_group_service_registry.rb +0 -46
  103. data/lib/adwords_api/v201502/ad_param_service.rb +0 -46
  104. data/lib/adwords_api/v201502/ad_param_service_registry.rb +0 -46
  105. data/lib/adwords_api/v201502/adwords_user_list_service.rb +0 -46
  106. data/lib/adwords_api/v201502/adwords_user_list_service_registry.rb +0 -46
  107. data/lib/adwords_api/v201502/bidding_strategy_service.rb +0 -54
  108. data/lib/adwords_api/v201502/bidding_strategy_service_registry.rb +0 -46
  109. data/lib/adwords_api/v201502/budget_order_service.rb +0 -54
  110. data/lib/adwords_api/v201502/budget_order_service_registry.rb +0 -46
  111. data/lib/adwords_api/v201502/budget_service.rb +0 -54
  112. data/lib/adwords_api/v201502/budget_service_registry.rb +0 -46
  113. data/lib/adwords_api/v201502/campaign_criterion_service.rb +0 -54
  114. data/lib/adwords_api/v201502/campaign_criterion_service_registry.rb +0 -46
  115. data/lib/adwords_api/v201502/campaign_extension_setting_service.rb +0 -54
  116. data/lib/adwords_api/v201502/campaign_extension_setting_service_registry.rb +0 -46
  117. data/lib/adwords_api/v201502/campaign_feed_service.rb +0 -54
  118. data/lib/adwords_api/v201502/campaign_feed_service_registry.rb +0 -46
  119. data/lib/adwords_api/v201502/campaign_service.rb +0 -62
  120. data/lib/adwords_api/v201502/campaign_service_registry.rb +0 -46
  121. data/lib/adwords_api/v201502/campaign_shared_set_service.rb +0 -46
  122. data/lib/adwords_api/v201502/campaign_shared_set_service_registry.rb +0 -46
  123. data/lib/adwords_api/v201502/constant_data_service.rb +0 -102
  124. data/lib/adwords_api/v201502/constant_data_service_registry.rb +0 -46
  125. data/lib/adwords_api/v201502/conversion_tracker_service.rb +0 -54
  126. data/lib/adwords_api/v201502/conversion_tracker_service_registry.rb +0 -46
  127. data/lib/adwords_api/v201502/customer_extension_setting_service.rb +0 -54
  128. data/lib/adwords_api/v201502/customer_extension_setting_service_registry.rb +0 -46
  129. data/lib/adwords_api/v201502/customer_feed_service.rb +0 -54
  130. data/lib/adwords_api/v201502/customer_feed_service_registry.rb +0 -46
  131. data/lib/adwords_api/v201502/customer_service.rb +0 -46
  132. data/lib/adwords_api/v201502/customer_service_registry.rb +0 -46
  133. data/lib/adwords_api/v201502/customer_sync_service.rb +0 -38
  134. data/lib/adwords_api/v201502/customer_sync_service_registry.rb +0 -47
  135. data/lib/adwords_api/v201502/data_service.rb +0 -78
  136. data/lib/adwords_api/v201502/data_service_registry.rb +0 -46
  137. data/lib/adwords_api/v201502/experiment_service.rb +0 -46
  138. data/lib/adwords_api/v201502/experiment_service_registry.rb +0 -46
  139. data/lib/adwords_api/v201502/feed_item_service.rb +0 -54
  140. data/lib/adwords_api/v201502/feed_item_service_registry.rb +0 -46
  141. data/lib/adwords_api/v201502/feed_mapping_service.rb +0 -54
  142. data/lib/adwords_api/v201502/feed_mapping_service_registry.rb +0 -46
  143. data/lib/adwords_api/v201502/feed_service.rb +0 -54
  144. data/lib/adwords_api/v201502/feed_service_registry.rb +0 -46
  145. data/lib/adwords_api/v201502/geo_location_service.rb +0 -38
  146. data/lib/adwords_api/v201502/geo_location_service_registry.rb +0 -46
  147. data/lib/adwords_api/v201502/label_service.rb +0 -54
  148. data/lib/adwords_api/v201502/label_service_registry.rb +0 -46
  149. data/lib/adwords_api/v201502/location_criterion_service.rb +0 -46
  150. data/lib/adwords_api/v201502/location_criterion_service_registry.rb +0 -46
  151. data/lib/adwords_api/v201502/managed_customer_service.rb +0 -78
  152. data/lib/adwords_api/v201502/managed_customer_service_registry.rb +0 -46
  153. data/lib/adwords_api/v201502/media_service.rb +0 -54
  154. data/lib/adwords_api/v201502/media_service_registry.rb +0 -46
  155. data/lib/adwords_api/v201502/mutate_job_service.rb +0 -54
  156. data/lib/adwords_api/v201502/mutate_job_service_registry.rb +0 -46
  157. data/lib/adwords_api/v201502/offline_conversion_feed_service.rb +0 -38
  158. data/lib/adwords_api/v201502/offline_conversion_feed_service_registry.rb +0 -46
  159. data/lib/adwords_api/v201502/report_definition_service.rb +0 -38
  160. data/lib/adwords_api/v201502/report_definition_service_registry.rb +0 -46
  161. data/lib/adwords_api/v201502/shared_criterion_service.rb +0 -46
  162. data/lib/adwords_api/v201502/shared_criterion_service_registry.rb +0 -46
  163. data/lib/adwords_api/v201502/shared_set_service.rb +0 -46
  164. data/lib/adwords_api/v201502/shared_set_service_registry.rb +0 -46
  165. data/lib/adwords_api/v201502/targeting_idea_service.rb +0 -38
  166. data/lib/adwords_api/v201502/targeting_idea_service_registry.rb +0 -46
  167. data/lib/adwords_api/v201502/traffic_estimator_service.rb +0 -38
  168. data/lib/adwords_api/v201502/traffic_estimator_service_registry.rb +0 -46
  169. data/test/templates/v201502/basic_operations_get_campaigns.def +0 -116
  170. data/test/templates/v201502/misc_use_oauth2_service_account.def +0 -131
@@ -23,6 +23,7 @@ require 'nori'
23
23
  require 'ads_common/http'
24
24
  require 'ads_common/savon_service'
25
25
  require 'adwords_api/errors'
26
+ require 'adwords_api/incremental_upload_helper'
26
27
 
27
28
  module AdwordsApi
28
29
  class BatchJobUtils
@@ -36,73 +37,99 @@ module AdwordsApi
36
37
  @api, @version = api, version
37
38
  end
38
39
 
39
- # Generates SOAP operations from a ruby hash. The hash uses the same format
40
- # as a normal request.
40
+ # Uploads the given operations for a batch job to the provided URL.
41
41
  #
42
42
  # Args:
43
- # - hash_operations: An array of ruby hash of operations to convert to SOAP
43
+ # - hash_operations: An array of ruby has operations to execute by
44
+ # posting them to the provided URL
44
45
  # - service_name: The name of the AdwordsApi service as a symbol that would
45
46
  # normally make this request
47
+ # - batch_job_url: The URL provided by BatchjobService to post the provided
48
+ # operations to
49
+ #
50
+ # Raises:
51
+ # - InvalidBatchJobOperationError: If there is a problem converting the
52
+ # given operations to SOAP.
53
+ #
54
+ def upload_operations(operations, batch_job_url)
55
+ soap_operations = generate_soap_operations(operations)
56
+ post_soap_operations(soap_operations, batch_job_url)
57
+ end
58
+
59
+ # Provides a helper to manage incremental uploads.
60
+ #
61
+ # Args:
62
+ # - batch_job_url: The URL provided by BatchJobService to put the provided
63
+ # operations to.
64
+ # - uploaded_bytes: The number of bytes already uploaded for this
65
+ # incremental batch job. Can be retrieved from the IncrementalUploadHelper
66
+ # using uploaded_bytes.
46
67
  #
47
68
  # Returns:
48
- # - operations elements of SOAP body as an array of strings
69
+ # - an IncrementalUploadHelper that will accept operations and put them,
70
+ # keeping track of uploaded bytes automatically.
49
71
  #
50
- def generate_soap_operations(hash_operations)
51
- unless hash_operations.is_a?(Array)
52
- raise AdwordsApi::Errors::InvalidBatchJobOperationError,
53
- 'Operations must be in an array.'
54
- end
55
- return hash_operations.map do |operation|
56
- operation_type = operation[:xsi_type]
57
- if operation_type.nil?
58
- raise AdwordsApi::Errors::InvalidBatchJobOperationError,
59
- ':xsi_type for operations must be defined ' +
60
- 'explicitly for batch jobs.'
61
- end
62
- service_name = SERVICES_BY_OPERATION_TYPE[operation_type]
63
- if service_name.nil?
64
- raise AdwordsApi::Errors::InvalidBatchJobOperationError,
65
- 'Unknown operation type: %s' % operation_type
66
- end
67
- method_name = METHODS_BY_OPERATION_TYPE[operation_type]
68
- service = @api.service(service_name, @version)
69
- full_soap_xml = service.send(method_name, [operation])
70
- operation_xml = extract_soap_operations(full_soap_xml)
71
- operation_xml
72
- end
72
+ def start_incremental_upload(batch_job_url, uploaded_bytes = 0)
73
+ return AdwordsApi::IncrementalUploadHelper.new(
74
+ self, uploaded_bytes, batch_job_url)
73
75
  end
74
76
 
75
- # Posts the provided SOAP operations to the provided URL.
77
+ # Puts the provided operations to the provided URL, allowing
78
+ # for incremental followup puts.
76
79
  #
77
80
  # Args:
78
81
  # - soap_operations: An array including SOAP operations provided by
79
82
  # generate_soap_operations
80
83
  # - batch_job_url: The URL provided by BatchJobService to post the provided
81
84
  # operations to
85
+ # - total_content_length: The total number of bytes already uploaded
86
+ # incrementally. Set this to 0 the first time you call the method.
87
+ # - is_last_request: Whether or not this set of uploads will conclude the
88
+ # full request.
82
89
  #
83
- def post_soap_operations(soap_operations, batch_job_url)
90
+ # Returns:
91
+ # - total content length, including what was just uploaded. Pass this back
92
+ # into this method on subsequent calls.
93
+ def put_incremental_operations(
94
+ operations, batch_job_url, total_content_length = 0,
95
+ is_last_request = false)
84
96
  headers = DEFAULT_HEADERS
85
- request_body = UPLOAD_XML_SKELETON % [@version, soap_operations.join]
97
+ soap_operations = generate_soap_operations(operations)
98
+ request_body = soap_operations.join
99
+ is_first_request = (total_content_length == 0)
100
+
101
+ if is_first_request
102
+ request_body = (UPLOAD_XML_PREFIX % [@version]) + request_body
103
+ end
104
+ if is_last_request
105
+ request_body += UPLOAD_XML_SUFFIX
106
+ end
107
+
108
+ request_body = add_padding(request_body)
109
+ content_length = request_body.size
110
+
111
+ headers['Content-Length'] = content_length.to_s
112
+
113
+ lower_bound = total_content_length
114
+ upper_bound = total_content_length + content_length - 1
115
+ total_bytes = is_last_request ? upper_bound + 1 : '*'
116
+ content_range = "bytes %d-%d/%s" %
117
+ [lower_bound, upper_bound, total_bytes]
118
+ headers['Content-Range'] = content_range
119
+
86
120
  log_request(batch_job_url, headers, request_body)
87
- response = AdsCommon::Http.post_response(
88
- batch_job_url, request_body, @api.config, headers)
89
- end
90
121
 
91
- # A convenience method that will generate SOAP operations and immediately
92
- # execute them. This is useful if your operations all would be
93
- # processed by a single service.
94
- #
95
- # Args:
96
- # - hash_operations: An array of ruby has operations to execute by
97
- # posting them to the provided URL
98
- # - service_name: The name of the AdwordsApi service as a symbol that would
99
- # normally make this request
100
- # - batch_job_url: The URL provided by BatchjobService to post the provided
101
- # operations to
102
- #
103
- def execute_hash_operations(hash_operations, batch_job_url)
104
- soap_operations = generate_soap_operations(hash_operations)
105
- post_soap_operations(soap_operations, batch_job_url)
122
+ # The HTTPI library fails to handle the response when uploading
123
+ # incremental requests. We're not interested in the response, so just
124
+ # ignore the error.
125
+ begin
126
+ AdsCommon::Http.put_response(
127
+ batch_job_url, request_body, @api.config, headers)
128
+ rescue ArgumentError
129
+ end
130
+
131
+ total_content_length += content_length
132
+ return total_content_length
106
133
  end
107
134
 
108
135
  # Downloads the results of a batch job from the specified URL.
@@ -126,9 +153,13 @@ module AdwordsApi
126
153
 
127
154
  private
128
155
 
129
- UPLOAD_XML_SKELETON = '<?xml version="1.0" encoding="UTF-8"?><ns1:mutate ' +
130
- 'xmlns:ns1="https://adwords.google.com/api/adwords/cm/%s">%s' +
131
- '</ns1:mutate>'
156
+ # For incremental uploads, the size (in bytes) of the body of the request
157
+ # must be in multiples of 256k.
158
+ REQUIRED_CONTENT_LENGTH_INCREMENT = 256 * 1024
159
+
160
+ UPLOAD_XML_PREFIX = '<?xml version="1.0" encoding="UTF-8"?><ns1:mutate ' +
161
+ 'xmlns:ns1="https://adwords.google.com/api/adwords/cm/%s">'
162
+ UPLOAD_XML_SUFFIX = '</ns1:mutate>'
132
163
  DEFAULT_HEADERS = {"Content-Type" => "application/xml"}
133
164
 
134
165
  SERVICES_BY_OPERATION_TYPE = {
@@ -161,6 +192,40 @@ module AdwordsApi
161
192
  'FeedItemOperation' => 'mutate_to_xml'
162
193
  }
163
194
 
195
+ def generate_soap_operations(hash_operations)
196
+ unless hash_operations.is_a?(Array)
197
+ raise AdwordsApi::Errors::InvalidBatchJobOperationError,
198
+ 'Operations must be in an array.'
199
+ end
200
+ return hash_operations.map do |operation|
201
+ operation_type = operation[:xsi_type]
202
+ if operation_type.nil?
203
+ raise AdwordsApi::Errors::InvalidBatchJobOperationError,
204
+ ':xsi_type for operations must be defined ' +
205
+ 'explicitly for batch jobs.'
206
+ end
207
+ service_name = SERVICES_BY_OPERATION_TYPE[operation_type]
208
+ if service_name.nil?
209
+ raise AdwordsApi::Errors::InvalidBatchJobOperationError,
210
+ 'Unknown operation type: %s' % operation_type
211
+ end
212
+ method_name = METHODS_BY_OPERATION_TYPE[operation_type]
213
+ service = @api.service(service_name, @version)
214
+ full_soap_xml = service.send(method_name, [operation])
215
+ operation_xml = extract_soap_operations(full_soap_xml)
216
+ operation_xml
217
+ end
218
+ end
219
+
220
+ def post_soap_operations(soap_operations, batch_job_url)
221
+ headers = DEFAULT_HEADERS
222
+ request_body = (UPLOAD_XML_PREFIX % [@version]) + soap_operations.join +
223
+ UPLOAD_XML_SUFFIX
224
+ log_request(batch_job_url, headers, request_body)
225
+ response = AdsCommon::Http.post_response(
226
+ batch_job_url, request_body, @api.config, headers)
227
+ end
228
+
164
229
  # Given a full SOAP xml string, extract just the operations element
165
230
  # from the SOAP body as a string.
166
231
  def extract_soap_operations(full_soap_xml)
@@ -217,6 +282,14 @@ module AdwordsApi
217
282
  return results
218
283
  end
219
284
 
285
+ def add_padding(xml)
286
+ remainder = xml.size % REQUIRED_CONTENT_LENGTH_INCREMENT
287
+ return xml if remainder == 0
288
+ bytes_to_add = REQUIRED_CONTENT_LENGTH_INCREMENT - remainder
289
+ padded_xml = xml + (' ' * bytes_to_add)
290
+ return padded_xml
291
+ end
292
+
220
293
  def get_nori()
221
294
  return @nori if @nori
222
295
 
@@ -0,0 +1,71 @@
1
+ # Encoding: utf-8
2
+ #
3
+ # Copyright:: Copyright 2015, Google Inc. All Rights Reserved.
4
+ #
5
+ # License:: 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
14
+ # implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # Contains helper methods to allow incremental uploads with the
19
+ # BatchJobService.
20
+
21
+ module AdwordsApi
22
+ class IncrementalUploadHelper
23
+ attr_reader :upload_url, :uploaded_bytes
24
+
25
+ # Default constructor.
26
+ #
27
+ # Args:
28
+ # - batch_job_service: The instance of BatchJobService that is providing
29
+ # this helper
30
+ # - uploaded_bytes: The number of bytes that have already been uploaded
31
+ # as part of this incremental process.
32
+ # - upload_url: The URL that should be used to upload incremental
33
+ # operations for this job.
34
+ #
35
+ def initialize(batch_job_utils, uploaded_bytes, upload_url)
36
+ @batch_job_utils = batch_job_utils
37
+ @uploaded_bytes = uploaded_bytes
38
+ @upload_url = upload_url
39
+ @finished = false
40
+ end
41
+
42
+ # Takes an array of operations and puts it to the batch job incrementally.
43
+ #
44
+ # Args:
45
+ # - hash_operations: An array of operations to put, represented in hashes
46
+ # like you would normally pass to services.
47
+ # - is_last_request: Whether this request is the last request of the
48
+ # incremental job.
49
+ #
50
+ # Raises:
51
+ # - InvalidBatchJobOperationError: If this incremental upload is already
52
+ # finished or if there is an error converting the hash operations to
53
+ # soap operations.
54
+ #
55
+ def upload(operations, is_last_request = false)
56
+ check_status()
57
+ @uploaded_bytes = @batch_job_utils.put_incremental_operations(
58
+ operations, @upload_url, @uploaded_bytes, is_last_request)
59
+ @finished = true if is_last_request
60
+ end
61
+
62
+ private
63
+
64
+ def check_status()
65
+ if @finished
66
+ raise AdwordsApi::Errors::InvalidBatchJobOperationError,
67
+ 'Cannot put new operations to completed incremental upload.'
68
+ end
69
+ end
70
+ end
71
+ end
@@ -67,7 +67,7 @@ module AdwordsApi
67
67
  include_zero_impressions_header =
68
68
  @config.read('library.include_zero_impressions_header')
69
69
  unless include_zero_impressions_header.nil?
70
- headers['includeZeroImpressoins'] = include_zero_impressions_header.to_s
70
+ headers['includeZeroImpressions'] = include_zero_impressions_header.to_s
71
71
  end
72
72
  return headers
73
73
  end
@@ -0,0 +1,64 @@
1
+ # Encoding: utf-8
2
+ #
3
+ # Copyright:: Copyright 2015, Google Inc. All Rights Reserved.
4
+ #
5
+ # License:: 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
14
+ # implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # Includes a way to iterate over a report stream by line, rather than in
19
+ # arbitrary chunks.
20
+
21
+ module AdwordsApi
22
+ class ReportStream
23
+ def initialize(report_utils, report_definition, cid = nil, format = nil,
24
+ is_awql = false, &block)
25
+ @report_utils = report_utils
26
+ @report_definition = report_definition
27
+ @format = format
28
+ @cid = cid
29
+ @block = block
30
+ @is_awql = is_awql
31
+ end
32
+
33
+ def self.set_up(report_utils, report_definition, cid = nil, &block)
34
+ return ReportStream.new(report_utils, report_definition, cid, &block)
35
+ end
36
+
37
+ def self.set_up_with_awql(
38
+ report_utils, report_query, format, cid = nil, &block)
39
+ return ReportStream.new(
40
+ report_utils, report_query, cid, format, true, &block)
41
+ end
42
+
43
+ def each_line()
44
+ # Determine which method and arguments to use depending on whether
45
+ # this is an AWQL request or not.
46
+ method = 'download_report_as_stream'
47
+ method += '_with_awql' if @is_awql
48
+ args = [@report_definition]
49
+ args << @format if @is_awql
50
+ args << @cid
51
+
52
+ leftover_data = ''
53
+ @report_utils.send(method, *args) do |batch|
54
+ data_to_process = leftover_data + batch
55
+ lines = data_to_process.split('\n')
56
+ leftover_data = lines.pop
57
+ lines.each do |line|
58
+ @block.call(line)
59
+ end
60
+ end
61
+ @block.call(leftover_data) unless leftover_data.empty?()
62
+ end
63
+ end
64
+ end
@@ -24,6 +24,7 @@ require 'nori'
24
24
  require 'ads_common/http'
25
25
  require 'adwords_api/errors'
26
26
  require 'adwords_api/report_header_handler'
27
+ require 'adwords_api/report_stream'
27
28
 
28
29
  module AdwordsApi
29
30
  class ReportUtils
@@ -76,6 +77,45 @@ module AdwordsApi
76
77
  return nil
77
78
  end
78
79
 
80
+ # Streams a report as a string to the given block. This method will not do
81
+ # error checking on returned values.
82
+ #
83
+ # Args:
84
+ # - report_definition: definition of the report in XML text or hash
85
+ # - path: path to save report to
86
+ # - cid: optional customer ID to run against
87
+ #
88
+ # Returns:
89
+ # - nil
90
+ #
91
+ # Raises:
92
+ # - AdwordsApi::Errors::InvalidReportDefinitionError if the report
93
+ # definition is invalid
94
+ #
95
+ def download_report_as_stream(report_definition, cid = nil, &block)
96
+ return get_report_response(report_definition, cid, &block)
97
+ end
98
+
99
+ # Returns a helper object that can manage breaking the streamed report
100
+ # results into individual lines.
101
+ #
102
+ # Args:
103
+ # - report_definition: definition of the report in XML text or hash
104
+ # - path: path to save report to
105
+ # - cid: optional customer ID to run against
106
+ #
107
+ # Returns:
108
+ # - ReportStream object initialized to begin streaming.
109
+ #
110
+ # Raises:
111
+ # - AdwordsApi::Errors::InvalidReportDefinitionError if the report
112
+ # definition is invalid
113
+ #
114
+ def get_stream_helper(report_definition, cid = nil, &block)
115
+ return AdwordsApi::ReportStream.set_up(
116
+ self, report_definition, cid, &block)
117
+ end
118
+
79
119
  # Downloads and returns a report with AWQL.
80
120
  #
81
121
  # Args:
@@ -113,6 +153,38 @@ module AdwordsApi
113
153
  return nil
114
154
  end
115
155
 
156
+ # Streams a report with AWQL as a string to the given block. This method
157
+ # will not do error checking on returned values.
158
+ #
159
+ # Args:
160
+ # - report_query: query for the report as string
161
+ # - format: format for the report as string
162
+ # - cid: optional customer ID to run report against
163
+ #
164
+ # Returns:
165
+ # - nil
166
+ #
167
+ def download_report_as_stream_with_awql(
168
+ report_query, format, cid = nil, &block)
169
+ return get_report_response_with_awql(report_query, format, cid, &block)
170
+ end
171
+
172
+ # Returns a helper object that can manage breaking the streamed report
173
+ # results into individual lines.
174
+ #
175
+ # Args:
176
+ # - report_query: query for the report as string
177
+ # - format: format for the report as string
178
+ # - cid: optional customer ID to run report against
179
+ #
180
+ # Returns:
181
+ # - ReportStream object initialized to begin streaming.
182
+ #
183
+ def get_stream_helper_with_awql(report_query, format, cid = nil, &block)
184
+ return AdwordsApi::ReportStream.set_up_with_awql(
185
+ self, report_query, format, cid, &block)
186
+ end
187
+
116
188
  private
117
189
 
118
190
  # Minimal set of required fields for report definition.
@@ -131,28 +203,36 @@ module AdwordsApi
131
203
  }
132
204
 
133
205
  # Send POST request for a report and returns Response object.
134
- def get_report_response(report_definition, cid)
206
+ def get_report_response(report_definition, cid, &block)
135
207
  definition_text = get_report_definition_text(report_definition)
136
208
  data = '__rdxml=%s' % CGI.escape(definition_text)
137
- return make_adhoc_request(data, cid)
209
+ return make_adhoc_request(data, cid, &block)
138
210
  end
139
211
 
140
212
  # Send POST request for a report with AWQL and returns Response object.
141
- def get_report_response_with_awql(report_query, format, cid)
213
+ def get_report_response_with_awql(report_query, format, cid, &block)
142
214
  data = '__rdquery=%s&__fmt=%s' %
143
215
  [CGI.escape(report_query), CGI.escape(format)]
144
- return make_adhoc_request(data, cid)
216
+ return make_adhoc_request(data, cid, &block)
145
217
  end
146
218
 
147
219
  # Makes request and AdHoc service and returns response.
148
- def make_adhoc_request(data, cid)
220
+ def make_adhoc_request(data, cid, &block)
149
221
  url = @api.api_config.adhoc_report_download_url(
150
222
  @api.config.read('service.environment'), @version)
151
223
  headers = get_report_request_headers(url, cid)
152
224
  log_request(url, headers, data)
153
- response = AdsCommon::Http.post_response(url, data, @api.config, headers)
154
- check_for_errors(response)
155
- return response
225
+ # A given block indicates that we should make a stream request and yield
226
+ # the results, rather than return a full response.
227
+ if block_given?
228
+ AdsCommon::Http.post_stream(url, data, @api.config, headers, &block)
229
+ return nil
230
+ else
231
+ response = AdsCommon::Http.post_response(
232
+ url, data, @api.config, headers)
233
+ check_for_errors(response)
234
+ return response
235
+ end
156
236
  end
157
237
 
158
238
  # Converts passed object to XML text. Currently support String (no changes)