looker-sdk 0.0.5

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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +52 -0
  3. data/.ruby-gemset +1 -0
  4. data/.travis.yml +16 -0
  5. data/Gemfile +22 -0
  6. data/LICENSE +21 -0
  7. data/Rakefile +37 -0
  8. data/authentication.md +104 -0
  9. data/examples/add_delete_users.rb +94 -0
  10. data/examples/change_credentials_email_address_for_users.rb +23 -0
  11. data/examples/create_credentials_email_for_users.rb +19 -0
  12. data/examples/delete_all_user_sessions.rb +15 -0
  13. data/examples/delete_credentials_google_for_users.rb +19 -0
  14. data/examples/generate_password_reset_tokens_for_users.rb +19 -0
  15. data/examples/ldap_roles_test.rb +50 -0
  16. data/examples/me.rb +3 -0
  17. data/examples/refresh_user_notification_addresses.rb +10 -0
  18. data/examples/roles_and_users_with_permission.rb +22 -0
  19. data/examples/sdk_setup.rb +21 -0
  20. data/examples/streaming_downloads.rb +20 -0
  21. data/examples/users_with_credentials_email.rb +6 -0
  22. data/examples/users_with_credentials_google.rb +8 -0
  23. data/examples/users_with_credentials_google_without_credentials_email.rb +6 -0
  24. data/lib/looker-sdk.rb +32 -0
  25. data/lib/looker-sdk/authentication.rb +104 -0
  26. data/lib/looker-sdk/client.rb +445 -0
  27. data/lib/looker-sdk/client/dynamic.rb +107 -0
  28. data/lib/looker-sdk/configurable.rb +116 -0
  29. data/lib/looker-sdk/default.rb +148 -0
  30. data/lib/looker-sdk/error.rb +235 -0
  31. data/lib/looker-sdk/rate_limit.rb +33 -0
  32. data/lib/looker-sdk/response/raise_error.rb +20 -0
  33. data/lib/looker-sdk/sawyer_patch.rb +33 -0
  34. data/lib/looker-sdk/version.rb +7 -0
  35. data/looker-sdk.gemspec +27 -0
  36. data/readme.md +117 -0
  37. data/shell/.gitignore +41 -0
  38. data/shell/Gemfile +6 -0
  39. data/shell/readme.md +18 -0
  40. data/shell/shell.rb +37 -0
  41. data/streaming.md +59 -0
  42. data/test/helper.rb +46 -0
  43. data/test/looker/swagger.json +1998 -0
  44. data/test/looker/test_client.rb +258 -0
  45. data/test/looker/test_dynamic_client.rb +158 -0
  46. data/test/looker/test_dynamic_client_agent.rb +131 -0
  47. data/test/looker/user.json +1 -0
  48. metadata +107 -0
@@ -0,0 +1,19 @@
1
+ require './sdk_setup'
2
+
3
+ $stdin.each_line do |line|
4
+ line.chomp!
5
+
6
+ id = line.split(',', 2).map(&:strip).first
7
+
8
+ begin
9
+ user = sdk.user(id)
10
+ if user.credentials_email
11
+ token = sdk.create_user_credentials_email_password_reset(id)
12
+ puts "#{token.email},#{token.password_reset_url}"
13
+ else
14
+ puts "Error: User with id '#{id}' Does not have credentials_email"
15
+ end
16
+ rescue LookerSDK::NotFound
17
+ puts "Error: User with id '#{id}' Not found"
18
+ end
19
+ end
@@ -0,0 +1,50 @@
1
+ require './sdk_setup'
2
+
3
+ ############################################################################################
4
+ # simulate a list read from file
5
+
6
+ user_list = <<-ENDMARK
7
+
8
+ mward
9
+ mwhite
10
+ missing
11
+
12
+ ENDMARK
13
+
14
+ ############################################################################################
15
+ # helpers
16
+
17
+ def ldap_config
18
+ @ldap_config ||= sdk.ldap_config.to_attrs
19
+ end
20
+
21
+ def groups_map_for_config
22
+ @groups_map_for_config ||= ldap_config[:groups].map do |group|
23
+ {:name => group[:name], :role_ids => group[:roles].map{|role| role[:id]}}
24
+ end
25
+ end
26
+
27
+ def test_ldap_user(user)
28
+ params = ldap_config.merge({:groups_with_role_ids => groups_map_for_config, :test_ldap_user => user})
29
+ sdk.test_ldap_config_user_info(params)
30
+ end
31
+
32
+ ############################################################################################
33
+ # process the list and puts results
34
+
35
+ user_list.each_line do |user|
36
+ user.strip!
37
+ next if user.empty?
38
+
39
+ puts "'#{user}' ..."
40
+
41
+ result = test_ldap_user(user).to_attrs
42
+ if result[:status] == 'success'
43
+ puts "Success"
44
+ puts result[:user]
45
+ else
46
+ puts "FAILURE"
47
+ puts result
48
+ end
49
+ puts
50
+ end
@@ -0,0 +1,3 @@
1
+ require './sdk_setup'
2
+
3
+ puts sdk.me.inspect
@@ -0,0 +1,10 @@
1
+ require './sdk_setup'
2
+
3
+ sdk.all_users(:fields => 'id,email').each do |user|
4
+ new_user = sdk.update_user(user.id, {})
5
+ if user.email == new_user.email
6
+ puts "No Change for #{user.id}"
7
+ else
8
+ puts "Refreshed #{user.id}. Old email '#{user.email}'. Refreshed email: '#{new_user.email}'."
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ require './sdk_setup'
2
+
3
+
4
+ while true do
5
+ print "permission [,model]? "
6
+ permission_name, model_name = gets.chomp.split(',')
7
+
8
+ break if permission_name.empty?
9
+
10
+ roles = sdk.all_roles.select do |role|
11
+ (role.permission_set.all_access || role.permission_set.permissions.join(',').include?(permission_name)) &&
12
+ (model_name.nil? || role.model_set.all_access || role.model_set.models.join(',').include?(model_name))
13
+ end
14
+
15
+ puts "Roles: #{roles.map(&:name).join(', ')}"
16
+
17
+ role_ids = roles.map(&:id)
18
+ users = sdk.all_users.select {|user| (user.role_ids & role_ids).any?}
19
+ user_names = users.map{|u| "#{u.id}#{" ("+u.display_name+")" if u.display_name}"}.join(', ')
20
+
21
+ puts "Users: #{user_names}"
22
+ end
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'looker-sdk'
5
+
6
+ # common file used by various examples to setup and init sdk
7
+
8
+ def sdk
9
+ @sdk ||= LookerSDK::Client.new(
10
+ :netrc => true,
11
+ :netrc_file => "./.netrc",
12
+
13
+ # use my local looker with self-signed cert
14
+ :connection_options => {:ssl => {:verify => false}},
15
+ :api_endpoint => "https://localhost:19999/api/3.0",
16
+
17
+ # use a real looker the way you are supposed to!
18
+ # :connection_options => {:ssl => {:verify => true}},
19
+ # :api_endpoint => "https://mycoolcompany.looker.com:19999/api/3.0",
20
+ )
21
+ end
@@ -0,0 +1,20 @@
1
+ require './sdk_setup'
2
+
3
+ # This snippet shows how to download sdk responses using http streaming.
4
+ # Streaming processes the download in chunks which you can write
5
+ # to file or perform other processing on without having to wait for the entire
6
+ # response to download first. Streaming can be very memory efficient
7
+ # for handling large result sets compared to just downloading the whole thing into
8
+ # a Ruby object in memory.
9
+
10
+ def run_look_to_file(look_id, filename, format, opts = {})
11
+ File.open(filename, 'w') do |file|
12
+ sdk.run_look(look_id, format, opts) do |data, progress|
13
+ file.write(data)
14
+ puts "Wrote #{data.length} bytes of #{progress.length} total"
15
+ end
16
+ end
17
+ end
18
+
19
+ # Replace the look id (38) with the id of your actual look
20
+ run_look_to_file(38, 'out.csv', 'csv', limit: 10000)
@@ -0,0 +1,6 @@
1
+ require './sdk_setup'
2
+
3
+ users = sdk.all_users(:fields => 'id, is_disabled, credentials_email').
4
+ select {|u| !u.is_diabled && u.credentials_email && u.credentials_email.email}
5
+
6
+ users.each {|u| puts "#{u.id},#{u.credentials_email.email}"}
@@ -0,0 +1,8 @@
1
+ # sdk.all_users(:fields => 'id,credentials_google').select{|u| u.credentials_google}.map{|u| u.id}
2
+
3
+ require './sdk_setup'
4
+
5
+ users = sdk.all_users(:fields => 'id, credentials_google').
6
+ select {|u| u.credentials_google}
7
+
8
+ users.each {|u| puts "#{u.id},#{u.credentials_google.email}"}
@@ -0,0 +1,6 @@
1
+ require './sdk_setup'
2
+
3
+ users = sdk.all_users(:fields => 'id, is_disabled, credentials_email, credentials_google').
4
+ select {|u| !u.is_diabled && u.credentials_google && u.credentials_google.email && !u.credentials_email}
5
+
6
+ users.each {|u| puts "#{u.id},#{u.credentials_google.email}"}
@@ -0,0 +1,32 @@
1
+ require 'looker-sdk/client'
2
+ require 'looker-sdk/default'
3
+
4
+ module LookerSDK
5
+
6
+ class << self
7
+ include LookerSDK::Configurable
8
+
9
+ # API client based on configured options {Configurable}
10
+ #
11
+ # @return [LookerSDK::Client] API wrapper
12
+ def client
13
+ @client = LookerSDK::Client.new(options) unless defined?(@client) && @client.same_options?(options)
14
+ @client
15
+ end
16
+
17
+ # @private
18
+ def respond_to_missing?(method_name, include_private=false); client.respond_to?(method_name, include_private); end if RUBY_VERSION >= "1.9"
19
+ # @private
20
+ def respond_to?(method_name, include_private=false); client.respond_to?(method_name, include_private) || super; end if RUBY_VERSION < "1.9"
21
+
22
+ private
23
+
24
+ def method_missing(method_name, *args, &block)
25
+ return super unless client.respond_to?(method_name)
26
+ client.send(method_name, *args, &block)
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+ LookerSDK.setup
@@ -0,0 +1,104 @@
1
+ module LookerSDK
2
+
3
+ # Authentication methods for {LookerSDK::Client}
4
+ module Authentication
5
+
6
+ attr_accessor :access_token_type, :access_token_expires_at
7
+
8
+ # This is called automatically by 'request'
9
+ def ensure_logged_in
10
+ authenticate unless token_authenticated? || @skip_authenticate
11
+ end
12
+
13
+ def without_authentication
14
+ begin
15
+ old_skip = @skip_authenticate || false
16
+ @skip_authenticate = true
17
+ yield
18
+ ensure
19
+ @skip_authenticate = old_skip
20
+ end
21
+ end
22
+
23
+ # Authenticate to the server and get an access_token for use in future calls.
24
+
25
+ def authenticate
26
+ raise "client_id and client_secret required" unless application_credentials?
27
+
28
+ set_access_token_from_params(nil)
29
+ without_authentication do
30
+ post('/login', {}, :query => application_credentials)
31
+ raise "login failure #{last_response.status}" unless last_response.status == 200
32
+ set_access_token_from_params(last_response.data)
33
+ end
34
+ end
35
+
36
+ def set_access_token_from_params(params)
37
+ reset_agent
38
+ if params
39
+ @access_token = params[:access_token]
40
+ @access_token_type = params[:token_type]
41
+ @access_token_expires_at = Time.now + params[:expires_in]
42
+ else
43
+ @access_token = @access_token_type = @access_token_expires_at = nil
44
+ end
45
+ end
46
+
47
+ def logout
48
+ without_authentication do
49
+ result = !!@access_token && ((delete('/logout') ; delete_succeeded?) rescue false)
50
+ set_access_token_from_params(nil)
51
+ result
52
+ end
53
+ end
54
+
55
+
56
+ # Indicates if the client has OAuth Application
57
+ # client_id and client_secret credentials
58
+ #
59
+ # @see look TODO docs link
60
+ # @return Boolean
61
+ def application_credentials?
62
+ !!application_credentials
63
+ end
64
+
65
+ # Indicates if the client has an OAuth
66
+ # access token
67
+ #
68
+ # @see look TODO docs link
69
+ # @return [Boolean]
70
+ def token_authenticated?
71
+ !!(@access_token && (@access_token_expires_at.nil? || @access_token_expires_at > Time.now))
72
+ end
73
+
74
+ private
75
+
76
+ def application_credentials
77
+ if @client_id && @client_secret
78
+ {
79
+ :client_id => @client_id,
80
+ :client_secret => @client_secret
81
+ }
82
+ end
83
+ end
84
+
85
+ def load_credentials_from_netrc
86
+ return unless netrc?
87
+
88
+ require 'netrc'
89
+ info = Netrc.read File.expand_path(netrc_file)
90
+ netrc_host = URI.parse(api_endpoint).host
91
+ creds = info[netrc_host]
92
+ if creds.nil?
93
+ # creds will be nil if there is no netrc for this end point
94
+ looker_warn "Error loading credentials from netrc file for #{api_endpoint}"
95
+ else
96
+ self.client_id = creds[0]
97
+ self.client_secret = creds[1]
98
+ end
99
+ rescue LoadError
100
+ looker_warn "Please install netrc gem for .netrc support"
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,445 @@
1
+ require 'sawyer'
2
+ require 'ostruct'
3
+ require 'looker-sdk/sawyer_patch'
4
+ require 'looker-sdk/configurable'
5
+ require 'looker-sdk/authentication'
6
+ require 'looker-sdk/rate_limit'
7
+ require 'looker-sdk/client/dynamic'
8
+
9
+ module LookerSDK
10
+
11
+ # Client for the LookerSDK API
12
+ #
13
+ # @see look TODO docs link
14
+ class Client
15
+
16
+ include LookerSDK::Authentication
17
+ include LookerSDK::Configurable
18
+ include LookerSDK::Client::Dynamic
19
+
20
+ # Header keys that can be passed in options hash to {#get},{#head}
21
+ CONVENIENCE_HEADERS = Set.new([:accept, :content_type])
22
+
23
+ def initialize(opts = {})
24
+ # Use options passed in, but fall back to module defaults
25
+ LookerSDK::Configurable.keys.each do |key|
26
+ instance_variable_set(:"@#{key}", opts[key] || LookerSDK.instance_variable_get(:"@#{key}"))
27
+ end
28
+
29
+ # allow caller to do configuration in a block before we load swagger and become dynamic
30
+ yield self if block_given?
31
+
32
+ # Save the original state of the options because live variables received later like access_token and
33
+ # client_id appear as if they are options and confuse the automatic client generation in LookerSDK#client
34
+ @original_options = options.dup
35
+
36
+ load_credentials_from_netrc unless application_credentials?
37
+ load_swagger
38
+ self.dynamic = true
39
+ end
40
+
41
+ # Compares client options to a Hash of requested options
42
+ #
43
+ # @param opts [Hash] Options to compare with current client options
44
+ # @return [Boolean]
45
+ def same_options?(opts)
46
+ opts.hash == @original_options.hash
47
+ end
48
+
49
+ # Text representation of the client, masking tokens and passwords
50
+ #
51
+ # @return [String]
52
+ def inspect
53
+ inspected = super
54
+
55
+ # Only show last 4 of token, secret
56
+ [@access_token, @client_secret].compact.each do |str|
57
+ len = [str.size - 4, 0].max
58
+ inspected = inspected.gsub! str, "#{'*'*len}#{str[len..-1]}"
59
+ end
60
+
61
+ inspected
62
+ end
63
+
64
+ # Make a HTTP GET request
65
+ #
66
+ # @param url [String] The path, relative to {#api_endpoint}
67
+ # @param options [Hash] Query and header params for request
68
+ # @param &block [Block] Block to be called with |response, chunk| for each chunk of the body from
69
+ # the server. The block must return true to continue, or false to abort streaming.
70
+ # @return [Sawyer::Resource]
71
+ def get(url, options = {}, &block)
72
+ request :get, url, nil, parse_query_and_convenience_headers(options), &block
73
+ end
74
+
75
+ # Make a HTTP POST request
76
+ #
77
+ # @param url [String] The path, relative to {#api_endpoint}
78
+ # @param data [String|Array|Hash] Body and optionally header params for request
79
+ # @param options [Hash] Optional header params for request
80
+ # @param &block [Block] Block to be called with |response, chunk| for each chunk of the body from
81
+ # the server. The block must return true to continue, or false to abort streaming.
82
+ # @return [Sawyer::Resource]
83
+ def post(url, data = {}, options = {}, &block)
84
+ request :post, url, data, parse_query_and_convenience_headers(options), &block
85
+ end
86
+
87
+ # Make a HTTP PUT request
88
+ #
89
+ # @param url [String] The path, relative to {#api_endpoint}
90
+ # @param data [String|Array|Hash] Body and optionally header params for request
91
+ # @param options [Hash] Optional header params for request
92
+ # @param &block [Block] Block to be called with |response, chunk| for each chunk of the body from
93
+ # the server. The block must return true to continue, or false to abort streaming.
94
+ # @return [Sawyer::Resource]
95
+ def put(url, data = {}, options = {}, &block)
96
+ request :put, url, data, parse_query_and_convenience_headers(options), &block
97
+ end
98
+
99
+ # Make a HTTP PATCH request
100
+ #
101
+ # @param url [String] The path, relative to {#api_endpoint}
102
+ # @param data [String|Array|Hash] Body and optionally header params for request
103
+ # @param options [Hash] Optional header params for request
104
+ # @param &block [Block] Block to be called with |response, chunk| for each chunk of the body from
105
+ # the server. The block must return true to continue, or false to abort streaming.
106
+ # @return [Sawyer::Resource]
107
+ def patch(url, data = {}, options = {}, &block)
108
+ request :patch, url, data, parse_query_and_convenience_headers(options), &block
109
+ end
110
+
111
+ # Make a HTTP DELETE request
112
+ #
113
+ # @param url [String] The path, relative to {#api_endpoint}
114
+ # @param options [Hash] Query and header params for request
115
+ # @return [Sawyer::Resource]
116
+ def delete(url, options = {}, &block)
117
+ request :delete, url, nil, parse_query_and_convenience_headers(options)
118
+ end
119
+
120
+ # Make a HTTP HEAD request
121
+ #
122
+ # @param url [String] The path, relative to {#api_endpoint}
123
+ # @param options [Hash] Query and header params for request
124
+ # @return [Sawyer::Resource]
125
+ def head(url, options = {}, &block)
126
+ request :head, url, nil, parse_query_and_convenience_headers(options)
127
+ end
128
+
129
+ # Make one or more HTTP GET requests, optionally fetching
130
+ # the next page of results from URL in Link response header based
131
+ # on value in {#auto_paginate}.
132
+ #
133
+ # @param url [String] The path, relative to {#api_endpoint}
134
+ # @param options [Hash] Query and header params for request
135
+ # @param block [Block] Block to perform the data concatination of the
136
+ # multiple requests. The block is called with two parameters, the first
137
+ # contains the contents of the requests so far and the second parameter
138
+ # contains the latest response.
139
+ # @return [Sawyer::Resource]
140
+ def paginate(url, options = {}, &block)
141
+ opts = parse_query_and_convenience_headers(options)
142
+ if @auto_paginate || @per_page
143
+ opts[:query][:per_page] ||= @per_page || (@auto_paginate ? 100 : nil)
144
+ end
145
+
146
+ data = request(:get, url, nil, opts)
147
+
148
+ if @auto_paginate
149
+ while @last_response.rels[:next] && rate_limit.remaining > 0
150
+ @last_response = @last_response.rels[:next].get
151
+ if block_given?
152
+ yield(data, @last_response)
153
+ else
154
+ data.concat(@last_response.data) if @last_response.data.is_a?(Array)
155
+ end
156
+ end
157
+
158
+ end
159
+
160
+ data
161
+ end
162
+
163
+ # Hypermedia agent for the LookerSDK API (with specific options)
164
+ #
165
+ # @return [Sawyer::Agent]
166
+ def make_agent(options = nil)
167
+ options ||= sawyer_options
168
+ Sawyer::Agent.new(api_endpoint, options) do |http|
169
+ http.headers[:accept] = default_media_type
170
+ http.headers[:user_agent] = user_agent
171
+ http.authorization('token', @access_token) if token_authenticated?
172
+ end
173
+ end
174
+
175
+ # Cached Hypermedia agent for the LookerSDK API (with default options)
176
+ #
177
+ # @return [Sawyer::Agent]
178
+ def agent
179
+ @agent ||= make_agent
180
+ end
181
+
182
+ # Fetch the root resource for the API
183
+ #
184
+ # @return [Sawyer::Resource]
185
+ def root
186
+ get URI(api_endpoint).path.sub(/\/$/,'')
187
+ end
188
+
189
+ # Is the server alive (this can be called w/o authentication)
190
+ #
191
+ # @return http status code
192
+ def alive
193
+ get '/alive'
194
+ last_response.status
195
+ end
196
+
197
+ # Response for last HTTP request
198
+ #
199
+ # @return [Sawyer::Response]
200
+ def last_response
201
+ @last_response if defined? @last_response
202
+ end
203
+
204
+ # Response for last HTTP request
205
+ #
206
+ # @return [StandardError]
207
+ def last_error
208
+ @last_error if defined? @last_error
209
+ end
210
+
211
+ # Set OAuth access token for authentication
212
+ #
213
+ # @param value [String] Looker OAuth access token
214
+ def access_token=(value)
215
+ reset_agent
216
+ @access_token = value
217
+ end
218
+
219
+ # Set OAuth app client_id
220
+ #
221
+ # @param value [String] Looker OAuth app client_id
222
+ def client_id=(value)
223
+ reset_agent
224
+ @client_id = value
225
+ end
226
+
227
+ # Set OAuth app client_secret
228
+ #
229
+ # @param value [String] Looker OAuth app client_secret
230
+ def client_secret=(value)
231
+ reset_agent
232
+ @client_secret = value
233
+ end
234
+
235
+ # Wrapper around Kernel#warn to print warnings unless
236
+ # LOOKER_SILENT is set to true.
237
+ #
238
+ # @return [nil]
239
+ def looker_warn(*message)
240
+ unless ENV['LOOKER_SILENT']
241
+ warn message
242
+ end
243
+ end
244
+
245
+ private
246
+
247
+ def reset_agent
248
+ @agent = nil
249
+ end
250
+
251
+ def request(method, path, data, options, &block)
252
+ ensure_logged_in
253
+ begin
254
+ @last_response = @last_error = nil
255
+ return stream_request(method, path, data, options, &block) if block_given?
256
+ @last_response = response = agent.call(method, URI::Parser.new.escape(path.to_s), data, options)
257
+ @raw_responses ? response : response.data
258
+ rescue StandardError => e
259
+ @last_error = e
260
+ raise
261
+ end
262
+ end
263
+
264
+ def stream_request(method, path, data, options, &block)
265
+ conn_opts = faraday_options(:builder => StreamingClient.new(self, &block))
266
+ agent = make_agent(sawyer_options(:faraday => Faraday.new(conn_opts)))
267
+ @last_response = agent.call(method, URI::Parser.new.escape(path.to_s), data, options)
268
+ end
269
+
270
+ # Since Faraday currently won't do streaming for us, we use Net::HTTP. Still, we go to the trouble
271
+ # to go through the Sawyer/Faraday codepath so that we can leverage all the header and param
272
+ # processing they do in order to be as consistent as we can with the normal non-streaming codepath.
273
+ # This class replaces the default Faraday 'builder' that Faraday uses to do the actual request after
274
+ # all the setup is done.
275
+
276
+ class StreamingClient
277
+ class Progress
278
+ attr_reader :response
279
+ attr_accessor :chunks, :length
280
+
281
+ def initialize(response)
282
+ @response = response
283
+ @chunks = @length = 0
284
+ @stopped = false
285
+ end
286
+
287
+ def add_chunk(chunk)
288
+ @chunks += 1
289
+ @length += chunk.length
290
+ end
291
+
292
+ def stop
293
+ @stopped = true
294
+ end
295
+
296
+ def stopped?
297
+ @stopped
298
+ end
299
+ end
300
+
301
+ def initialize(client, &block)
302
+ @client, @block = client, block
303
+ end
304
+
305
+ # This is the method that faraday calls on a builder to do the actual request and build a response.
306
+ def build_response(connection, request)
307
+ full_path = connection.build_exclusive_url(request.path, request.params,
308
+ request.options.params_encoder).to_s
309
+ uri = URI(full_path)
310
+ path_with_query = uri.query ? "#{uri.path}?#{uri.query}" : uri.path
311
+
312
+ http_request = (
313
+ case request.method
314
+ when :get then Net::HTTP::Get
315
+ when :post then Net::HTTP::Post
316
+ when :put then Net::HTTP::Put
317
+ when :patch then Net::HTTP::Patch
318
+ else raise "Stream to block not supported for '#{request.method}'"
319
+ end
320
+ ).new(path_with_query, request.headers)
321
+
322
+ http_request.body = request.body
323
+
324
+ connect_opts = {
325
+ :use_ssl => !!connection.ssl,
326
+ :verify_mode => (connection.ssl.verify rescue true) ?
327
+ OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE,
328
+ }
329
+
330
+ # TODO: figure out how/if to support proxies
331
+ # TODO: figure out how to test this comprehensively
332
+
333
+ progress = nil
334
+ Net::HTTP.start(uri.host, uri.port, connect_opts) do |http|
335
+ http.open_timeout = connection.options.open_timeout rescue 30
336
+ http.read_timeout = connection.options.timeout rescue 60
337
+
338
+ http.request(http_request) do |response|
339
+ progress = Progress.new(response)
340
+ if response.code == "200"
341
+ response.read_body do |chunk|
342
+ next unless chunk.length > 0
343
+ progress.add_chunk(chunk)
344
+ @block.call(chunk, progress)
345
+ return OpenStruct.new(status:"0", headers:{}, env:nil, body:nil) if progress.stopped?
346
+ end
347
+ end
348
+ end
349
+ end
350
+
351
+ return OpenStruct.new(status:"500", headers:{}, env:nil, body:nil) unless progress
352
+
353
+ OpenStruct.new(status:progress.response.code, headers:progress.response, env:nil, body:nil)
354
+ end
355
+ end
356
+
357
+ def delete_succeeded?
358
+ !!last_response && last_response.status == 204
359
+ end
360
+
361
+ class Serializer < Sawyer::Serializer
362
+ def encode(data)
363
+ data.kind_of?(Faraday::UploadIO) ? data : super
364
+ end
365
+
366
+ # slight modification to the base class' decode_hash_value function to
367
+ # less permissive when decoding time values.
368
+ #
369
+ # See https://github.com/looker/looker-sdk-ruby/issues/53 for more details
370
+ #
371
+ # Base class function that we're overriding: https://github.com/lostisland/sawyer/blob/master/lib/sawyer/serializer.rb#L101-L121
372
+ def decode_hash_value(key, value)
373
+ if time_field?(key, value) && value.is_a?(String)
374
+ begin
375
+ Time.iso8601(value)
376
+ rescue ArgumentError
377
+ value
378
+ end
379
+ else
380
+ super
381
+ end
382
+ end
383
+ end
384
+
385
+ def serializer
386
+ @serializer ||= (
387
+ require 'json'
388
+ Serializer.new(JSON)
389
+ )
390
+ end
391
+
392
+ def faraday_options(options = {})
393
+ conn_opts = @connection_options.clone
394
+ builder = options[:builder] || @middleware
395
+ conn_opts[:builder] = builder if builder
396
+ conn_opts[:proxy] = @proxy if @proxy
397
+ conn_opts
398
+ end
399
+
400
+ def sawyer_options(options = {})
401
+ {
402
+ :links_parser => Sawyer::LinkParsers::Simple.new,
403
+ :serializer => serializer,
404
+ :faraday => options[:faraday] || @faraday || Faraday.new(faraday_options)
405
+ }
406
+ end
407
+
408
+ def merge_content_type_if_body(body, options = {})
409
+ if body
410
+ if body.kind_of?(Faraday::UploadIO)
411
+ length = File.new(body.local_path).size.to_s
412
+ headers = {:content_type => body.content_type, :content_length => length}.merge(options[:headers] || {})
413
+ else
414
+ headers = {:content_type => default_media_type}.merge(options[:headers] || {})
415
+ end
416
+ {:headers => headers}.merge(options)
417
+ else
418
+ options
419
+ end
420
+ end
421
+
422
+ def parse_query_and_convenience_headers(options)
423
+ return {} if options.nil?
424
+ raise "options is not a hash" unless options.is_a?(Hash)
425
+ return {} if options.empty?
426
+
427
+ options = options.dup
428
+ headers = options.delete(:headers) || {}
429
+ CONVENIENCE_HEADERS.each do |h|
430
+ if header = options.delete(h)
431
+ headers[h] = header
432
+ end
433
+ end
434
+ query = options.delete(:query) || {}
435
+ raise "query '#{query}' is not a hash" unless query.is_a?(Hash)
436
+ query = options.merge(query)
437
+
438
+ opts = {}
439
+ opts[:query] = query unless query.empty?
440
+ opts[:headers] = headers unless headers.empty?
441
+
442
+ opts
443
+ end
444
+ end
445
+ end