Class: Bio::BaseSpace::APIClient

Inherits:
Object
  • Object
show all
Defined in:
lib/basespace/api/api_client.rb

Overview

This class provides wrapper methods to the BaseSpace API RESTful interface. It also handles serialization and deserialization of objects (Ruby to/from JSON). It is primarily used as a helper class for BaseSpaceAPI.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(access_token = nil, api_server = nil, timeout = 10) ⇒ APIClient

Create a new instance that will carry out REST calls.

access_token

Access token that is provided by App triggering.

api_server

URI of the BaseSpace API server.

timeout

Timeout for REST calls.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/basespace/api/api_client.rb', line 37

def initialize(access_token = nil, api_server = nil, timeout = 10)
  if $DEBUG
    $stderr.puts "    # ----- BaseAPI#initialize ----- "
    $stderr.puts "    # caller: #{caller}"
    $stderr.puts "    # access_token: #{access_token}"
    $stderr.puts "    # api_server: #{api_server}"
    $stderr.puts "    # timeout: #{timeout}"
    $stderr.puts "    # "
  end
  raise UndefinedParameterError.new('AccessToken') unless access_token
  @api_key = access_token
  @api_server = api_server
  @timeout = timeout
end

Instance Attribute Details

#api_keyObject

Returns the value of attribute api_key.



30
31
32
# File 'lib/basespace/api/api_client.rb', line 30

def api_key
  @api_key
end

#api_serverObject

Returns the value of attribute api_server.



30
31
32
# File 'lib/basespace/api/api_client.rb', line 30

def api_server
  @api_server
end

#timeoutObject

Returns the value of attribute timeout.



30
31
32
# File 'lib/basespace/api/api_client.rb', line 30

def timeout
  @timeout
end

Instance Method Details

#bool(value) ⇒ Object

Deserialize a boolean value to a Ruby object.

value

serialized representation of the boolean value.



98
99
100
101
102
103
104
105
106
# File 'lib/basespace/api/api_client.rb', line 98

def bool(value)
  case value
  when nil, false, 0, 'nil', 'false', '0', 'None'
    result = false
  else
    result = true
  end
  return result
end

#call_api(resource_path, method, query_params, post_data, header_params = nil, force_post = false) ⇒ Object

Carries out a RESTful operation on the BaseSpace API.

TODO Need check. Logic in this method is rather complicated…

resource_path

URI that should be used for the API call.

method

HTTP method for the rest call (GET, POST, DELETE, etc.)

query_params

query parameters that should be sent along to the API.

post_data

Hash that contains data to be transferred.

header_params

Additional settings that should be transferred in the HTTP header.

force_post

Truth value that indicates whether a POST should be forced.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/basespace/api/api_client.rb', line 118

def call_api(resource_path, method, query_params, post_data, header_params = nil, force_post = false)
  url = @api_server + resource_path

  headers = header_params.dup
  headers['Content-Type'] = 'application/json' if not headers.has_key?('Content-Type') and not method == 'PUT'
  # include access token in header
  headers['Authorization'] = "Bearer #{@api_key}"

  uri = request = response = data = cgi_params = nil

  if query_params
    # Need to remove None (in Python, nil in Ruby) values, these should not be sent
    sent_query_params = {}
    query_params.each do |param, value|
      sent_query_params[param] = value if bool(value)
    end
    cgi_params = hash2urlencode(sent_query_params)
  end

  if $DEBUG
    $stderr.puts "    # ----- APIClient#call_api ----- "
    $stderr.puts "    # caller: #{caller}"
    $stderr.puts "    # url: #{url}"
    $stderr.puts "    # method: #{method}"
    $stderr.puts "    # headers: #{headers}"
    $stderr.puts "    # cgi_params: #{cgi_params}"
    $stderr.puts "    # post_data: #{post_data}"
    $stderr.puts "    # "
  end

  case method
  when 'GET'
    if cgi_params
      url += "?#{cgi_params}"
    end
    uri = URI.parse(url)
    # https://www.ruby-forum.com/topic/4411398
    # In Ruby 1.9: Use Net::HTTP::Get.new(uri.path) or Net::HTTP::Get.new(uri.path + '?' + uri.query)
    # In Ruby 2.0: Use Net::HTTP::Get.new(uri)
    case RUBY_VERSION
    when /^1.9/
      if uri.query and not uri.query.empty?
        request = Net::HTTP::Get.new(uri.path + '?' + uri.query, headers)
      else
        request = Net::HTTP::Get.new(uri.path, headers)
      end
    else
      request = Net::HTTP::Get.new(uri, headers)
    end
  when 'POST', 'PUT', 'DELETE'
    if cgi_params
      force_post_url = url 
      url += "?#{cgi_params}"
    end
    if post_data
      # [TODO] Do we need to skip String, Integer, Float and bool also in Ruby?
      data = post_data # if not [str, int, float, bool].include?(type(post_data))
    end
    if force_post
      response = force_post_call(force_post_url, sent_query_params, headers)
    else
      data = {} if not data or (data and data.empty?) # temp fix, in case is no data in the file, to prevent post request from failing
      uri = URI.parse(url)
      case RUBY_VERSION
      when /^1.9/
        if uri.query and not uri.query.empty?
          request = Net::HTTP::Post.new(uri.path + '?' + uri.query, headers)
        else
          request = Net::HTTP::Post.new(uri.path, headers)
        end
      else
        request = Net::HTTP::Post.new(uri, headers)
      end
      if data.kind_of?(Hash) then
        request.set_form_data(data)
      else
        request.content_type = 'multipart/form-data'
        request.body = data
      end
    end
    if ['PUT', 'DELETE'].include?(method) # urllib doesnt do put and delete, default to pycurl here
      response = put_call(url, query_params, headers, data)
      response = response.split.last
    end
  else
    raise "Method #{method} is not recognized."
  end

  # Make the request, request may raise 403 forbidden, or 404 non-response
  if not force_post and not ['PUT', 'DELETE'].include?(method)  # the normal case
    # puts url
    # puts request
    # puts "request with timeout=#{@timeout}"
    # [TODO] confirm this works or not
    #response = urllib2.urlopen(request, @timeout).read()
    http_opts = {}
    if uri.scheme == "https"
      http_opts[:use_ssl] = true
    end
    response = Net::HTTP.start(uri.host, uri.port, http_opts) { |http|
      http.request(request)
    }
  end
          
  begin
    data = JSON.parse(response.body)
  rescue => err
    $stderr.puts "    # ----- APIClient#call_api ----- "
    $stderr.puts "    # Error: #{err}"
    $stderr.puts "    # "
    data = nil
  end
  return data
end

#deserialize(obj, obj_class) ⇒ Object

Deserialize a JSON string into an object.

obj

String or object to be deserialized.

obj_class

Class literal for deserialzied object, or string of class name.



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/basespace/api/api_client.rb', line 248

def deserialize(obj, obj_class)
  case obj_class.downcase
  when 'str'
    return obj.to_s
  when 'int'
    return obj.to_i
  when 'float'
    return obj.to_f
  when 'bool'
    return bool(obj)
  when 'file'
    # Bio::BaseSpace::File
    instance = File.new 
  else
    # models in BaseSpace
    klass = ::Bio::BaseSpace.const_get(obj_class)
    instance = klass.new
  end

  if $DEBUG
    $stderr.puts "    # ----- APIClient#deserialize ----- "
    $stderr.puts "    # caller: #{caller}"
    $stderr.puts "    # obj_class: #{obj_class}"
    $stderr.puts "    # obj: #{obj}"  # JSON.pretty_generate(obj)
    $stderr.puts "    # "
  end

  instance.swagger_types.each do |attr, attr_type|
    if obj.has_key?(attr) or obj.has_key?(attr.to_s)
      if $DEBUG
        $stderr.puts "    # # ----- APIClient#deserialize/swagger_types ----- "
        $stderr.puts "    # # attr: #{attr}"
        $stderr.puts "    # # attr_type: #{attr_type}"
        $stderr.puts "    # # value: #{obj[attr]}"
        $stderr.puts "    # # "
      end
      value = obj[attr]
      case attr_type.downcase
      when 'str'
        instance.set_attr(attr, value.to_s)
      when 'int'
        instance.set_attr(attr, value.to_i)
      when 'float'
        instance.set_attr(attr, value.to_f)
      when 'datetime'
        instance.set_attr(attr, DateTime.parse(value))
      when 'bool'
        instance.set_attr(attr, bool(value))
      when /list</
        sub_class = attr_type[/list<(.*)>/, 1]
        sub_values = []
        value.each do |sub_value|
          sub_values << deserialize(sub_value, sub_class)
        end
        instance.set_attr(attr, sub_values)
      when 'dict'  # support for parsing dictionary
        if $DEBUG
          $stderr.puts "    # # # ----- APIClient#deserialize/swagger_types/dict ----- "
          $stderr.puts "    # # # dict: #{value}"
          $stderr.puts "    # # # "
        end
        # [TODO] May need to convert value -> Hash (check in what format the value is passed)
        instance.set_attr(attr, value)
      else
        if $DEBUG
          # print "recursive call w/ " + attrType
          $stderr.puts "    # # # ----- APIClient#deserialize/swagger_types/recursive call ----- "
          $stderr.puts "    # # # attr: #{attr}"
          $stderr.puts "    # # # attr_type: #{attr_type}"
          $stderr.puts "    # # # value: #{value}"
          $stderr.puts "    # # # "
        end
        instance.set_attr(attr, deserialize(value, attr_type))
      end
    end
  end
  if $DEBUG
    $stderr.puts "    # # ----- APIClient#deserialize/instance ----- "
    $stderr.puts "    # # instance: #{instance.attributes.inspect}"
    $stderr.puts "    # # "
  end
  return instance
end

#force_post_call(resource_path, post_data, headers, data = nil) ⇒ Object

POST data to a provided URI.

resource_path

URI to which the data should be POSTed.

post_data

Hash that contains the data.

headers

Header of the POST call.

data

(unused; TODO)



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/basespace/api/api_client.rb', line 58

def force_post_call(resource_path, post_data, headers, data = nil)
  # [TODO] Confirm whether we can expect those two parameters are Hash objects:
  # headers = { "key" => "value" }
  # post_data = { "key" => "value" }
  uri = URI.parse(resource_path)
  # If headers are not needed, the following line should be enough:
  # return Net::HTTP.post_form(uri, post_data).body
  http_opts = {}
  if uri.scheme == "https"
    http_opts[:use_ssl] = true
  end
  res = Net::HTTP.start(uri.host, uri.port, http_opts) { |http|
    encoded_data = hash2urlencode(post_data)
    http.post(uri.path, encoded_data, headers)
  }
  return res.body
end

#hash2urlencode(hash) ⇒ Object

URL encode a Hash of data values.

hash

data encoded in a Hash.



79
80
81
82
83
# File 'lib/basespace/api/api_client.rb', line 79

def hash2urlencode(hash)
  # URI.escape (alias URI.encode) is obsolete since Ruby 1.9.2.
  #return hash.map{|k,v| URI.encode(k.to_s) + "=" + URI.encode(v.to_s)}.join("&")
  return hash.map{|k,v| URI.encode_www_form_component(k.to_s) + "=" + URI.encode_www_form_component(v.to_s)}.join("&")
end

#put_call(resource_path, post_data, headers, trans_file) ⇒ Object

Makes a PUT call to a given URI for depositing file contents.

resource_path

URI to which the data should be transferred.

post_data

(unused; TODO)

headers

Header of the PUT call.

trans_file

Path to the file that should be transferred.



91
92
93
# File 'lib/basespace/api/api_client.rb', line 91

def put_call(resource_path, post_data, headers, trans_file)
  return %x(curl -H "x-access-token:#{@api_key}" -H "Content-MD5:#{headers['Content-MD5'].strip}" -T "#{trans_file}" -X PUT #{resource_path})
end

#to_path_value(obj) ⇒ Object

Serialize a list to a CSV string, if necessary.

obj

Data object to be serialized.



236
237
238
239
240
241
242
# File 'lib/basespace/api/api_client.rb', line 236

def to_path_value(obj)
  if obj.kind_of?(Array)
    return obj.join(',')
  else
    return obj
  end
end