google-adwords-api 0.17.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
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)