bio-basespace-sdk 0.1.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bio-basespace-sdk might be problematic. Click here for more details.

Files changed (65) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +11 -0
  4. data/Gemfile +16 -0
  5. data/License.txt +275 -0
  6. data/README.md +671 -0
  7. data/Rakefile +54 -0
  8. data/VERSION +1 -0
  9. data/examples/0_app_triggering.rb +135 -0
  10. data/examples/1_authentication.rb +156 -0
  11. data/examples/2_browsing.rb +84 -0
  12. data/examples/3_accessing_files.rb +129 -0
  13. data/examples/4_app_result_upload.rb +102 -0
  14. data/examples/5_purchasing.rb +119 -0
  15. data/lib/basespace.rb +126 -0
  16. data/lib/basespace/api/api_client.rb +313 -0
  17. data/lib/basespace/api/base_api.rb +242 -0
  18. data/lib/basespace/api/basespace_api.rb +789 -0
  19. data/lib/basespace/api/basespace_error.rb +80 -0
  20. data/lib/basespace/api/billing_api.rb +115 -0
  21. data/lib/basespace/model.rb +78 -0
  22. data/lib/basespace/model/app_result.rb +158 -0
  23. data/lib/basespace/model/app_result_response.rb +40 -0
  24. data/lib/basespace/model/app_session.rb +99 -0
  25. data/lib/basespace/model/app_session_compact.rb +43 -0
  26. data/lib/basespace/model/app_session_launch_object.rb +58 -0
  27. data/lib/basespace/model/app_session_response.rb +41 -0
  28. data/lib/basespace/model/application.rb +47 -0
  29. data/lib/basespace/model/application_compact.rb +44 -0
  30. data/lib/basespace/model/basespace_model.rb +60 -0
  31. data/lib/basespace/model/coverage.rb +48 -0
  32. data/lib/basespace/model/coverage_meta_response.rb +40 -0
  33. data/lib/basespace/model/coverage_metadata.rb +43 -0
  34. data/lib/basespace/model/coverage_response.rb +40 -0
  35. data/lib/basespace/model/file.rb +172 -0
  36. data/lib/basespace/model/file_response.rb +40 -0
  37. data/lib/basespace/model/genome_response.rb +40 -0
  38. data/lib/basespace/model/genome_v1.rb +56 -0
  39. data/lib/basespace/model/list_response.rb +53 -0
  40. data/lib/basespace/model/multipart_upload.rb +288 -0
  41. data/lib/basespace/model/product.rb +50 -0
  42. data/lib/basespace/model/project.rb +103 -0
  43. data/lib/basespace/model/project_response.rb +40 -0
  44. data/lib/basespace/model/purchase.rb +89 -0
  45. data/lib/basespace/model/purchase_response.rb +39 -0
  46. data/lib/basespace/model/purchased_product.rb +56 -0
  47. data/lib/basespace/model/query_parameters.rb +86 -0
  48. data/lib/basespace/model/query_parameters_purchased_product.rb +67 -0
  49. data/lib/basespace/model/refund_purchase_response.rb +40 -0
  50. data/lib/basespace/model/resource_list.rb +48 -0
  51. data/lib/basespace/model/response_status.rb +42 -0
  52. data/lib/basespace/model/run_compact.rb +55 -0
  53. data/lib/basespace/model/sample.rb +127 -0
  54. data/lib/basespace/model/sample_response.rb +40 -0
  55. data/lib/basespace/model/user.rb +80 -0
  56. data/lib/basespace/model/user_compact.rb +45 -0
  57. data/lib/basespace/model/user_response.rb +40 -0
  58. data/lib/basespace/model/variant.rb +57 -0
  59. data/lib/basespace/model/variant_header.rb +44 -0
  60. data/lib/basespace/model/variant_info.rb +48 -0
  61. data/lib/basespace/model/variants_header_response.rb +40 -0
  62. data/spec/basespaceapi_spec.rb +26 -0
  63. data/spec/basespaceerror_spec.rb +87 -0
  64. data/spec/basespacemodel_spec.rb +57 -0
  65. metadata +239 -0
@@ -0,0 +1,242 @@
1
+ # Copyright 2013 Toshiaki Katayama, Joachim Baran
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+
14
+ require 'basespace/api/api_client'
15
+ require 'basespace/api/basespace_error'
16
+
17
+ require 'net/https'
18
+ require 'uri'
19
+
20
+ Net::HTTP.version_1_2
21
+
22
+ module Bio
23
+ module BaseSpace
24
+
25
+ # Parent class for BaseSpaceAPI and BillingAPI objects. It provides rudimentary
26
+ # API access functionality.
27
+ class BaseAPI
28
+
29
+ # Create a new BaseAPI instance using a given access token.
30
+ #
31
+ # +access_token+:: Access token provided by App triggering.
32
+ def initialize(access_token = nil)
33
+ if $DEBUG
34
+ $stderr.puts " # ----- BaseAPI#initialize ----- "
35
+ $stderr.puts " # caller: #{caller}"
36
+ $stderr.puts " # access_token: #{access_token}"
37
+ $stderr.puts " # "
38
+ end
39
+ @api_client = nil
40
+ set_timeout(10)
41
+ set_access_token(access_token) # logic for setting the access-token
42
+ end
43
+
44
+ # Set a new access token. The value of a previously supplied access token
45
+ # is simply overwritten here.
46
+ #
47
+ # +access_token+:: Access token provided by App triggering.
48
+ def update_access_token(access_token)
49
+ @api_client.api_key = access_token
50
+ end
51
+
52
+ # Make a single request to the BaseSpace REST API.
53
+ #
54
+ # +my_model+:: Class or classname of the model that is applied in deserialization.
55
+ # +resource_path+:: URI that should be used for the API call.
56
+ # +method+:: HTTP method for the rest call (GET, POST, DELETE, etc.)
57
+ # +query_params+:: query parameters that should be sent along to the API.
58
+ # +header_params+:: Additional settings that should be transferred in the HTTP header.
59
+ # +post_data+:: Hash that contains data to be transferred.
60
+ # +verbose+:: Truth value indicating whether verbose output should be provided.
61
+ # +force_post+:: Truth value that indicates whether a POST should be forced.
62
+ # +no_api+:: (unclear; TODO)
63
+ def single_request(my_model, resource_path, method, query_params, header_params, post_data = nil, verbose = false, force_post = false, no_api = true)
64
+ # test if access-token has been set
65
+ if not @api_client and no_api
66
+ raise 'Access-token not set, use the "set_access_token"-method to supply a token value'
67
+ end
68
+ # Make the API Call
69
+ if verbose or $DEBUG
70
+ $stderr.puts " # ----- BaseAPI#single_request ----- "
71
+ $stderr.puts " # caller: #{caller}"
72
+ $stderr.puts " # resource_path: #{resource_path}"
73
+ $stderr.puts " # method: #{method}"
74
+ $stderr.puts " # query_params: #{query_params}"
75
+ $stderr.puts " # post_data: #{post_data}"
76
+ $stderr.puts " # header_params: #{header_params}"
77
+ $stderr.puts " # force_post: #{force_post}"
78
+ $stderr.puts " # "
79
+ end
80
+ response = @api_client.call_api(resource_path, method, query_params, post_data, header_params, force_post)
81
+ if verbose or $DEBUG
82
+ $stderr.puts " # ----- BaseAPI#single_request ----- "
83
+ $stderr.puts " # response: #{response.inspect}"
84
+ $stderr.puts " # "
85
+ end
86
+ unless response
87
+ raise 'BaseSpace error: None response returned'
88
+ end
89
+
90
+ # throw exception here for various error messages
91
+ if response['ResponseStatus'].has_key?('ErrorCode')
92
+ raise "BaseSpace error: #{response['ResponseStatus']['ErrorCode']}: #{response['ResponseStatus']['Message']}"
93
+ end
94
+
95
+ # Create output objects if the response has more than one object
96
+ response_object = @api_client.deserialize(response, my_model)
97
+ return response_object.response
98
+ end
99
+
100
+ # Send a list request to the BaseSpace REST API.
101
+ #
102
+ # +my_model+:: Class or classname of the model that is applied in deserialization.
103
+ # +resource_path+:: URI that should be used for the API call.
104
+ # +method+:: HTTP method for the rest call (GET, POST, DELETE, etc.)
105
+ # +query_params+:: query parameters that should be sent along to the API.
106
+ # +header_params+:: Additional settings that should be transferred in the HTTP header.
107
+ # +verbose+:: Truth value indicating whether verbose output should be provided.
108
+ # +no_api+:: (unclear; TODO)
109
+ def list_request(my_model, resource_path, method, query_params, header_params, verbose = false, no_api = true)
110
+ # test if access-token has been set
111
+ if not @api_client and no_api
112
+ raise 'Access-token not set, use the "set_access_token"-method to supply a token value'
113
+ end
114
+
115
+ # Make the API Call
116
+ if verbose or $DEBUG
117
+ $stderr.puts " # ----- BaseAPI#list_request ----- "
118
+ $stderr.puts " # caller: #{caller}"
119
+ $stderr.puts " # Path: #{resource_path}"
120
+ $stderr.puts " # Pars: #{query_params}"
121
+ $stderr.puts " # "
122
+ end
123
+ response = @api_client.call_api(resource_path, method, query_params, nil, header_params) # post_data = nil
124
+ if verbose or $DEBUG
125
+ $stderr.puts " # ----- BaseAPI#list_request ----- "
126
+ $stderr.puts " # response: #{response.inspect}"
127
+ $stderr.puts " # "
128
+ end
129
+ unless response
130
+ raise "BaseSpace Exception: No data returned"
131
+ end
132
+ unless response.kind_of?(Array) # list
133
+ response = [response]
134
+ end
135
+ response_objects = []
136
+ response.each do |response_object|
137
+ response_objects << @api_client.deserialize(response_object, 'ListResponse')
138
+ end
139
+
140
+ # convert list response dict to object type
141
+ # TODO check that Response is present -- errors sometime don't include
142
+ # [TODO] check why the Python SDK only uses the first element in the response_objects
143
+ convertet = []
144
+ if response_object = response_objects.first
145
+ response_object.convert_to_object_list.each do |c|
146
+ convertet << @api_client.deserialize(c, my_model)
147
+ end
148
+ end
149
+ return convertet
150
+ end
151
+
152
+ # URL encode a Hash of data values.
153
+ #
154
+ # +hash+:: data encoded in a Hash.
155
+ def hash2urlencode(hash)
156
+ # URI.escape (alias URI.encode) is obsolete since Ruby 1.9.2.
157
+ #return hash.map{|k,v| URI.encode(k.to_s) + "=" + URI.encode(v.to_s)}.join("&")
158
+ return hash.map{|k,v| URI.encode_www_form_component(k.to_s) + "=" + URI.encode_www_form_component(v.to_s)}.join("&")
159
+ end
160
+
161
+ # Post data to the given BaseSpace API URL. Method name is a bit of a
162
+ # misnomer, because 'curl' is not used by this method -- instead only
163
+ # Ruby core classes are used.
164
+ #
165
+ # +data+:: Data that should be transferred to the API.
166
+ # +url+:: URL of the BaseSpace API.
167
+ def make_curl_request(data, url)
168
+ if $DEBUG
169
+ $stderr.puts " # ----- BaseAPI#make_curl_request ----- "
170
+ $stderr.puts " # caller: #{caller}"
171
+ $stderr.puts " # data: #{data}"
172
+ $stderr.puts " # url: #{url}"
173
+ $stderr.puts " # "
174
+ end
175
+ post = hash2urlencode(data)
176
+ uri = URI.parse(url)
177
+ #res = Net::HTTP.post_form(uri, post).body
178
+ http_opts = {}
179
+ if uri.scheme == "https"
180
+ http_opts[:use_ssl] = true
181
+ end
182
+ res = Net::HTTP.start(uri.host, uri.port, http_opts) { |http|
183
+ http.post(uri.path, post)
184
+ }
185
+ obj = JSON.parse(res.body)
186
+ if $DEBUG
187
+ $stderr.puts " # res: #{res}"
188
+ $stderr.puts " # obj: #{obj}"
189
+ $stderr.puts " # "
190
+ end
191
+ if obj.has_key?('error')
192
+ raise "BaseSpace exception: " + obj['error'] + " - " + obj['error_description']
193
+ end
194
+ return obj
195
+ end
196
+
197
+ # Return a string representation of this object.
198
+ def to_s
199
+ return "BaseSpaceAPI instance - using token=#{get_access_token}"
200
+ end
201
+
202
+ # Return a string representation of this object.
203
+ def to_str
204
+ return self.to_s
205
+ end
206
+
207
+ # Specify the timeout in seconds for each request.
208
+ #
209
+ # +param time+:: Timeout in second.
210
+ def set_timeout(time)
211
+ @timeout = time
212
+ if @api_client
213
+ @api_client.timeout = @timeout
214
+ end
215
+ end
216
+
217
+ # Sets a new API token.
218
+ #
219
+ # +token+:: New API token.
220
+ def set_access_token(token)
221
+ @api_client = nil
222
+ if token
223
+ @api_client = APIClient.new(token, @api_server, @timeout)
224
+ end
225
+ end
226
+
227
+ # Returns the access-token that was used to initialize the BaseSpaceAPI object.
228
+ def get_access_token
229
+ if @api_client
230
+ return @api_client.api_key
231
+ end
232
+ return "" # [TODO] Should return nil in Ruby?
233
+ end
234
+
235
+ # Returns the server URI used by this instance.
236
+ def get_server_uri
237
+ return @api_client.api_server
238
+ end
239
+ end
240
+
241
+ end # module BaseSpace
242
+ end # module Bio
@@ -0,0 +1,789 @@
1
+ # Copyright 2013 Toshiaki Katayama, Joachim Baran
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+
14
+ require 'basespace/api/api_client'
15
+ require 'basespace/api/base_api'
16
+ require 'basespace/api/basespace_error'
17
+ require 'basespace/model/query_parameters'
18
+
19
+ require 'net/https'
20
+ require 'uri'
21
+ require 'json'
22
+
23
+ Net::HTTP.version_1_2
24
+
25
+ module Bio
26
+ module BaseSpace
27
+
28
+ # The main API class used for all communication with the BaseSpace REST server.
29
+ class BaseSpaceAPI < BaseAPI
30
+
31
+ # URIs for obtaining a access token, user verification code, and app trigger information.
32
+ TOKEN_URL = '/oauthv2/token'
33
+ DEVICE_URL = "/oauthv2/deviceauthorization"
34
+ WEB_AUTHORIZE = '/oauth/authorize'
35
+
36
+ attr_reader :app_session_id
37
+
38
+ # Load credentials and start a new BaseSpace session.
39
+ def self.start
40
+ if opts = Bio::BaseSpace.load_credentials
41
+ self.new(opts['client_id'], opts['client_secret'], opts['basespace_url'], opts['api_version'], opts['app_session_id'], opts['access_token'])
42
+ else
43
+ raise "Please specify your BaseSpace credentials in the credentials.json file or use Bio::BaseSpace::BaseSpaceAPI.new with arguments"
44
+ end
45
+ end
46
+
47
+ # Create a new object for communicating with the BaseSpace REST server; preferred method of calling
48
+ # is through the 'start' class method.
49
+ #
50
+ # +client_key+:: Client key to use for authentication (provided when registering an App).
51
+ # +client_secret+:: Client secret key to use for authentication (provided when registering an App).
52
+ # +api_server+:: URI of the BaseSpace API server.
53
+ # +version+:: API version to use.
54
+ # +app_session_id+:: App session ID that was generated by application triggering.
55
+ # +access_token+:: Access token provided by App triggering.
56
+ def initialize(client_key, client_secret, api_server, version, app_session_id = nil, access_token = nil)
57
+ end_with_slash = %r(/$)
58
+ unless api_server[end_with_slash]
59
+ api_server += '/'
60
+ end
61
+
62
+ @app_session_id = app_session_id
63
+ @key = client_key
64
+ @secret = client_secret
65
+ @api_server = api_server + version
66
+ @version = version
67
+ @weburl = api_server.sub('api.', '')
68
+ @timeout = nil
69
+
70
+ super(access_token)
71
+ end
72
+
73
+ # This method is not for general use and should only be called from 'get_app_session'.
74
+ #
75
+ # +obj+:: Application trigger JSON.
76
+ def get_trigger_object(obj)
77
+ if obj['ResponseStatus'].has_key?('ErrorCode')
78
+ raise 'BaseSpace error: ' + obj['ResponseStatus']['ErrorCode'].to_s + ": " + obj['ResponseStatus']['Message']
79
+ end
80
+ #access_token = nil # '' is false in Python but APIClient.new only raises when the value is None (not '')
81
+ access_token = ''
82
+ temp_api = APIClient.new(access_token, @api_server)
83
+ response = temp_api.deserialize(obj, 'AppSessionResponse')
84
+ # AppSessionResponse object has a response method which returns a AppSession object
85
+ app_sess = response.get_attr('Response')
86
+ # AppSession object has a serialize_references method which converts an array of
87
+ # AppSessionLaunchObject objects by calling serialize_object method in each object.
88
+ # The method in turn calls the serialize_object method of the given BaseSpaceAPI object
89
+ # with @content ('dict') and @type ('str') arguments. Returns an array of serialized objects.
90
+ res = app_sess.serialize_references(self)
91
+ return res
92
+ end
93
+
94
+ def serialize_object(d, type)
95
+ # [TODO] None (nil) or '' ?
96
+ #access_token = nil
97
+ access_token = ''
98
+ temp_api = APIClient.new(access_token, @api_server)
99
+ if type.downcase == 'project'
100
+ return temp_api.deserialize(d, 'Project')
101
+ end
102
+ if type.downcase == 'sample'
103
+ return temp_api.deserialize(d, 'Sample')
104
+ end
105
+ if type.downcase == 'appresult'
106
+ return temp_api.deserialize(d, 'AppResult')
107
+ end
108
+ return d
109
+ end
110
+
111
+
112
+ # Returns the AppSession instance identified by the given ID.
113
+ #
114
+ # +id+:: The ID of the AppSession.
115
+ def get_app_session_by_id(id)
116
+ # TO_DO make special case for access-token only retrieval
117
+ return get_app_session(id)
118
+ end
119
+
120
+ # Returns an AppSession instance containing user and data-type the app was triggered by/on.
121
+ #
122
+ # +id+:: The AppSessionId, ID not supplied the AppSessionId used for instantiating the BaseSpaceAPI instance.
123
+ def get_app_session(id = nil)
124
+ if (not @app_session_id) and (not id)
125
+ raise "This BaseSpaceAPI instance has no app_session_id set and no alternative id was supplied for method get_app_session"
126
+ end
127
+
128
+ # if (not id) and (not @key)
129
+ # raise "This BaseSpaceAPI instance has no client_secret (key) set and no alternative id was supplied for method get_app_session"
130
+ # end
131
+
132
+ resource_path = @api_server + '/appsessions/{AppSessionId}'
133
+ if id
134
+ resource_path = resource_path.sub('{AppSessionId}', id)
135
+ else
136
+ resource_path = resource_path.sub('{AppSessionId}', @app_session_id)
137
+ end
138
+ if $DEBUG
139
+ $stderr.puts " # ----- BaseSpaceAPI#get_app_session ----- "
140
+ $stderr.puts " # resource_path: #{resource_path}"
141
+ $stderr.puts " # "
142
+ end
143
+ uri = URI.parse(resource_path)
144
+ uri.user = @key
145
+ uri.password = @secret
146
+ #response = Net::HTTP.get(uri)
147
+ http_opts = {}
148
+ if uri.scheme == "https"
149
+ http_opts[:use_ssl] = true
150
+ end
151
+ response = Net::HTTP.start(uri.host, uri.port, http_opts) { |http|
152
+ request = Net::HTTP::Get.new(uri.path)
153
+ request.basic_auth uri.user, uri.password
154
+ http.request(request)
155
+ }
156
+ obj = JSON.parse(response.body)
157
+ # TODO add exception if response isn't OK, e.g. incorrect server gives path not recognized
158
+ return get_trigger_object(obj)
159
+ end
160
+
161
+ # Request access to a data object.
162
+ #
163
+ # +obj+:: The data object we wish to get access to.
164
+ # +access_type+:: The type of access (read|write), default is write.
165
+ # +web+:: True if the App is web-based, default is false meaning a device based App.
166
+ # +redirect_url+:: For the web-based case, a redirection URL.
167
+ # +state+:: (unclear from Python port)
168
+ def get_access(obj, access_type = 'write', web = nil, redirect_url = nil, state = nil)
169
+ scope_str = obj.get_access_str(access_type)
170
+ if web
171
+ return get_web_verification_code(scope_str, redirect_url, state)
172
+ else
173
+ return get_verification_code(scope_str)
174
+ end
175
+ end
176
+
177
+ # Returns the BaseSpace dictionary containing the verification code and verification URL for the user to approve
178
+ # access to a specific data scope.
179
+ #
180
+ # Corresponding curl call:
181
+ # curlCall = 'curl -d "response_type=device_code" -d "client_id=' + client_key + '" -d "scope=' + scope + '" ' + DEVICE_URL
182
+ #
183
+ # For details see:
184
+ # https://developer.basespace.illumina.com/docs/content/documentation/authentication/obtaining-access-tokens
185
+ #
186
+ # +scope+:: The scope that access is requested for.
187
+ def get_verification_code(scope)
188
+ #curlCall = 'curl -d "response_type=device_code" -d "client_id=' + @key + '" -d "scope=' + scope + '" ' + @api_server + DEVICE_URL
189
+ #puts curlCall
190
+ unless @key
191
+ raise "This BaseSpaceAPI instance has no client_secret (key) set and no alternative id was supplied for method get_verification_code"
192
+ end
193
+ data = {'client_id' => @key, 'scope' => scope, 'response_type' => 'device_code'}
194
+ return make_curl_request(data, @api_server + DEVICE_URL)
195
+ end
196
+
197
+ # Generates the URL the user should be redirected to for web-based authentication.
198
+ #
199
+ # +scope+: The scope that access is requested for.
200
+ # +redirect_url+:: The redirect URL.
201
+ # +state+:: An optional state parameter that will passed through to the redirect response.
202
+ def get_web_verification_code(scope, redirect_url, state = nil)
203
+ if (not @key)
204
+ raise "This BaseSpaceAPI instance has no client_id (key) set and no alternative id was supplied for method get_verification_code"
205
+ end
206
+ data = {'client_id' => @key, 'redirect_uri' => redirect_url, 'scope' => scope, 'response_type' => 'code', "state" => state}
207
+ return @weburl + WEB_AUTHORIZE + '?' + hash2urlencode(data)
208
+ end
209
+
210
+ # Returns a user specific access token.
211
+ #
212
+ # +device_code+:: The device code returned by the verification code method.
213
+ def obtain_access_token(device_code)
214
+ if (not @key) or (not @secret)
215
+ raise "This BaseSpaceAPI instance has either no client_secret or no client_id set and no alternative id was supplied for method get_verification_code"
216
+ end
217
+ data = {'client_id' => @key, 'client_secret' => @secret, 'code' => device_code, 'grant_type' => 'device', 'redirect_uri' => 'google.com'}
218
+ dict = make_curl_request(data, @api_server + TOKEN_URL)
219
+ return dict['access_token']
220
+ end
221
+
222
+ def update_privileges(code)
223
+ token = obtain_access_token(code)
224
+ set_access_token(token)
225
+ end
226
+
227
+ # Creates a project with the specified name and returns a project object.
228
+ # If a project with this name already exists, the existing project is returned.
229
+ #
230
+ # +name+:: Name of the project.
231
+ def create_project(name)
232
+ #: v1pre3/projects, it requires 1 input parameter which is Name
233
+ my_model = 'ProjectResponse'
234
+ resource_path = '/projects/'
235
+ resource_path = resource_path.sub('{format}', 'json')
236
+ method = 'POST'
237
+ query_params = {}
238
+ header_params = {}
239
+ post_data = {}
240
+ post_data['Name'] = name
241
+ verbose = false
242
+ return single_request(my_model, resource_path, method, query_params, header_params, post_data, verbose)
243
+ end
244
+
245
+ # Returns the User object corresponding to ID.
246
+ #
247
+ # +id+:: The ID of the user.
248
+ def get_user_by_id(id)
249
+ my_model = 'UserResponse'
250
+ resource_path = '/users/{Id}'
251
+ resource_path = resource_path.sub('{format}', 'json')
252
+ resource_path = resource_path.sub('{Id}', id)
253
+ method = 'GET'
254
+ query_params = {}
255
+ header_params = {}
256
+ return single_request(my_model, resource_path, method, query_params, header_params)
257
+ end
258
+
259
+ # Returns an AppResult object corresponding to ID.
260
+ #
261
+ # +param id+:: The ID of the AppResult.
262
+ def get_app_result_by_id(id)
263
+ my_model = 'AppResultResponse'
264
+ resource_path = '/appresults/{Id}'
265
+ resource_path = resource_path.sub('{format}', 'json')
266
+ resource_path = resource_path.sub('{Id}', id)
267
+ method = 'GET'
268
+ query_params = {}
269
+ header_params = {}
270
+ return single_request(my_model, resource_path, method, query_params, header_params)
271
+ end
272
+
273
+ # Returns a list of File objects for the AppResult with the given ID.
274
+ #
275
+ # +id+:: The ID of the AppResult.
276
+ # +qp: An object of type QueryParameters for custom sorting and filtering.
277
+ def get_app_result_files(id, qp = {})
278
+ query_pars = qp.kind_of?(Hash) ? QueryParameters.new(qp) : qp
279
+ query_pars.validate
280
+ my_model = 'File'
281
+ resource_path = '/appresults/{Id}/files'
282
+ resource_path = resource_path.sub('{format}', 'json')
283
+ resource_path = resource_path.sub('{Id}', id)
284
+ method = 'GET'
285
+ query_params = query_pars.get_parameter_dict
286
+ header_params = {}
287
+ verbose = false
288
+ return list_request(my_model, resource_path, method, query_params, header_params, verbose)
289
+ end
290
+
291
+ # Request a project object by ID.
292
+ #
293
+ # +id+:: The ID of the project.
294
+ def get_project_by_id(id)
295
+ my_model = 'ProjectResponse'
296
+ resource_path = '/projects/{Id}'
297
+ resource_path = resource_path.sub('{format}', 'json')
298
+ resource_path = resource_path.sub('{Id}', id)
299
+ method = 'GET'
300
+ query_params = {}
301
+ header_params = {}
302
+ return single_request(my_model, resource_path, method, query_params, header_params)
303
+ end
304
+
305
+ # Returns a list available projects for a User with the specified ID.
306
+ #
307
+ # +id+:: The ID of the user.
308
+ # +qp+:: An object of type QueryParameters for custom sorting and filtering.
309
+ def get_project_by_user(id, qp = {})
310
+ query_pars = qp.kind_of?(Hash) ? QueryParameters.new(qp) : qp
311
+ query_pars.validate
312
+ my_model = 'Project'
313
+ resource_path = '/users/{Id}/projects'
314
+ resource_path = resource_path.sub('{format}', 'json')
315
+ resource_path = resource_path.sub('{Id}', id) if id != nil
316
+ method = 'GET'
317
+ query_params = query_pars.get_parameter_dict
318
+ header_params = {}
319
+ return list_request(my_model, resource_path, method, query_params, header_params)
320
+ end
321
+
322
+ # Returns a list of accessible runs for the User with the given ID.
323
+ #
324
+ # +id+:: User id.
325
+ # +qp+:: An object of type QueryParameters for custom sorting and filtering.
326
+ def get_accessible_runs_by_user(id, qp = {})
327
+ query_pars = qp.kind_of?(Hash) ? QueryParameters.new(qp) : qp
328
+ query_pars.validate
329
+ my_model = 'RunCompact'
330
+ resource_path = '/users/{Id}/runs'
331
+ resource_path = resource_path.sub('{format}', 'json')
332
+ resource_path = resource_path.sub('{Id}', id)
333
+ method = 'GET'
334
+ query_params = query_pars.get_parameter_dict
335
+ header_params = {}
336
+ return list_request(my_model, resource_path, method, query_params, header_params)
337
+ end
338
+
339
+ # Returns a list of AppResult object associated with the project with ID.
340
+ #
341
+ # +id+:: The project ID.
342
+ # +qp+:: An object of type QueryParameters for custom sorting and filtering.
343
+ # +statuses+:: A list of AppResult statuses to filter by.
344
+ def get_app_results_by_project(id, qp = {}, statuses = [])
345
+ query_pars = qp.kind_of?(Hash) ? QueryParameters.new(qp) : qp
346
+ query_pars.validate
347
+ my_model = 'AppResult'
348
+ resource_path = '/projects/{Id}/appresults'
349
+ resource_path = resource_path.sub('{format}', 'json')
350
+ resource_path = resource_path.sub('{Id}', id)
351
+ method = 'GET'
352
+ query_params = query_pars.get_parameter_dict
353
+ unless statuses.empty?
354
+ query_params['Statuses'] = statuses.join(",")
355
+ end
356
+ header_params = {}
357
+ verbose = false
358
+ return list_request(my_model, resource_path, method, query_params, header_params, verbose)
359
+ end
360
+
361
+ # Returns a list of samples associated with a project with ID.
362
+ #
363
+ # +id+:: The ID of the project.
364
+ # +qp+:: An object of type QueryParameters for custom sorting and filtering.
365
+ def get_samples_by_project(id, qp = {})
366
+ query_pars = qp.kind_of?(Hash) ? QueryParameters.new(qp) : qp
367
+ query_pars.validate
368
+ my_model = 'Sample'
369
+ resource_path = '/projects/{Id}/samples'
370
+ resource_path = resource_path.sub('{format}', 'json')
371
+ resource_path = resource_path.sub('{Id}', id)
372
+ method = 'GET'
373
+ query_params = query_pars.get_parameter_dict
374
+ header_params = {}
375
+ verbose = false
376
+ return list_request(my_model, resource_path, method, query_params, header_params, verbose)
377
+ end
378
+
379
+ # Returns a Sample object.
380
+ #
381
+ # +id+:: The ID of the sample.
382
+ def get_sample_by_id(id)
383
+ my_model = 'SampleResponse'
384
+ resource_path = '/samples/{Id}'
385
+ resource_path = resource_path.sub('{format}', 'json')
386
+ resource_path = resource_path.sub('{Id}', id)
387
+ method = 'GET'
388
+ query_params = {}
389
+ header_params = {}
390
+ post_data = nil
391
+ verbose = false
392
+ return single_request(my_model, resource_path, method, query_params, header_params, post_data, verbose)
393
+ end
394
+
395
+ # Returns a list of File objects associated with sample with ID.
396
+ #
397
+ # +id+:: Sample ID.
398
+ # +qp+:: An object of type QueryParameters for custom sorting and filtering.
399
+ def get_files_by_sample(id, qp = {})
400
+ query_pars = qp.kind_of?(Hash) ? QueryParameters.new(qp) : qp
401
+ query_pars.validate
402
+ my_model = 'File'
403
+ resource_path = '/samples/{Id}/files'
404
+ resource_path = resource_path.sub('{format}', 'json')
405
+ resource_path = resource_path.sub('{Id}', id)
406
+ method = 'GET'
407
+ query_params = query_pars.get_parameter_dict
408
+ header_params = {}
409
+ verbose = false
410
+ return list_request(my_model, resource_path, method, query_params, header_params, verbose)
411
+ end
412
+
413
+ # Returns a file object by ID.
414
+ #
415
+ # +id+:: The ID of the file.
416
+ def get_file_by_id(id)
417
+ my_model = 'FileResponse'
418
+ resource_path = '/files/{Id}'
419
+ resource_path = resource_path.sub('{format}', 'json')
420
+ resource_path = resource_path.sub('{Id}', id)
421
+ method = 'GET'
422
+ query_params = {}
423
+ header_params = {}
424
+ post_data = nil
425
+ verbose = false
426
+ return single_request(my_model, resource_path, method, query_params, header_params, post_data, verbose)
427
+ end
428
+
429
+ # Returns an instance of Genome with the specified ID.
430
+ #
431
+ # +id+:: The genome ID.
432
+ def get_genome_by_id(id)
433
+ my_model = 'GenomeResponse'
434
+ resource_path = '/genomes/{Id}'
435
+ resource_path = resource_path.sub('{format}', 'json')
436
+ resource_path = resource_path.sub('{Id}', id)
437
+ method = 'GET'
438
+ query_params = {}
439
+ header_params = {}
440
+ return single_request(my_model, resource_path, method, query_params, header_params)
441
+ end
442
+
443
+ # Returns a list of all available genomes.
444
+ #
445
+ # +qp+:: An object of type QueryParameters for custom sorting and filtering.
446
+ def get_available_genomes(qp = {})
447
+ query_pars = qp.kind_of?(Hash) ? QueryParameters.new(qp) : qp
448
+ query_pars.validate
449
+ my_model = 'GenomeV1'
450
+ resource_path = '/genomes'
451
+ resource_path = resource_path.sub('{format}', 'json')
452
+ method = 'GET'
453
+ query_params = query_pars.get_parameter_dict
454
+ header_params = {}
455
+ verbose = false
456
+ return list_request(my_model, resource_path, method, query_params, header_params, verbose)
457
+ end
458
+
459
+ # TODO, needs more work in parsing meta data, currently only map keys are returned.
460
+
461
+ # Returns a VariantMetadata object for a variant file.
462
+ #
463
+ # +id+:: ID of the VCF file.
464
+ # +format+:: Set to 'vcf' to get the results as lines in VCF format.
465
+ def get_variant_metadata(id, format)
466
+ my_model = 'VariantsHeaderResponse'
467
+ resource_path = '/variantset/{Id}'
468
+ resource_path = resource_path.sub('{format}', 'json')
469
+ resource_path = resource_path.sub('{Id}', id)
470
+ method = 'GET'
471
+ query_params = {}
472
+ query_params['Format'] = @api_client.to_path_value(format)
473
+ header_params = {}
474
+ verbose = false
475
+ return single_request(my_model, resource_path, method, query_params, header_params, verbose)
476
+ end
477
+
478
+ # List the variants in a set of variants. Maximum returned records is 1000.
479
+ #
480
+ # +id+:: ID of the variant file.
481
+ # +chrom+:: The chromosome of interest.
482
+ # +start_pos+:: The start position of the sequence of interest.
483
+ # +end_pos+:: The start position of the sequence of interest.
484
+ # +format+:: Set to 'vcf' to get the results as lines in VCF format.
485
+ # +qp+:: An (optional) object of type QueryParameters for custom sorting and filtering.
486
+ def filter_variant_set(id, chrom, start_pos, end_pos, format, qp = {'SortBy' => 'Position'})
487
+ query_pars = qp.kind_of?(Hash) ? QueryParameters.new(qp) : qp
488
+ query_pars.validate
489
+ my_model = 'Variant'
490
+ resource_path = '/variantset/{Id}/variants/chr{Chrom}'
491
+ resource_path = resource_path.sub('{format}', 'json')
492
+ resource_path = resource_path.sub('{Chrom}', chrom)
493
+ resource_path = resource_path.sub('{Id}', id)
494
+ method = 'GET'
495
+ query_params = query_pars.get_parameter_dict
496
+ header_params = {}
497
+ query_params['StartPos'] = start_pos
498
+ query_params['EndPos'] = end_pos
499
+ query_params['Format'] = format
500
+ verbose = false
501
+ return list_request(my_model, resource_path, method, query_params, header_params, verbose)
502
+ end
503
+
504
+ # Mean coverage levels over a sequence interval. Returns an instance of CoverageResponse.
505
+ #
506
+ # +id+:: Chromosome to query.
507
+ # +chrom+:: The ID of the resource.
508
+ # +start_pos+:: Get coverage starting at this position. Default is 1.
509
+ # +end_pos+:: Get coverage up to and including this position. Default is start_pos + 1280.
510
+ def get_interval_coverage(id, chrom, start_pos = nil, end_pos = nil)
511
+ my_model = 'CoverageResponse'
512
+ resource_path = '/coverage/{Id}/{Chrom}'
513
+ resource_path = resource_path.sub('{format}', 'json')
514
+ resource_path = resource_path.sub('{Chrom}', chrom)
515
+ resource_path = resource_path.sub('{Id}', id)
516
+ method = 'GET'
517
+ query_params = {}
518
+ header_params = {}
519
+ query_params['StartPos'] = @api_client.to_path_value(start_pos)
520
+ query_params['EndPos'] = @api_client.to_path_value(end_pos)
521
+ post_data = nil
522
+ verbose = false
523
+ return single_request(my_model, resource_path, method, query_params, header_params, post_data, verbose)
524
+ end
525
+
526
+ # Returns Metadata about coverage as a CoverageMetadata instance.
527
+ #
528
+ # +id+:: ID of a BAM file.
529
+ # +chrom+:: Chromosome to query.
530
+ def get_coverage_meta_info(id, chrom)
531
+ my_model = 'CoverageMetaResponse'
532
+ resource_path = '/coverage/{Id}/{Chrom}/meta'
533
+ resource_path = resource_path.sub('{format}', 'json')
534
+ resource_path = resource_path.sub('{Chrom}', chrom)
535
+ resource_path = resource_path.sub('{Id}', id)
536
+ method = 'GET'
537
+ query_params = {}
538
+ header_params = {}
539
+ post_data = nil
540
+ verbose = false
541
+ return single_request(my_model, resource_path, method, query_params, header_params, post_data, verbose)
542
+ end
543
+
544
+ # Create an AppResult object.
545
+ #
546
+ # +id+:: ID of the project in which the AppResult is to be added.
547
+ # +name+:: The name of the AppResult.
548
+ # +desc+:: A describtion of the AppResult.
549
+ # +samples+:: List of samples (if any).
550
+ # +app_session_id+:: If no app_session_id is given, the id used to initialize the BaseSpaceAPI instance will be used. If app_session_id is set equal to an empty string, a new appsession will be created.
551
+ def create_app_result(id, name, desc, samples = [], app_session_id = nil)
552
+ if (not @app_session_id) and (not app_session_id)
553
+ raise "This BaseSpaceAPI instance has no app_session_id set and no alternative id was supplied for method create_app_result"
554
+ end
555
+
556
+ my_model = 'AppResultResponse'
557
+ resource_path = '/projects/{ProjectId}/appresults'
558
+ resource_path = resource_path.sub('{format}', 'json')
559
+ resource_path = resource_path.sub('{ProjectId}', id)
560
+ method = 'POST'
561
+ query_params = {}
562
+ header_params = {}
563
+ post_data = {}
564
+ verbose = false
565
+
566
+ if app_session_id
567
+ query_params['appsessionid'] = app_session_id
568
+ else
569
+ query_params['appsessionid'] = @app_session_id # default case, we use the current appsession
570
+ end
571
+
572
+ # add the sample references
573
+ if samples.length > 0
574
+ ref = []
575
+ samples.each do |s|
576
+ d = { "Rel" => "using", "Type" => "Sample", "HrefContent" => @version + '/samples/' + s.id }
577
+ ref << d
578
+ end
579
+ post_data['References'] = ref
580
+ end
581
+
582
+ # case, an appSession is provided, we need to check if the a
583
+ if query_params.has_key?('appsessionid')
584
+ sid = query_params['appsessionid']
585
+ session = get_app_session(sid)
586
+ unless session.can_work_on
587
+ raise 'AppSession status must be "running," to create and AppResults. Current status is ' + session.status
588
+ end
589
+ end
590
+
591
+ post_data['Name'] = name
592
+ post_data['Description'] = desc
593
+ return single_request(my_model, resource_path, method, query_params, header_params, post_data, verbose)
594
+ end
595
+
596
+ # Uploads a file associated with an AppResult to BaseSpace and returns the corresponding file object.
597
+ #
598
+ # +id+:: AppResult ID.
599
+ # +local_path+:: The local path to the file to be uploaded.
600
+ # +file_name+:: The desired filename in the AppResult folder on the BaseSpace server.
601
+ # +directory+:: The directory the file should be placed in.
602
+ # +content_type+:: The content-type of the file.
603
+ def app_result_file_upload(id, local_path, file_name, directory, content_type, multipart = 0)
604
+ my_model = 'FileResponse'
605
+ resource_path = '/appresults/{Id}/files'
606
+ resource_path = resource_path.sub('{format}', 'json')
607
+ resource_path = resource_path.sub('{Id}', id)
608
+ method = 'POST'
609
+ query_params = {}
610
+ header_params = {}
611
+ verbose = false
612
+
613
+ query_params['name'] = file_name
614
+ query_params['directory'] = directory
615
+ header_params['Content-Type'] = content_type
616
+
617
+ # three cases, two for multipart, starting
618
+ if multipart == 1
619
+ query_params['multipart'] = 'true'
620
+ post_data = nil
621
+ force_post = true
622
+ # Set force post as this need to use POST though no data is being streamed
623
+ return single_request(my_model, resource_path, method, query_params, header_params, post_data, verbose, force_post)
624
+ elsif multipart == 2
625
+ query_params = {'uploadstatus' => 'complete'}
626
+ post_data = nil
627
+ force_post = true
628
+ # Set force post as this need to use POST though no data is being streamed
629
+ return single_request(my_model, resource_path, method, query_params, header_params, post_data, verbose, force_post)
630
+ else
631
+ post_data = File.open(local_path).read
632
+ return single_request(my_model, resource_path, method, query_params, header_params, post_data, verbose)
633
+ end
634
+ end
635
+
636
+ # Downloads a BaseSpace file to a local directory.
637
+ #
638
+ # +id+:: File ID.
639
+ # +local_dir+:: The local directory to place the file in.
640
+ # +name+:: The name of the local file.
641
+ # +range+:: The byte range of the file to retrieve (not yet implemented).
642
+ def file_download(id, local_dir, name, range = []) #@ReservedAssignment
643
+ resource_path = '/files/{Id}/content'
644
+ resource_path = resource_path.sub('{format}', 'json')
645
+ resource_path = resource_path.sub('{Id}', id)
646
+ method = 'GET'
647
+ query_params = {}
648
+ header_params = {}
649
+
650
+ query_params['redirect'] = 'meta' # we need to add this parameter to get the Amazon link directly
651
+
652
+ response = @api_client.call_api(resource_path, method, query_params, nil, header_params)
653
+ if response['ResponseStatus'].has_key?('ErrorCode')
654
+ raise 'BaseSpace error: ' + response['ResponseStatus']['ErrorCode'].to_s + ": " + response['ResponseStatus']['Message']
655
+ end
656
+
657
+ # get the Amazon URL
658
+ file_url = response['Response']['HrefContent']
659
+
660
+ header = nil
661
+ unless range.empty?
662
+ # puts "Case range request"
663
+ header = { 'Range' => format('bytes=%s-%s', range[0], range[1]) }
664
+ end
665
+
666
+ # Do the download
667
+ File.open(File.join(local_dir, name), "wb") do |fp|
668
+ http_opts = {}
669
+ if uri.scheme == "https"
670
+ http_opts[:use_ssl] = true
671
+ end
672
+ uri = URI.parse(file_url)
673
+ res = Net::HTTP.start(uri.host, uri.port, http_opts) { |http|
674
+ # [TODO] Do we need user and pass here also?
675
+ http.get(uri.path, header)
676
+ }
677
+ fp.print res.body
678
+ end
679
+
680
+ return 1
681
+ end
682
+
683
+ # Returns URL of a file on S3.
684
+ #
685
+ # +id+:: File ID.
686
+ def file_url(id) # @ReservedAssignment
687
+ resource_path = '/files/{Id}/content'
688
+ resource_path = resource_path.sub('{format}', 'json')
689
+ resource_path = resource_path.sub('{Id}', id)
690
+ method = 'GET'
691
+ query_params = {}
692
+ header_params = {}
693
+
694
+ query_params['redirect'] = 'meta' # we need to add this parameter to get the Amazon link directly
695
+
696
+ response = @api_client.call_api(resource_path, method, query_params, nil, header_params)
697
+ if response['ResponseStatus'].has_key?('ErrorCode')
698
+ raise 'BaseSpace error: ' + response['ResponseStatus']['ErrorCode'].to_s + ": " + response['ResponseStatus']['Message']
699
+ end
700
+
701
+ # return the Amazon URL
702
+ return response['Response']['HrefContent']
703
+ end
704
+
705
+
706
+ # Helper method for uploading multipart files, do not call directly.
707
+ #
708
+ # +id+:: File ID.
709
+ # +part_number+:: File part to be uploaded.
710
+ # +md5+:: MD5 sum of datastream.
711
+ # +data+:: The data-stream to be uploaded.
712
+ def upload_multipart_unit(id, part_number, md5, data)
713
+ resource_path = '/files/{Id}/parts/{partNumber}'
714
+ resource_path = resource_path.sub('{format}', 'json')
715
+ resource_path = resource_path.sub('{Id}', id)
716
+ resource_path = resource_path.sub('{partNumber}', part_number.to_s)
717
+ method = 'PUT'
718
+ query_params = {}
719
+ header_params = {'Content-MD5' => md5.strip()}
720
+ force_post = false
721
+ out = @api_client.call_api(resource_path, method, query_params, data, header_params, force_post)
722
+ return out
723
+ # curl -v -H "x-access-token: {access token}" \
724
+ # -H "Content-MD5: 9mvo6qaA+FL1sbsIn1tnTg==" \
725
+ # -T reportarchive.zipaa \
726
+ # -X PUT https://api.cloud-endor.illumina.com/v1pre2/files/7094087/parts/1
727
+ end
728
+
729
+ # Not yet implemented (by Illumina Python SDK)
730
+ #
731
+ # def large_file_download
732
+ # raise 'Not yet implemented'
733
+ # end
734
+
735
+ # Method for multi-threaded file-upload for parallel transfer of very large files (currently only runs on unix systems)
736
+ #
737
+ #
738
+ # :param id: The AppResult ID
739
+ # :param local_path: The local path of the file to be uploaded
740
+ # :param file_name: The desired filename on the server
741
+ # :param directory: The server directory to place the file in (empty string will place it in the root directory)
742
+ # :param content_type: The content type of the file
743
+ # :param tempdir: Temp directory to use, if blank the directory for 'local_path' will be used
744
+ # :param cpuCount: The number of CPUs to be used
745
+ # :param partSize: The size of individual upload parts (must be between 5 and 25mb)
746
+ # :param verbose: Write process output to stdout as upload progresses
747
+ #
748
+ # def multipart_file_upload(self, id, local_path, file_name, directory, content_type, tempdir = nil, cpuCount = 2, partSize = 25, verbose = false)
749
+ # # Create file object on server
750
+ # multipart = 1
751
+ # my_file = app_result_file_upload(id, local_path, file_name, directory, content_type, multipart)
752
+ #
753
+ # # prepare multi-par upload objects
754
+ # my_mpu = mpu(self, id, local_path, my_file, cpu_count, part_size, tempdir, verbose)
755
+ # return my_mpu
756
+ # end
757
+ #
758
+ # def mark_file_state(id)
759
+ # end
760
+
761
+ # Set the status of an AppResult object.
762
+ #
763
+ # +id+:: The id of the AppResult.
764
+ # +status+:: Status assignment string.
765
+ # +summary+:: Summary string.
766
+ def set_app_session_state(id, status, summary)
767
+ my_model = 'AppSessionResponse'
768
+ resource_path = '/appsessions/{Id}'
769
+ resource_path = resource_path.sub('{format}', 'json')
770
+ resource_path = resource_path.sub('{Id}', id)
771
+ method = 'POST'
772
+ query_params = {}
773
+ header_params = {}
774
+ post_data = {}
775
+ verbose = false
776
+
777
+ status_allowed = ['running', 'complete', 'needsattention', 'aborted', 'error']
778
+ unless status_allowed.include?(status.downcase)
779
+ raise "AppResult state must be in #{status_allowed.inspect}"
780
+ end
781
+ post_data['status'] = status.downcase
782
+ post_data['statussummary'] = summary
783
+ return single_request(my_model, resource_path, method, query_params, header_params, post_data, verbose)
784
+ end
785
+ end
786
+
787
+ end # module BaseSpace
788
+ end # module Bio
789
+