google-gax 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d5a83ab24363b35811939797cf9bf42efb7f6c9d
4
+ data.tar.gz: c57b1a19fbce28c086d469989edba2ecd269da1c
5
+ SHA512:
6
+ metadata.gz: 9ab3cfa6e5f2addb6b41f640d29a660fae38b66d261835e04fde29736370e0bf9a87a017d8b4eb8d4545e13d53419bf391f598e5c8128a00de58eef129a8c2b1
7
+ data.tar.gz: a013ae626c7286b2ded6e1a315e88be2a1e107aabdfb17554c315edb487b051df8274dc4c92cf256db5566338ae4add38491dc3935d7995b03b88888164d1600
@@ -0,0 +1,15 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ Bundler.setup :default, :development
4
+
5
+ require 'rspec/core'
6
+ require 'rspec/core/rake_task'
7
+
8
+ RSpec::Core::RakeTask.new(:spec) do |spec|
9
+ spec.pattern = FileList['spec/**/*_spec.rb']
10
+ end
11
+
12
+ require 'rubocop/rake_task'
13
+ RuboCop::RakeTask.new(:rubocop)
14
+
15
+ task default: [:spec, :rubocop]
@@ -0,0 +1,283 @@
1
+ # Copyright 2016, Google Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are
6
+ # met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright
9
+ # notice, this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above
11
+ # copyright notice, this list of conditions and the following disclaimer
12
+ # in the documentation and/or other materials provided with the
13
+ # distribution.
14
+ # * Neither the name of Google Inc. nor the names of its
15
+ # contributors may be used to endorse or promote products derived from
16
+ # this software without specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ require 'google/gax/api_callable'
31
+ require 'google/gax/errors'
32
+ require 'google/gax/grpc'
33
+ require 'google/gax/path_template'
34
+ require 'google/gax/settings'
35
+ require 'google/gax/version'
36
+
37
+ module Google
38
+ module Gax
39
+ # Encapsulates the call settings for an ApiCallable
40
+ # @!attribute [r] timeout
41
+ # @return [Numeric]
42
+ # @!attribute [r] retry_options
43
+ # @return [RetryOptions]
44
+ # @!attribute [r] page_descriptor
45
+ # @return [PageDescriptor]
46
+ # @!attribute [r] bundle_descriptor
47
+ # @return [BundleDescriptor]
48
+ class CallSettings
49
+ attr_reader :timeout, :retry_options, :page_descriptor, :bundler,
50
+ :bundle_descriptor
51
+
52
+ # @param timeout [Numeric] The client-side timeout for API calls. This
53
+ # parameter is ignored for retrying calls.
54
+ # @param retry_options [RetryOptions] The configuration for retrying upon
55
+ # transient error. If set to None, this call will not retry.
56
+ # @param page_descriptor [PageDescriptor] indicates the structure of page
57
+ # streaming to be performed. If set to None, page streaming is not
58
+ # performed.
59
+ # @param bundler orchestrates bundling. If None, bundling is not
60
+ # performed.
61
+ # @param bundle_descriptor [BundleDescriptor] indicates the structure of
62
+ # the bundle. If None, bundling is not performed.
63
+ def initialize(
64
+ timeout: 30, retry_options: nil, page_descriptor: nil,
65
+ bundler: nil, bundle_descriptor: nil)
66
+ @timeout = timeout
67
+ @retry_options = retry_options
68
+ @page_descriptor = page_descriptor
69
+ @bundler = bundler
70
+ @bundle_descriptor = bundle_descriptor
71
+ end
72
+
73
+ # @return true when it has retry codes.
74
+ def retry_codes?
75
+ @retry_options && @retry_options.retry_codes
76
+ end
77
+
78
+ # @return true when it has valid bundler configuration.
79
+ def bundler?
80
+ @bundler && @bundle_descriptor
81
+ end
82
+
83
+ # Creates a new CallSetting instance which is based on this but merged
84
+ # settings from options.
85
+ # @param options [CallOptions, nil] The overriding call settings.
86
+ # @return a new merged call settings.
87
+ def merge(options)
88
+ unless options
89
+ return CallSettings.new(
90
+ timeout: @timeout,
91
+ retry_options: @retry_options,
92
+ page_descriptor: @page_descriptor,
93
+ bundler: @bundler,
94
+ bundle_descriptor: @bundle_descriptor)
95
+ end
96
+
97
+ timeout = if options.timeout == :OPTION_INHERIT
98
+ @timeout
99
+ else
100
+ options.timeout
101
+ end
102
+ retry_options = if options.retry_options == :OPTION_INHERIT
103
+ @retry_options
104
+ else
105
+ options.retry_options
106
+ end
107
+ page_descriptor = @page_descriptor if options.is_page_streaming
108
+
109
+ CallSettings.new(
110
+ timeout: timeout,
111
+ retry_options: retry_options,
112
+ page_descriptor: page_descriptor,
113
+ bundler: @bundler,
114
+ bundle_descriptor: @bundle_descriptor)
115
+ end
116
+ end
117
+
118
+ # Encapsulates the overridable settings for a particular API call
119
+ # @!attribute [r] timeout
120
+ # @return [Numeric, :OPTION_INHERIT]
121
+ # @!attribute [r] retry_options
122
+ # @return [RetryOptions, :OPTION_INHERIT]
123
+ # @!attribute [r] is_page_streaming
124
+ # @return [true, false, :OPTION_INHERIT]
125
+ class CallOptions
126
+ attr_reader :timeout, :retry_options, :is_page_streaming
127
+
128
+ # @param timeout [Numeric, :OPTION_INHERIT]
129
+ # The client-side timeout for API calls.
130
+ # @param retry_options [RetryOptions, :OPTION_INHERIT]
131
+ # The configuration for retrying upon transient error.
132
+ # If set to nil, this call will not retry.
133
+ # @param is_page_streaming [true, false, :OPTION_INHERIT]
134
+ # If set and the call is configured for page streaming, page streaming
135
+ # is performed.
136
+ def initialize(
137
+ timeout: :OPTION_INHERIT,
138
+ retry_options: :OPTION_INHERIT,
139
+ is_page_streaming: :OPTION_INHERIT)
140
+ @timeout = timeout
141
+ @retry_options = retry_options
142
+ @is_page_streaming = is_page_streaming
143
+ end
144
+ end
145
+
146
+ # Describes the structure of a page-streaming call.
147
+ class PageDescriptor < Struct.new(
148
+ :request_page_token_field,
149
+ :response_page_token_field,
150
+ :resource_field)
151
+ end
152
+
153
+ # Per-call configurable settings for retrying upon transient failure.
154
+ class RetryOptions < Struct.new(:retry_codes, :backoff_settings)
155
+ # @!attribute retry_codes
156
+ # @return [Array<Grpc::Code>] a list of exceptions upon which
157
+ # a retry should be attempted.
158
+ # @!attribute backoff_settings
159
+ # @return [BackoffSettings] configuring the retry exponential
160
+ # backoff algorithm.
161
+ end
162
+
163
+ # Parameters to the exponential backoff algorithm for retrying.
164
+ class BackoffSettings < Struct.new(
165
+ :initial_retry_delay_millis,
166
+ :retry_delay_multiplier,
167
+ :max_retry_delay_millis,
168
+ :initial_rpc_timeout_millis,
169
+ :rpc_timeout_multiplier,
170
+ :max_rpc_timeout_millis,
171
+ :total_timeout_millis)
172
+ # @!attribute initial_retry_delay_millis
173
+ # @return [Numeric] the initial delay time, in milliseconds,
174
+ # between the completion of the first failed request and the
175
+ # initiation of the first retrying request.
176
+ # @!attribute retry_delay_multiplier
177
+ # @return [Numeric] the multiplier by which to increase the
178
+ # delay time between the completion of failed requests, and
179
+ # the initiation of the subsequent retrying request.
180
+ # @!attribute max_retry_delay_millis
181
+ # @return [Numeric] the maximum delay time, in milliseconds,
182
+ # between requests. When this value is reached,
183
+ # +retry_delay_multiplier+ will no longer be used to
184
+ # increase delay time.
185
+ # @!attribute initial_rpc_timeout_millis
186
+ # @return [Numeric] the initial timeout parameter to the request.
187
+ # @!attribute rpc_timeout_multiplier
188
+ # @return [Numeric] the multiplier by which to increase the
189
+ # timeout parameter between failed requests.
190
+ # @!attribute max_rpc_timeout_millis
191
+ # @return [Numeric] the maximum timeout parameter, in
192
+ # milliseconds, for a request. When this value is reached,
193
+ # +rpc_timeout_multiplier+ will no longer be used to
194
+ # increase the timeout.
195
+ # @!attribute total_timeout_millis
196
+ # @return [Numeric] the total time, in milliseconds, starting
197
+ # from when the initial request is sent, after which an
198
+ # error will be returned, regardless of the retrying
199
+ # attempts made meanwhile.
200
+ end
201
+
202
+ # Describes the structure of bundled call.
203
+ #
204
+ # request_discriminator_fields may include '.' as a separator, which is
205
+ # used to indicate object traversal. This allows fields in nested objects
206
+ # to be used to determine what requests to bundle.
207
+ class BundleDescriptor < Struct.new(
208
+ :bundled_field,
209
+ :request_discriminator_fields,
210
+ :subresponse_field)
211
+ # @!attribute bundled_field
212
+ # @return [String] the repeated field in the request message
213
+ # that will have its elements aggregated by bundling
214
+ # @!attribute request_discriminator_fields
215
+ # @return [Array<String>] a list of fields in the target
216
+ # request message class that are used to determine which
217
+ # messages should be bundled together.
218
+ # @!attribute subresponse_field
219
+ # @return [String] an optional field, when present it
220
+ # indicates the field in the response message that should be
221
+ # used to demultiplex the response into multiple response
222
+ # messages.
223
+ def initialize(bundled_field, request_discriminator_fields,
224
+ subresponse_field: nil)
225
+ super(bundled_field, request_discriminator_fields, subresponse_field)
226
+ end
227
+ end
228
+
229
+ # Holds values used to configure bundling.
230
+ #
231
+ # The xxx_threshold attributes are used to configure when the bundled
232
+ # request should be made.
233
+ class BundleOptions < Struct.new(
234
+ :element_count_threshold,
235
+ :element_count_limit,
236
+ :request_byte_threshold,
237
+ :request_byte_limit,
238
+ :delay_threshold)
239
+ # @!attribute element_count_threshold
240
+ # @return [Numeric] the bundled request will be sent once the
241
+ # count of outstanding elements in the repeated field
242
+ # reaches this value.
243
+ # @!attribute element_count_limit
244
+ # @return [Numeric] represents a hard limit on the number of
245
+ # elements in the repeated field of the bundle; if adding a
246
+ # request to a bundle would exceed this value, the bundle is
247
+ # sent and the new request is added to a fresh bundle. It is
248
+ # invalid for a single request to exceed this limit.
249
+ # @!attribute request_byte_threshold
250
+ # @return [Numeric] the bundled request will be sent once the
251
+ # count of bytes in the request reaches this value. Note
252
+ # that this value is pessimistically approximated by summing
253
+ # the bytesizes of the elements in the repeated field, and
254
+ # therefore may be an under-approximation.
255
+ # @!attribute request_byte_limit
256
+ # @return [Numeric] represents a hard limit on the size of the
257
+ # bundled request; if adding a request to a bundle would
258
+ # exceed this value, the bundle is sent and the new request
259
+ # is added to a fresh bundle. It is invalid for a single
260
+ # request to exceed this limit. Note that this value is
261
+ # pessimistically approximated by summing the bytesizes of
262
+ # the elements in the repeated field, with a buffer applied
263
+ # to correspond to the resulting under-approximation.
264
+ # @!attribute delay_threshold
265
+ # @return [Numeric] the bundled request will be sent this
266
+ # amount of time after the first element in the bundle was
267
+ # added to it.
268
+ def initialize(
269
+ element_count_threshold: 0,
270
+ element_count_limit: 0,
271
+ request_byte_threshold: 0,
272
+ request_byte_limit: 0,
273
+ delay_threshold: 0)
274
+ super(
275
+ element_count_threshold,
276
+ element_count_limit,
277
+ request_byte_threshold,
278
+ request_byte_limit,
279
+ delay_threshold)
280
+ end
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,356 @@
1
+ # Copyright 2016, Google Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are
6
+ # met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright
9
+ # notice, this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above
11
+ # copyright notice, this list of conditions and the following disclaimer
12
+ # in the documentation and/or other materials provided with the
13
+ # distribution.
14
+ # * Neither the name of Google Inc. nor the names of its
15
+ # contributors may be used to endorse or promote products derived from
16
+ # this software without specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ require 'time'
31
+
32
+ require 'google/gax/errors'
33
+ require 'google/gax/grpc'
34
+
35
+ # rubocop:disable Metrics/ModuleLength
36
+ module Google
37
+ module Gax
38
+ MILLIS_PER_SECOND = 1000.0
39
+
40
+ # A class to provide the Enumerable interface for page-streaming method.
41
+ # PagedEnumerable assumes that the API call returns a message for a page
42
+ # which holds a list of resources and the token to the next page.
43
+ #
44
+ # PagedEnumerable provides the enumerations over the resource data,
45
+ # and also provides the enumerations over the pages themselves.
46
+ #
47
+ # @attribute [r] page
48
+ # @return [Page] The current page object.
49
+ class PagedEnumerable
50
+ # A class to represent a page in a PagedEnumerable. This also implements
51
+ # Enumerable, so it can iterate over the resource elements.
52
+ #
53
+ # @attribute [r] response
54
+ # @return [Object] The actual response object.
55
+ class Page
56
+ include Enumerable
57
+ attr_reader :response
58
+
59
+ # @param response [Object]
60
+ # The response object for the page.
61
+ # @param response_page_token_field [String]
62
+ # The name of the field in response which holds the next page token.
63
+ # @param resource_field [String]
64
+ # The name of the field in response which holds the resources.
65
+ def initialize(response, response_page_token_field, resource_field)
66
+ @response = response
67
+ @response_page_token_field = response_page_token_field
68
+ @resource_field = resource_field
69
+ end
70
+
71
+ # Creates another instance of Page with replacing the new response.
72
+ # @param response [Object] a new response object.
73
+ def dup_with(response)
74
+ self.class.new(response, @response_page_token_field, @resource_field)
75
+ end
76
+
77
+ # Iterate over the resources.
78
+ # @yield [Object] Gives the resource objects in the page.
79
+ def each
80
+ @response[@resource_field].each do |obj|
81
+ yield obj
82
+ end
83
+ end
84
+
85
+ def next_page_token
86
+ @response[@response_page_token_field]
87
+ end
88
+
89
+ # Truthiness of next_page_token.
90
+ def next_page_token?
91
+ !@response.nil? && !next_page_token.nil? && next_page_token != 0 &&
92
+ (!next_page_token.respond_to?(:empty) || !next_page_token.empty?)
93
+ end
94
+ end
95
+
96
+ include Enumerable
97
+ attr_reader :page
98
+
99
+ # @param a_func [Proc]
100
+ # A proc to update the response object.
101
+ # @param request_page_token_field [String]
102
+ # The name of the field in request which will have the page token.
103
+ # @param response_page_token_field [String]
104
+ # The name of the field in the response which holds the next page token.
105
+ # @param resource_field [String]
106
+ # The name of the field in the response which holds the resources.
107
+ def initialize(a_func, request_page_token_field,
108
+ response_page_token_field, resource_field)
109
+ @func = a_func
110
+ @request_page_token_field = request_page_token_field
111
+ @page = Page.new(nil, response_page_token_field, resource_field)
112
+ end
113
+
114
+ # Initiate the streaming with the requests and keywords.
115
+ # @param request [Object]
116
+ # The initial request object.
117
+ # @param kwargs [Hash]
118
+ # Other keyword arguments to be passed to a_func.
119
+ # @return [PagedEnumerable]
120
+ # returning self for further uses.
121
+ def start(request, **kwargs)
122
+ @request = request
123
+ @kwargs = kwargs
124
+ @page = @page.dup_with(@func.call(@request, **@kwargs))
125
+ self
126
+ end
127
+
128
+ # True if it's already started.
129
+ def started?
130
+ !@request.nil?
131
+ end
132
+
133
+ # Iterate over the resources.
134
+ # @yield [Object] Gives the resource objects in the stream.
135
+ # @raise [RuntimeError] if it's not started yet.
136
+ def each
137
+ each_page do |page|
138
+ page.each do |obj|
139
+ yield obj
140
+ end
141
+ end
142
+ end
143
+
144
+ # Iterate over the pages.
145
+ # @yield [Page] Gives the pages in the stream.
146
+ # @raise [RuntimeError] if it's not started yet.
147
+ def each_page
148
+ raise 'not started!' unless started?
149
+ yield @page
150
+ loop do
151
+ break unless next_page?
152
+ yield next_page
153
+ end
154
+ end
155
+
156
+ # True if it has the next page.
157
+ def next_page?
158
+ @page.next_page_token?
159
+ end
160
+
161
+ # Update the response in the current page.
162
+ # @return [Page] the new page object.
163
+ def next_page
164
+ return unless next_page?
165
+ @request[@request_page_token_field] = @page.next_page_token
166
+ @page = @page.dup_with(@func.call(@request, **@kwargs))
167
+ end
168
+ end
169
+
170
+ # rubocop:disable Metrics/AbcSize
171
+
172
+ # Converts an rpc call into an API call governed by the settings.
173
+ #
174
+ # In typical usage, +func+ will be a proc used to make an rpc request.
175
+ # This will mostly likely be a bound method from a request stub used to make
176
+ # an rpc call.
177
+ #
178
+ # The result is created by applying a series of function decorators
179
+ # defined in this module to +func+. +settings+ is used to determine
180
+ # which function decorators to apply.
181
+ #
182
+ # The result is another proc which for most values of +settings+ has the
183
+ # same signature as the original. Only when +settings+ configures bundling
184
+ # does the signature change.
185
+ #
186
+ # @param func [Proc] used to make a bare rpc call
187
+ # @param settings [CallSettings provides the settings for this call
188
+ # @return [Proc] a bound method on a request stub used to make an rpc call
189
+ # @raise [StandardError] if +settings+ has incompatible values,
190
+ # e.g, if bundling and page_streaming are both configured
191
+ def create_api_call(func, settings)
192
+ api_call = if settings.retry_codes?
193
+ _retryable(func, settings.retry_options)
194
+ else
195
+ _add_timeout_arg(func, settings.timeout)
196
+ end
197
+
198
+ if settings.page_descriptor
199
+ if settings.bundler?
200
+ raise 'ApiCallable has incompatible settings: ' \
201
+ 'bundling and page streaming'
202
+ end
203
+ return _page_streamable(
204
+ api_call,
205
+ settings.page_descriptor.request_page_token_field,
206
+ settings.page_descriptor.response_page_token_field,
207
+ settings.page_descriptor.resource_field)
208
+ end
209
+ if settings.bundler?
210
+ return _bundleable(api_call, settings.bundle_descriptor,
211
+ settings.bundler)
212
+ end
213
+
214
+ _catch_errors(api_call)
215
+ end
216
+
217
+ # Updates a_func to wrap exceptions with GaxError
218
+ #
219
+ # @param a_func [Proc]
220
+ # @param errors [Array<Exception>] Configures the exceptions to wrap.
221
+ # @return [Proc] A proc that will wrap certain exceptions with GaxError
222
+ def _catch_errors(a_func, errors: Grpc::API_ERRORS)
223
+ proc do |request, **kwargs|
224
+ begin
225
+ a_func.call(request, **kwargs)
226
+ rescue => err
227
+ if errors.any? { |eclass| err.is_a? eclass }
228
+ raise GaxError.new('RPC failed', cause: err)
229
+ else
230
+ raise err
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ # Creates a proc that transforms an API call into a bundling call.
237
+ #
238
+ # It transform a_func from an API call that receives the requests and
239
+ # returns the response into a proc that receives the same request, and
240
+ # returns a +Google::Gax::Bundling::Event+.
241
+ #
242
+ # The returned Event object can be used to obtain the eventual result of the
243
+ # bundled call.
244
+ #
245
+ # @param a_func [Proc] an API call that supports bundling.
246
+ # @param desc [BundleDescriptor] describes the bundling that
247
+ # +a_func+ supports.
248
+ # @param bundler orchestrates bundling.
249
+ # @return [Proc] A proc takes the API call's request and returns
250
+ # an Event object.
251
+ def _bundleable(a_func, desc, bundler)
252
+ proc do |request|
253
+ the_id = bundling.compute_bundle_id(
254
+ request,
255
+ desc.request_discriminator_fields)
256
+ return bundler.schedule(a_func, the_id, desc, request)
257
+ end
258
+ end
259
+
260
+ # Creates a proc that yields an iterable to performs page-streaming.
261
+ #
262
+ # @param a_func [Proc] an API call that is page streaming.
263
+ # @param request_page_token_field [String] The field of the page
264
+ # token in the request.
265
+ # @param response_page_token_field [String] The field of the next
266
+ # page token in the response.
267
+ # @param resource_field [String] The field to be streamed.
268
+ # @return [Proc] A proc that returns an iterable over the specified field.
269
+ def _page_streamable(
270
+ a_func,
271
+ request_page_token_field,
272
+ response_page_token_field,
273
+ resource_field)
274
+ enumerable = PagedEnumerable.new(a_func,
275
+ request_page_token_field,
276
+ response_page_token_field,
277
+ resource_field)
278
+ enumerable.method(:start)
279
+ end
280
+
281
+ # rubocop:disable Metrics/MethodLength
282
+
283
+ # Creates a proc equivalent to a_func, but that retries on certain
284
+ # exceptions.
285
+ #
286
+ # @param a_func [Proc]
287
+ # @param retry_options [RetryOptions] Configures the exceptions
288
+ # upon which the proc should retry, and the parameters to the
289
+ # exponential backoff retry algorithm.
290
+ # @return [Proc] A proc that will retry on exception.
291
+ def _retryable(a_func, retry_options)
292
+ delay_mult = retry_options.backoff_settings.retry_delay_multiplier
293
+ max_delay = (retry_options.backoff_settings.max_retry_delay_millis /
294
+ MILLIS_PER_SECOND)
295
+ timeout_mult = retry_options.backoff_settings.rpc_timeout_multiplier
296
+ max_timeout = (retry_options.backoff_settings.max_rpc_timeout_millis /
297
+ MILLIS_PER_SECOND)
298
+ total_timeout = (retry_options.backoff_settings.total_timeout_millis /
299
+ MILLIS_PER_SECOND)
300
+
301
+ proc do |request, **kwargs|
302
+ delay = retry_options.backoff_settings.initial_retry_delay_millis
303
+ timeout = (retry_options.backoff_settings.initial_rpc_timeout_millis /
304
+ MILLIS_PER_SECOND)
305
+ exc = nil
306
+ result = nil
307
+ now = Time.now
308
+ deadline = now + total_timeout
309
+
310
+ while now < deadline
311
+ begin
312
+ exc = nil
313
+ result = _add_timeout_arg(a_func, timeout).call(request, **kwargs)
314
+ break
315
+ rescue => exception
316
+ unless exception.respond_to?(:code) &&
317
+ retry_options.retry_codes.include?(exception.code)
318
+ raise RetryError.new('Exception occurred in retry method that ' \
319
+ 'was not classified as transient',
320
+ cause: exception)
321
+ end
322
+ exc = RetryError.new('Retry total timeout exceeded with exception',
323
+ cause: exception)
324
+ sleep(rand(delay) / MILLIS_PER_SECOND)
325
+ now = Time.now
326
+ delay = [delay * delay_mult, max_delay].min
327
+ timeout = [timeout * timeout_mult, max_timeout, deadline - now].min
328
+ end
329
+ end
330
+ raise exc unless exc.nil?
331
+ result
332
+ end
333
+ end
334
+
335
+ # Updates +a_func+ so that it gets called with the timeout as its final arg.
336
+ #
337
+ # This converts a proc, a_func, into another proc with an additional
338
+ # positional arg.
339
+ #
340
+ # @param a_func [Proc] a proc to be updated
341
+ # @param timeout [Numeric] to be added to the original proc as it
342
+ # final positional arg.
343
+ # @return [Proc] the original proc updated to the timeout arg
344
+ def _add_timeout_arg(a_func, timeout)
345
+ proc do |request, **kwargs|
346
+ kwargs[:timeout] = timeout
347
+ a_func.call(request, **kwargs)
348
+ end
349
+ end
350
+
351
+ module_function :create_api_call, :_catch_errors, :_bundleable,
352
+ :_page_streamable, :_retryable, :_add_timeout_arg
353
+ private_class_method :_catch_errors, :_bundleable, :_page_streamable,
354
+ :_retryable, :_add_timeout_arg
355
+ end
356
+ end