bio-basespace-sdk 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bio-basespace-sdk might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +479 -482
- data/Rakefile +3 -5
- data/VERSION +1 -1
- data/examples/0_app_triggering.rb +51 -62
- data/examples/1_authentication.rb +14 -99
- data/examples/2_browsing.rb +42 -44
- data/examples/3_accessing_files.rb +72 -77
- data/examples/4_app_result_upload.rb +97 -54
- data/examples/5_purchasing.rb +4 -10
- data/lib/basespace/api/api_client.rb +11 -5
- data/lib/basespace/api/base_api.rb +3 -3
- data/lib/basespace/api/basespace_api.rb +9 -9
- data/lib/basespace/model/project.rb +1 -1
- data/lib/basespace/model/sample.rb +6 -5
- metadata +6 -8
@@ -13,8 +13,8 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
|
16
|
-
# Creating an AppResult and
|
17
|
-
#
|
16
|
+
# Creating an AppResult and Uploading Files
|
17
|
+
# https://github.com/joejimbo/basespace-ruby-sdk#creating-an-appresult-and-uploading-files
|
18
18
|
|
19
19
|
require 'bio-basespace-sdk'
|
20
20
|
|
@@ -39,64 +39,107 @@ unless opts.select{|k,v| v[/^<.*>$/]}.empty?
|
|
39
39
|
exit 1 unless opts
|
40
40
|
end
|
41
41
|
|
42
|
-
#
|
42
|
+
# Initialize a BaseSpace API object:
|
43
43
|
bs_api = BaseSpaceAPI.new(opts['client_id'], opts['client_secret'], opts['basespace_url'], opts['api_version'], opts['app_session_id'], opts['access_token'])
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
#
|
48
|
-
|
45
|
+
### Creating an AppResult ###
|
46
|
+
|
47
|
+
#
|
48
|
+
# Request privileges
|
49
|
+
#
|
50
|
+
|
51
|
+
device_info = bs_api.get_verification_code('browse global')
|
52
|
+
link = device_info['verification_with_code_uri']
|
53
|
+
puts "Visit the URI within 15 seconds and grant access:"
|
54
|
+
puts link
|
55
|
+
host = RbConfig::CONFIG['host_os']
|
56
|
+
case host
|
57
|
+
when /mswin|mingw|cygwin/
|
58
|
+
system("start #{link}")
|
59
|
+
when /darwin/
|
60
|
+
system("open #{link}")
|
61
|
+
when /linux/
|
62
|
+
system("xdg-open #{link}")
|
63
|
+
end
|
64
|
+
sleep(15)
|
65
|
+
|
66
|
+
code = device_info['device_code']
|
67
|
+
bs_api.update_privileges(code)
|
68
|
+
|
69
|
+
#
|
70
|
+
# Get a project
|
71
|
+
#
|
72
|
+
|
73
|
+
# NOTE THAT YOUR PROJECT ID WILL MOST LIKELY BE DIFFERENT!
|
74
|
+
# YOU CAN GET IT VIA THE SDK OR FROM THE BASESPACE WEB INTERFACE!
|
75
|
+
# FOR EXAMPLE: my_projects.first.id
|
76
|
+
puts 'NOTE THAT YOU NEED TO UPDATE THE PROJECT ID IN THE EXAMPLE CODE TO MATCH A PROJECT OF YOURS!'
|
77
|
+
prj = bs_api.get_project_by_id('469469')
|
78
|
+
|
79
|
+
#
|
80
|
+
# List the current analyses for the project
|
81
|
+
#
|
49
82
|
|
50
|
-
# Assuming we have write access to the project
|
51
|
-
# we list the current App Results for the project.
|
52
83
|
statuses = ['Running']
|
53
|
-
app_res = prj.get_app_results(bs_api, {}, statuses)
|
54
|
-
puts "
|
55
|
-
puts
|
84
|
+
app_res = prj.get_app_results(bs_api, {}, statuses)
|
85
|
+
puts "AppResult instances: #{app_res.map { |r| r.to_s }.join(', ')}"
|
56
86
|
|
57
87
|
#
|
58
|
-
#
|
88
|
+
# Request project creation privileges
|
89
|
+
#
|
90
|
+
|
91
|
+
device_info = bs_api.get_verification_code("create project #{prj.id}")
|
92
|
+
link = device_info['verification_with_code_uri']
|
93
|
+
puts "Visit the URI within 15 seconds and grant access:"
|
94
|
+
puts link
|
95
|
+
host = RbConfig::CONFIG['host_os']
|
96
|
+
case host
|
97
|
+
when /mswin|mingw|cygwin/
|
98
|
+
system("start #{link}")
|
99
|
+
when /darwin/
|
100
|
+
system("open #{link}")
|
101
|
+
when /linux/
|
102
|
+
system("xdg-open #{link}")
|
103
|
+
end
|
104
|
+
sleep(15)
|
105
|
+
|
106
|
+
code = device_info['device_code']
|
107
|
+
bs_api.update_privileges(code)
|
108
|
+
|
109
|
+
# NOTE THAT THE APP SESSION ID OF A RUNNING APP MUST BE PROVIDED!
|
110
|
+
app_result = prj.create_app_result(bs_api, "testing", "this is my results", bs_api.app_session_id)
|
111
|
+
puts "AppResult ID: #{app_result.id}"
|
112
|
+
puts "AppResult's AppSession: #{app_result.app_session}"
|
113
|
+
|
114
|
+
#
|
115
|
+
# Change the status of our `AppSession` and add a status-summary as follows:
|
116
|
+
#
|
117
|
+
|
118
|
+
app_result.app_session.set_status(bs_api, 'needsattention', "We worked hard, but encountered some trouble.")
|
119
|
+
|
120
|
+
# Updated status:
|
121
|
+
puts "AppResult's AppSession: #{app_result.app_session}"
|
122
|
+
|
123
|
+
# Set back to running:
|
124
|
+
app_result.app_session.set_status(bs_api, 'running', "Back on track")
|
125
|
+
|
126
|
+
### Uploading Files ###
|
127
|
+
|
128
|
+
#
|
129
|
+
# Attach a file to the `AppResult` object and upload it:
|
130
|
+
#
|
131
|
+
|
132
|
+
puts 'NOTE: THIS ASSUMES A FILE /tmp/testFile.txt IN YOUR FILESYSTEM!'
|
133
|
+
app_result.upload_file(bs_api, '/tmp/testFile.txt', 'BaseSpaceTestFile.txt', '/mydir/', 'text/plain')
|
134
|
+
|
135
|
+
# Let's see if our new file made it into the cloud:
|
136
|
+
app_result_files = app_result.get_files(bs_api)
|
137
|
+
puts "Files: #{app_result_files.map { |f| f.to_s }.join(', ')}"
|
138
|
+
|
59
139
|
#
|
140
|
+
# Download our newly uploaded file (will be saved as BaseSpaceTestFile.txt):
|
60
141
|
|
61
|
-
|
62
|
-
|
63
|
-
puts
|
64
|
-
puts app_results
|
65
|
-
puts app_results.id
|
66
|
-
puts
|
67
|
-
puts "The app results also comes with a reference to our AppSession"
|
68
|
-
my_app_session = app_results.app_session
|
69
|
-
puts my_app_session
|
70
|
-
puts
|
71
|
-
|
72
|
-
# We can change the status of our AppSession and add a status-summary as follows.
|
73
|
-
my_app_session.set_status(bs_api, 'needsattention', "We worked hard, but encountered some trouble.")
|
74
|
-
puts "After a change of status of the app sessions we get #{my_app_session}"
|
75
|
-
puts
|
76
|
-
# We set our appSession back to running so we can do some more work.
|
77
|
-
my_app_session.set_status(bs_api, 'running', "Back on track")
|
78
|
-
|
79
|
-
|
80
|
-
# Let's list all AppResults again and see if our new object shows up.
|
81
|
-
app_res = prj.get_app_results(bs_api, {}, ['Running'])
|
82
|
-
puts "The updated app results are #{app_res}"
|
83
|
-
app_result2 = bs_api.get_app_result_by_id(app_results.id)
|
84
|
-
puts app_result2
|
85
|
-
puts
|
86
|
-
|
87
|
-
# Now we will make another AppResult and try to upload a file to it
|
88
|
-
app_results2 = prj.create_app_result(bs_api, "My second AppResult", "This one I will upload to")
|
89
|
-
app_results2.upload_file(bs_api, '/home/mkallberg/Desktop/testFile2.txt', 'BaseSpaceTestFile.txt', '/mydir/', 'text/plain')
|
90
|
-
puts "My AppResult number 2 #{app_results2}"
|
91
|
-
puts
|
92
|
-
|
93
|
-
# Let's see if our new file made it.
|
94
|
-
app_result_files = app_results2.get_files(bs_api)
|
95
|
-
puts "These are the files in the appResult"
|
96
|
-
puts app_result_files
|
97
|
-
f = app_result_files.last
|
98
|
-
|
99
|
-
# We can even download our newly uploaded file.
|
100
|
-
f = bs_api.get_file_by_id(f.id)
|
101
|
-
f.download_file(bs_api, '/home/mkallberg/Desktop/')
|
142
|
+
f = bs_api.get_file_by_id(app_result_files.last.id)
|
143
|
+
f.download_file(bs_api, '/tmp/')
|
144
|
+
puts 'NOTE: downloaded \'BaseSpaceTestFile.txt\' into the /tmp directory.'
|
102
145
|
|
data/examples/5_purchasing.rb
CHANGED
@@ -13,19 +13,13 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
|
16
|
+
# Purchasing
|
17
|
+
# --- Sorry, we have not documented this yet. ---
|
18
|
+
|
16
19
|
require 'bio-basespace-sdk'
|
17
20
|
|
18
21
|
include Bio::BaseSpace
|
19
22
|
|
20
|
-
# This example demonstrates the billing methods of BaseSpace.
|
21
|
-
#
|
22
|
-
# Below a purchase is created, requiring the user to click 'Purchase' in a web
|
23
|
-
# browser. The purchase is then refunded, and the purchase is again retrieved
|
24
|
-
# via the API using the purchase id and tags, which are used by developers
|
25
|
-
# to help clarify exactly what was purchased.
|
26
|
-
#
|
27
|
-
# NOTE You will need to fill client values for your app below!
|
28
|
-
|
29
23
|
opts = {
|
30
24
|
# FILL IN WITH YOUR APP VALUES HERE!
|
31
25
|
'client_id' => '<your client key>', # from dev portal app Credentials tab
|
@@ -48,7 +42,7 @@ unless opts['client_id'] or opts['product_id']
|
|
48
42
|
raise "Please fill in client values (in the script) before running the script"
|
49
43
|
end
|
50
44
|
|
51
|
-
#
|
45
|
+
# Initialize a BaseSpace API object:
|
52
46
|
bill_api = BillingAPI.new(opts['basespace_store_url'], opts['api_version'], opts['app_session_id'], opts['access_token'])
|
53
47
|
|
54
48
|
# Create a non-consumable purchase.
|
@@ -153,7 +153,7 @@ class APIClient
|
|
153
153
|
# [TODO] confirm this works or not
|
154
154
|
#request = urllib2.Request(url, headers)
|
155
155
|
uri = URI.parse(url)
|
156
|
-
request = Net::HTTP::Get.new(uri
|
156
|
+
request = Net::HTTP::Get.new(uri, headers)
|
157
157
|
when 'POST', 'PUT', 'DELETE'
|
158
158
|
if cgi_params
|
159
159
|
force_post_url = url
|
@@ -161,16 +161,22 @@ class APIClient
|
|
161
161
|
end
|
162
162
|
if post_data
|
163
163
|
# [TODO] Do we need to skip String, Integer, Float and bool in Ruby?
|
164
|
-
data = post_data
|
164
|
+
data = post_data # if not [str, int, float, bool].include?(type(post_data))
|
165
165
|
end
|
166
166
|
if force_post
|
167
167
|
response = force_post_call(force_post_url, sent_query_params, headers)
|
168
168
|
else
|
169
|
-
data =
|
169
|
+
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
|
170
170
|
# [TODO] confirm this works or not
|
171
171
|
#request = urllib2.Request(url, headers, data)#, @timeout)
|
172
172
|
uri = URI.parse(url)
|
173
|
-
request = Net::HTTP::Post.new(uri
|
173
|
+
request = Net::HTTP::Post.new(uri, headers)
|
174
|
+
if data.kind_of?(Hash) then
|
175
|
+
request.set_form_data(data)
|
176
|
+
else
|
177
|
+
request.content_type = 'multipart/form-data'
|
178
|
+
request.body = data
|
179
|
+
end
|
174
180
|
end
|
175
181
|
if ['PUT', 'DELETE'].include?(method) # urllib doesnt do put and delete, default to pycurl here
|
176
182
|
response = put_call(url, query_params, headers, data)
|
@@ -237,7 +243,7 @@ class APIClient
|
|
237
243
|
instance = File.new
|
238
244
|
else
|
239
245
|
# models in BaseSpace
|
240
|
-
klass =
|
246
|
+
klass = ::Bio::BaseSpace.const_get(obj_class)
|
241
247
|
instance = klass.new
|
242
248
|
end
|
243
249
|
|
@@ -140,13 +140,13 @@ class BaseAPI
|
|
140
140
|
# convert list response dict to object type
|
141
141
|
# TODO check that Response is present -- errors sometime don't include
|
142
142
|
# [TODO] check why the Python SDK only uses the first element in the response_objects
|
143
|
-
|
143
|
+
converted = []
|
144
144
|
if response_object = response_objects.first
|
145
145
|
response_object.convert_to_object_list.each do |c|
|
146
|
-
|
146
|
+
converted << @api_client.deserialize(c, my_model)
|
147
147
|
end
|
148
148
|
end
|
149
|
-
return
|
149
|
+
return converted
|
150
150
|
end
|
151
151
|
|
152
152
|
# URL encode a Hash of data values.
|
@@ -162,7 +162,7 @@ class BaseSpaceAPI < BaseAPI
|
|
162
162
|
#
|
163
163
|
# +obj+:: The data object we wish to get access to.
|
164
164
|
# +access_type+:: The type of access (read|write), default is write.
|
165
|
-
# +web+::
|
165
|
+
# +web+:: If the App is web-based, then set this parameter to 'true'. The default value is false, which means that the request is for a device based App.
|
166
166
|
# +redirect_url+:: For the web-based case, a redirection URL.
|
167
167
|
# +state+:: (unclear from Python port)
|
168
168
|
def get_access(obj, access_type = 'write', web = nil, redirect_url = nil, state = nil)
|
@@ -188,7 +188,7 @@ class BaseSpaceAPI < BaseAPI
|
|
188
188
|
#curlCall = 'curl -d "response_type=device_code" -d "client_id=' + @key + '" -d "scope=' + scope + '" ' + @api_server + DEVICE_URL
|
189
189
|
#puts curlCall
|
190
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"
|
191
|
+
raise "This BaseSpaceAPI instance has no client_secret (key) set and no alternative id was supplied for method get_verification_code."
|
192
192
|
end
|
193
193
|
data = {'client_id' => @key, 'scope' => scope, 'response_type' => 'device_code'}
|
194
194
|
return make_curl_request(data, @api_server + DEVICE_URL)
|
@@ -201,7 +201,7 @@ class BaseSpaceAPI < BaseAPI
|
|
201
201
|
# +state+:: An optional state parameter that will passed through to the redirect response.
|
202
202
|
def get_web_verification_code(scope, redirect_url, state = nil)
|
203
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"
|
204
|
+
raise "This BaseSpaceAPI instance has no client_id (key) set and no alternative id was supplied for method get_verification_code."
|
205
205
|
end
|
206
206
|
data = {'client_id' => @key, 'redirect_uri' => redirect_url, 'scope' => scope, 'response_type' => 'code', "state" => state}
|
207
207
|
return @weburl + WEB_AUTHORIZE + '?' + hash2urlencode(data)
|
@@ -212,7 +212,7 @@ class BaseSpaceAPI < BaseAPI
|
|
212
212
|
# +device_code+:: The device code returned by the verification code method.
|
213
213
|
def obtain_access_token(device_code)
|
214
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"
|
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
216
|
end
|
217
217
|
data = {'client_id' => @key, 'client_secret' => @secret, 'code' => device_code, 'grant_type' => 'device', 'redirect_uri' => 'google.com'}
|
218
218
|
dict = make_curl_request(data, @api_server + TOKEN_URL)
|
@@ -584,7 +584,7 @@ class BaseSpaceAPI < BaseAPI
|
|
584
584
|
sid = query_params['appsessionid']
|
585
585
|
session = get_app_session(sid)
|
586
586
|
unless session.can_work_on
|
587
|
-
raise 'AppSession status must be "running," to create and AppResults. Current status is ' + session.status
|
587
|
+
raise 'AppSession status must be "running," to create and AppResults. Current status is: ' + session.status
|
588
588
|
end
|
589
589
|
end
|
590
590
|
|
@@ -628,7 +628,7 @@ class BaseSpaceAPI < BaseAPI
|
|
628
628
|
# Set force post as this need to use POST though no data is being streamed
|
629
629
|
return single_request(my_model, resource_path, method, query_params, header_params, post_data, verbose, force_post)
|
630
630
|
else
|
631
|
-
post_data = File.open(local_path).read
|
631
|
+
post_data = ::File.open(local_path).read
|
632
632
|
return single_request(my_model, resource_path, method, query_params, header_params, post_data, verbose)
|
633
633
|
end
|
634
634
|
end
|
@@ -664,15 +664,15 @@ class BaseSpaceAPI < BaseAPI
|
|
664
664
|
end
|
665
665
|
|
666
666
|
# Do the download
|
667
|
-
File.open(File.join(local_dir, name), "wb") do |fp|
|
667
|
+
::File.open(::File.join(local_dir, name), "wb") do |fp|
|
668
668
|
http_opts = {}
|
669
|
+
uri = URI.parse(file_url)
|
669
670
|
if uri.scheme == "https"
|
670
671
|
http_opts[:use_ssl] = true
|
671
672
|
end
|
672
|
-
uri = URI.parse(file_url)
|
673
673
|
res = Net::HTTP.start(uri.host, uri.port, http_opts) { |http|
|
674
674
|
# [TODO] Do we need user and pass here also?
|
675
|
-
http.get(uri
|
675
|
+
http.get(uri, header)
|
676
676
|
}
|
677
677
|
fp.print res.body
|
678
678
|
end
|
@@ -57,7 +57,7 @@ class Project < Model
|
|
57
57
|
raise ModelNotInitializedError.new('The project model has not been initialized yet') unless get_attr('Id')
|
58
58
|
end
|
59
59
|
|
60
|
-
# Returns the scope-string to used for requesting BaseSpace access to the object.
|
60
|
+
# Returns the scope-string to be used for requesting BaseSpace access to the object.
|
61
61
|
#
|
62
62
|
# +scope+:: The scope-type that is requested (write|read).
|
63
63
|
def get_access_str(scope = 'write')
|
@@ -28,8 +28,8 @@ class Sample < Model
|
|
28
28
|
'SampleNumber' => 'int',
|
29
29
|
'ExperimentName' => 'str',
|
30
30
|
'HrefFiles' => 'str',
|
31
|
-
|
32
|
-
'IsPairedEnd' => '
|
31
|
+
'AppSession' => 'dict',
|
32
|
+
'IsPairedEnd' => 'bool',
|
33
33
|
'Read1' => 'int',
|
34
34
|
'Read2' => 'int',
|
35
35
|
'NumReadsRaw' => 'int',
|
@@ -42,12 +42,13 @@ class Sample < Model
|
|
42
42
|
'Status' => 'str',
|
43
43
|
'StatusSummary' => 'str',
|
44
44
|
'DateCreated' => 'datetime',
|
45
|
-
'References' => 'dict',
|
45
|
+
'References' => 'dict', # NOTE Is this correct? Because references is a list.
|
46
46
|
'Run' => 'RunCompact',
|
47
47
|
}
|
48
48
|
@attributes = {
|
49
49
|
'Name' => nil, # str
|
50
50
|
'HrefFiles' => nil, # str
|
51
|
+
'AppSession' => nil, # dict
|
51
52
|
'DateCreated' => nil, # datetime
|
52
53
|
'SampleNumber' => nil, # int
|
53
54
|
'Id' => nil, # str
|
@@ -56,7 +57,7 @@ class Sample < Model
|
|
56
57
|
'ExperimentName' => nil, # str
|
57
58
|
'Run' => nil, # RunCompact
|
58
59
|
'HrefGenome' => nil, # str
|
59
|
-
'IsPairedEnd' => nil, #
|
60
|
+
'IsPairedEnd' => nil, # bool
|
60
61
|
'Read1' => nil, # int
|
61
62
|
'Read2' => nil, # int
|
62
63
|
'NumReadsRaw' => nil, # int
|
@@ -95,7 +96,7 @@ class Sample < Model
|
|
95
96
|
# Return the AppResults referenced by this sample.
|
96
97
|
#
|
97
98
|
# Note: the returned AppResult objects do not have their "References" field set,
|
98
|
-
# to get a fully populate AppResult object you must use
|
99
|
+
# to get a fully populate AppResult object you must use get_app_result_by_id in BaseSpaceAPI.
|
99
100
|
#
|
100
101
|
# +api+:: BaseSpaceAPI instance.
|
101
102
|
def get_referenced_app_results(api)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bio-basespace-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joachim Baran
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2013-07-
|
15
|
+
date: 2013-07-29 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: getopt
|
@@ -113,11 +113,9 @@ dependencies:
|
|
113
113
|
- !ruby/object:Gem::Version
|
114
114
|
version: '3.12'
|
115
115
|
description: |-
|
116
|
-
|
116
|
+
BaseSpace Ruby SDK is a Ruby based Software Development Kit to be used in the development of Apps and scripts for working with Illumina's BaseSpace cloud-computing solution for next-gen sequencing data analysis.
|
117
117
|
|
118
118
|
The primary purpose of the SDK is to provide an easy-to-use Ruby environment enabling developers to authenticate a user, retrieve data, and upload data/results from their own analysis to BaseSpace.
|
119
|
-
|
120
|
-
If you haven't already done so, you may wish to familiarize yourself with the general BaseSpace developers documentation (https://developer.basespace.illumina.com/) and create a new BaseSpace App to be used when working through the examples provided in 'examples' folder.
|
121
119
|
email: joachim.baran@gmail.com
|
122
120
|
executables: []
|
123
121
|
extensions: []
|
@@ -211,7 +209,7 @@ rubyforge_project:
|
|
211
209
|
rubygems_version: 2.0.5
|
212
210
|
signing_key:
|
213
211
|
specification_version: 4
|
214
|
-
summary:
|
215
|
-
and scripts for working with Illumina's BaseSpace cloud-computing
|
216
|
-
sequencing data analysis.
|
212
|
+
summary: BaseSpace Ruby SDK is a Ruby based Software Development Kit to be used in
|
213
|
+
the development of Apps and scripts for working with Illumina's BaseSpace cloud-computing
|
214
|
+
solution for next-gen sequencing data analysis.
|
217
215
|
test_files: []
|