bio-basespace-sdk 0.1.2

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.

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
+