looker-sdk 0.0.5

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