solvebio 1.5.2 → 1.6.1
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.
- data/.travis.yml +13 -8
- data/Gemfile +4 -2
- data/README.md +5 -3
- data/demo/cheatsheet.rb +31 -0
- data/lib/cli/auth.rb +6 -6
- data/lib/cli/irbrc.rb +2 -1
- data/lib/cli/options.rb +1 -1
- data/lib/client.rb +85 -83
- data/lib/credentials.rb +2 -2
- data/lib/main.rb +11 -2
- data/lib/query.rb +5 -6
- data/lib/resource/annotation.rb +23 -0
- data/lib/resource/apiresource.rb +241 -0
- data/lib/resource/dataset.rb +91 -0
- data/lib/resource/datasetfield.rb +37 -0
- data/lib/resource/depository.rb +50 -0
- data/lib/resource/depositoryversion.rb +69 -0
- data/lib/resource/main.rb +123 -0
- data/lib/resource/sample.rb +75 -0
- data/lib/{solveobject.rb → resource/solveobject.rb} +43 -22
- data/lib/resource/user.rb +5 -0
- data/lib/solvebio.rb +1 -1
- data/lib/util.rb +29 -0
- data/solvebio.gemspec +7 -4
- data/test/Makefile +9 -0
- data/test/data/sample.vcf.gz +0 -0
- data/test/helper.rb +9 -2
- data/test/test-annotation.rb +46 -0
- data/test/test-auth.rb +8 -4
- data/test/test-client.rb +6 -6
- data/test/test-conversion.rb +13 -0
- data/test/test-dataset.rb +42 -0
- data/test/test-depository.rb +35 -0
- data/test/test-netrc.rb +13 -3
- data/test/test-query-batch.rb +26 -46
- data/test/test-query-paging.rb +77 -98
- data/test/test-query.rb +47 -64
- data/test/test-resource.rb +8 -15
- data/test/test-sample-access.rb +59 -0
- data/test/test-sample-download.rb +20 -0
- data/test/test-tabulate.rb +27 -23
- data/test/{test-solveobject.rb → test-util.rb} +17 -2
- metadata +128 -56
- data/lib/apiresource.rb +0 -130
- data/lib/help.rb +0 -46
- data/lib/resource.rb +0 -414
data/.travis.yml
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
language: ruby
|
2
2
|
before_script:
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
- gem install netrc --version=0.7.7
|
4
|
+
- gem install rest_client addressable
|
5
|
+
- touch ~/.netrc
|
6
|
+
- chmod 0600 ~/.netrc
|
6
7
|
env:
|
7
|
-
|
8
|
+
global:
|
9
|
+
- COLUMNS=80
|
8
10
|
script: rake
|
9
11
|
rvm:
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
- 1.9.3
|
13
|
+
- 2.0.0
|
14
|
+
- 2.1.1
|
15
|
+
- 2.1.2
|
16
|
+
notifications:
|
17
|
+
slack:
|
18
|
+
secure: aIpZOAZwIk/3ZToZqAPEdM4yKyD6UuDmR+KyH2eL+24mTxU+Jp8hum02LlRsdeOXweup9Ov/h7SuDQXfEt506oLmkhfDwk+v2j2RkHmFco4l4M+rI1QAtJw1xhzrscwgnJqkW8n81U5Se84KzU+x7N6EvDcki8G1KlbuU2A9f74=
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -10,13 +10,15 @@ For more information about SolveBio, see https://www.solvebio.com
|
|
10
10
|
|
11
11
|
# Installation
|
12
12
|
|
13
|
-
|
13
|
+
gem install solvebio # may need sudo
|
14
|
+
|
15
|
+
Installing from git:
|
14
16
|
|
15
17
|
git clone https://github.com/solvebio/solvebio-ruby.git
|
16
18
|
cd solvebio-ruby
|
17
19
|
gem install netrc # install gem dependencies, may need sudo
|
18
|
-
rake test # or make test
|
19
|
-
sudo rake install # or make install
|
20
|
+
rake test # or "make test"
|
21
|
+
sudo rake install # or "make install"
|
20
22
|
|
21
23
|
This also builds a *solvebio* gem which you can use elsewhere.
|
22
24
|
|
data/demo/cheatsheet.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Cheet sheet for SolveBio API
|
2
|
+
require 'solvebio'
|
3
|
+
|
4
|
+
# Load the Dataset object
|
5
|
+
dataset = SolveBio::Dataset.retrieve('ClinVar/2.0.0-1/Variants')
|
6
|
+
|
7
|
+
# Print the Dataset
|
8
|
+
puts dataset.query()
|
9
|
+
|
10
|
+
# Get help (fields/facets)
|
11
|
+
dataset.help()
|
12
|
+
|
13
|
+
# Query the dataset (filterless)
|
14
|
+
q = dataset.query()
|
15
|
+
|
16
|
+
filters = SolveBio::Filter.new :gene_symbols => "BRCA2"
|
17
|
+
|
18
|
+
puts dataset.query(:filters => filters)
|
19
|
+
|
20
|
+
# Multiple keyword filter (boolean 'or')
|
21
|
+
filters = SolveBio::Filter.new :gene_symbols => "BRCA2"
|
22
|
+
filters |= SolveBio::Filter.new :gene_symbols => "BRCA1"
|
23
|
+
|
24
|
+
# Same as above 'or' in one go using 'in'
|
25
|
+
filters = SolveBio::Filter.new :gene_symbols__in => ["BRCA2", "BRCA1"]
|
26
|
+
|
27
|
+
puts dataset.query(:filters => filters)
|
28
|
+
|
29
|
+
# Range filter. Like 'in' for a contiguous numeric range
|
30
|
+
dataset.query(filters =>
|
31
|
+
SolveBio::RangeFilter.new('hg38', "13", 32_200_000, 32_200_500))
|
data/lib/cli/auth.rb
CHANGED
@@ -37,9 +37,9 @@ module SolveBio::Auth
|
|
37
37
|
:architecture => SolveBio::ARCHITECTURE,
|
38
38
|
# :processor => processor(),
|
39
39
|
}
|
40
|
-
SolveBio::Client.client
|
41
|
-
|
42
|
-
|
40
|
+
SolveBio::Client.client
|
41
|
+
.request('post', '/v1/reports/install',
|
42
|
+
{:params => data}) rescue nil
|
43
43
|
end
|
44
44
|
|
45
45
|
|
@@ -51,7 +51,7 @@ module SolveBio::Auth
|
|
51
51
|
# Prompt user for login information (email/password).
|
52
52
|
# Email and password are used to get the user's auth_token key.
|
53
53
|
#
|
54
|
-
def login(email=nil
|
54
|
+
def login(email=nil)
|
55
55
|
delete_credentials
|
56
56
|
|
57
57
|
email, password = ask_for_credentials email unless
|
@@ -65,8 +65,8 @@ module SolveBio::Auth
|
|
65
65
|
# code. Not sure if it's valid here, or what the equivalent
|
66
66
|
# is.
|
67
67
|
begin
|
68
|
-
response = SolveBio::Client.
|
69
|
-
|
68
|
+
response = SolveBio::Client.client
|
69
|
+
.request 'post', '/v1/auth/token', {:payload => data}
|
70
70
|
rescue SolveBio::Error => e
|
71
71
|
puts "Login failed: #{e.to_s}"
|
72
72
|
return false
|
data/lib/cli/irbrc.rb
CHANGED
@@ -10,6 +10,7 @@ IRB.conf[:PROMPT][:SIMPLE] = {
|
|
10
10
|
}
|
11
11
|
|
12
12
|
require_relative '../solvebio'
|
13
|
+
require_relative '../resource/apiresource'
|
13
14
|
include SolveBio::Auth
|
14
15
|
|
15
16
|
# Set some demo names that can be used.
|
@@ -48,6 +49,6 @@ creds = get_credentials()
|
|
48
49
|
if creds
|
49
50
|
puts "You are logged in as #{creds[0]}"
|
50
51
|
else
|
51
|
-
puts 'You are not logged in yet. Login using "login [email
|
52
|
+
puts 'You are not logged in yet. Login using "login [email]"'
|
52
53
|
|
53
54
|
end
|
data/lib/cli/options.rb
CHANGED
@@ -46,7 +46,7 @@ SolveBio Commands:
|
|
46
46
|
login [email] Login and save credentials. Use email if provided.
|
47
47
|
logout Logout and delete saved credentials
|
48
48
|
whoami Show your SolveBio email address
|
49
|
-
shell Open
|
49
|
+
shell Open a SolveBio IRB shell
|
50
50
|
test Make sure the SolveBio API is working correctly
|
51
51
|
EOH
|
52
52
|
exit
|
data/lib/client.rb
CHANGED
@@ -1,30 +1,39 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# -*- coding: utf-8 -*-
|
3
3
|
require 'openssl'
|
4
|
-
require '
|
4
|
+
require 'rest_client'
|
5
5
|
require 'json'
|
6
|
+
require 'addressable/uri'
|
6
7
|
require_relative 'credentials'
|
7
8
|
require_relative 'errors'
|
8
9
|
|
9
|
-
# import textwrap
|
10
|
-
|
11
10
|
# A requests-based HTTP client for SolveBio API resources
|
12
11
|
class SolveBio::Client
|
13
12
|
|
14
13
|
attr_reader :headers, :api_host
|
15
14
|
attr_accessor :api_key
|
16
15
|
|
16
|
+
# Add our own kind of Authorization tokens. This has to be
|
17
|
+
# done this way, late, because the rest-client gem looks for
|
18
|
+
# .netrc and will set basic authentication if it finds a match.
|
19
|
+
RestClient.add_before_execution_proc do | req, args |
|
20
|
+
if args[:authorization]
|
21
|
+
req.instance_variable_get('@header')['authorization'] = [args[:authorization]]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
17
25
|
def initialize(api_key=nil, api_host=nil)
|
18
26
|
@api_key = api_key || SolveBio::api_key
|
19
27
|
SolveBio::api_key ||= api_key
|
20
28
|
@api_host = api_host || SolveBio::API_HOST
|
29
|
+
|
21
30
|
# Mirroring comments from:
|
22
31
|
# http://ruby-doc.org/stdlib-2.1.2/libdoc/net/http/rdoc/Net/HTTP.html
|
23
32
|
# gzip compression is used in preference to deflate
|
24
33
|
# compression, which is used in preference to no compression.
|
25
34
|
@headers = {
|
26
|
-
|
27
|
-
|
35
|
+
:content_type => :json,
|
36
|
+
:accept => :json,
|
28
37
|
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
|
29
38
|
'User-Agent' => 'SolveBio Ruby Client %s [%s/%s]' % [
|
30
39
|
SolveBio::VERSION,
|
@@ -34,119 +43,112 @@ class SolveBio::Client
|
|
34
43
|
}
|
35
44
|
end
|
36
45
|
|
37
|
-
|
46
|
+
DEFAULT_REQUEST_OPTS = {
|
47
|
+
:raw => false,
|
48
|
+
:default_headers => true
|
49
|
+
}
|
38
50
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
51
|
+
# Issues an HTTP GET across the wire via the Ruby 'rest-client'
|
52
|
+
# library. See *request()* for information on opts.
|
53
|
+
def get(url, opts={})
|
54
|
+
request('get', url, opts)
|
55
|
+
end
|
44
56
|
|
45
|
-
|
46
|
-
|
57
|
+
# Issues an HTTP POST across the wire via the Ruby 'rest-client'
|
58
|
+
# library. See *request* for information on opts.
|
59
|
+
def post(url, data, opts={})
|
60
|
+
opts[:payload] =
|
61
|
+
if opts.member?(:no_json)
|
62
|
+
data
|
63
|
+
else
|
64
|
+
data.to_json
|
65
|
+
end
|
66
|
+
request('post', url, opts)
|
67
|
+
end
|
47
68
|
|
48
|
-
|
49
|
-
|
69
|
+
# Issues an HTTP Request across the wire via the Ruby 'rest-client'
|
70
|
+
# library.
|
71
|
+
def request(method, url, opts={})
|
50
72
|
|
51
|
-
|
52
|
-
http.use_ssl = true
|
53
|
-
# FIXME? Risky - see
|
54
|
-
# http://www.rubyinside.com/how-to-cure-nethttps-risky-default-https-behavior-4010.html
|
55
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
56
|
-
end
|
73
|
+
opts = DEFAULT_REQUEST_OPTS.merge(opts)
|
57
74
|
|
58
|
-
|
59
|
-
|
75
|
+
# Expand URL with API host if none was given
|
76
|
+
api_host = @api_host or SolveBio::API_HOST
|
60
77
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
request = Net::HTTP::Post.new(uri.request_uri)
|
66
|
-
request.body = params.to_json
|
67
|
-
else
|
68
|
-
request = Net::HTTP::Get.new(uri.request_uri)
|
69
|
-
end
|
70
|
-
@headers.each { |k, v| request.add_field(k, v) }
|
71
|
-
request.add_field('Authorization', "Token #{@api_key}") if @api_key
|
72
|
-
response = http.request(request)
|
73
|
-
|
74
|
-
# FIXME: There's probably gzip decompression built in to
|
75
|
-
# net/http. Until I figure out how to get that to work, the
|
76
|
-
# below works.
|
77
|
-
case response
|
78
|
-
when Net::HTTPSuccess then
|
79
|
-
begin
|
80
|
-
if response['Content-Encoding'].eql?( 'gzip' ) then
|
81
|
-
puts "Performing gzip decompression for response body." if $DEBUG
|
82
|
-
sio = StringIO.new( response.body )
|
83
|
-
gz = Zlib::GzipReader.new( sio )
|
84
|
-
response.body = gz.read()
|
85
|
-
end
|
86
|
-
rescue Exception
|
87
|
-
puts "Error occurred (#{$!.message})" if $DEBUG
|
88
|
-
# handle errors
|
89
|
-
raise $!.message
|
90
|
-
end
|
78
|
+
if not api_host
|
79
|
+
raise SolveBio::Error.new('No SolveBio API host is set')
|
80
|
+
elsif not url.start_with?(api_host)
|
81
|
+
url = Addressable::URI.join(api_host, url).to_s
|
91
82
|
end
|
92
83
|
|
93
|
-
|
94
|
-
if
|
95
|
-
|
84
|
+
# Handle some default options and add authorization header
|
85
|
+
if opts[:default_headers] and @api_key
|
86
|
+
headers = @headers.merge(opts[:headers]||{})
|
87
|
+
authorization = "Token #{@api_key}"
|
88
|
+
else
|
89
|
+
headers = nil
|
90
|
+
authorization = nil
|
96
91
|
end
|
97
92
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
93
|
+
SolveBio::logger.debug('API %s Request: %s' % [method.upcase, url])
|
94
|
+
|
95
|
+
response = nil
|
96
|
+
RestClient::Request.
|
97
|
+
execute(:method => method,
|
98
|
+
:url => url,
|
99
|
+
:headers => headers,
|
100
|
+
:authorization => authorization,
|
101
|
+
:timeout => opts[:timeout] || 80,
|
102
|
+
:payload => opts[:payload]) do
|
103
|
+
|resp, request, result, &block|
|
104
|
+
response = resp
|
105
|
+
if response.code < 200 or response.code >= 400
|
106
|
+
self.handle_api_error(result)
|
107
|
+
end
|
102
108
|
end
|
109
|
+
|
110
|
+
response = JSON.parse(response) unless opts[:raw]
|
111
|
+
response
|
103
112
|
end
|
104
113
|
|
105
|
-
def handle_request_error(e)
|
114
|
+
def self.handle_request_error(e)
|
106
115
|
# FIXME: go over this. It is still a rough translation
|
107
116
|
# from the python.
|
108
117
|
err = e.inspect
|
109
118
|
if e.kind_of?(requests.exceptions.RequestException)
|
110
119
|
msg = SolveBio::Error::Default_message
|
111
120
|
else
|
112
|
-
msg =
|
121
|
+
msg = "Unexpected error communicating with SolveBio.\n" +
|
113
122
|
"It looks like there's probably a configuration " +
|
114
|
-
'issue locally
|
123
|
+
'issue locally.\nIf this problem persists, let us ' +
|
115
124
|
'know at contact@solvebio.com.'
|
116
125
|
end
|
117
126
|
msg = msg + "\n\n(Network error: #{err}"
|
118
127
|
raise SolveBio::Error.new(nil, msg)
|
119
128
|
end
|
120
129
|
|
130
|
+
# SolveBio's API error handler returns a SolveBio::Error. The
|
131
|
+
# *response* parameter is a (subclass) of Net::HTTPResponse.
|
121
132
|
def handle_api_error(response)
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
SolveBio::logger.info("API Error: #{response.msg}")
|
126
|
-
raise SolveBio::Error.new(response)
|
127
|
-
end
|
133
|
+
SolveBio::logger.info("API Error: #{response.msg}") unless
|
134
|
+
[400, 401, 403, 404].member?(response.code.to_i)
|
135
|
+
raise SolveBio::Error.new(response)
|
128
136
|
end
|
129
137
|
|
130
138
|
def self.client
|
131
139
|
@@client ||= SolveBio::Client.new()
|
132
140
|
end
|
133
141
|
|
142
|
+
def self.get(*args)
|
143
|
+
client.get(*args)
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.post(*args)
|
147
|
+
client.post(*args)
|
148
|
+
end
|
149
|
+
|
134
150
|
def self.request(*args)
|
135
151
|
client.request(*args)
|
136
152
|
end
|
137
153
|
|
138
154
|
end
|
139
|
-
|
140
|
-
if __FILE__ == $0
|
141
|
-
puts SolveBio::Client.client.headers
|
142
|
-
puts SolveBio::Client.client.api_host
|
143
|
-
client = SolveBio::Client.new(nil, 'http://google.com')
|
144
|
-
response = client.request('http', 'http://google.com') rescue 'no good'
|
145
|
-
puts response.inspect
|
146
|
-
puts '-' * 30
|
147
|
-
response = client.request('http', 'http://www.google.com') rescue 'nope'
|
148
|
-
puts response.inspect
|
149
|
-
puts '-' * 30
|
150
|
-
response = client.request('http', 'https://www.google.com') rescue 'nope'
|
151
|
-
puts response.inspect
|
152
|
-
end
|
data/lib/credentials.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# Deals with reading netrc credentials
|
4
4
|
require_relative 'main'
|
5
5
|
require 'netrc'
|
6
|
-
require 'uri'
|
6
|
+
require 'addressable/uri'
|
7
7
|
|
8
8
|
#
|
9
9
|
# Raised if the credentials are not found.
|
@@ -17,7 +17,7 @@ module SolveBio::Credentials
|
|
17
17
|
|
18
18
|
# SolveBio API host -- just the hostname
|
19
19
|
def api_host
|
20
|
-
URI(SolveBio::API_HOST).host
|
20
|
+
Addressable::URI.parse(SolveBio::API_HOST).host
|
21
21
|
end
|
22
22
|
|
23
23
|
def netrc_path
|
data/lib/main.rb
CHANGED
@@ -7,12 +7,21 @@
|
|
7
7
|
# Have questions or comments? email us at: contact@solvebio.com
|
8
8
|
|
9
9
|
require 'logger'
|
10
|
+
require 'fileutils'
|
10
11
|
|
11
12
|
module SolveBio
|
12
13
|
|
13
|
-
VERSION = '1.
|
14
|
+
VERSION = '1.6.1'
|
14
15
|
@api_key = ENV['SOLVEBIO_API_KEY']
|
15
|
-
|
16
|
+
logfile =
|
17
|
+
if ENV['SOLVEBIO_LOGFILE']
|
18
|
+
ENV['SOLVEBIO_LOGFILE']
|
19
|
+
else
|
20
|
+
dir = File::expand_path '~/.solvebio'
|
21
|
+
FileUtils.mkdir_p(dir) unless File.exist? dir
|
22
|
+
File::expand_path File.join(dir, 'solvebio.log')
|
23
|
+
end
|
24
|
+
@logger = Logger.new(logfile)
|
16
25
|
API_HOST = ENV['SOLVEBIO_API_HOST'] || 'https://api.solvebio.com'
|
17
26
|
|
18
27
|
# Config info in reports and requests. Encapsulate more?
|
data/lib/query.rb
CHANGED
@@ -118,11 +118,11 @@ class SolveBio::PagingQuery
|
|
118
118
|
return 'query returned 0 results'
|
119
119
|
end
|
120
120
|
|
121
|
+
sorted_items = SolveBio::Tabulate.
|
122
|
+
tabulate(self[0].to_a.sort_by{|x| x[0]})
|
121
123
|
msg =
|
122
124
|
"\n%s\n\n... %s more results." %
|
123
|
-
[
|
124
|
-
['Fields', 'Data'],
|
125
|
-
['right', 'left']),
|
125
|
+
[sorted_items, ['Fields', 'Data'], ['right', 'left'],
|
126
126
|
(@total - 1).pretty_int]
|
127
127
|
return msg
|
128
128
|
end
|
@@ -283,7 +283,7 @@ class SolveBio::PagingQuery
|
|
283
283
|
_params.merge!(params)
|
284
284
|
SolveBio::logger.debug("querying dataset: #{_params}")
|
285
285
|
|
286
|
-
@response = SolveBio::Client.client.
|
286
|
+
@response = SolveBio::Client.client.post(@data_url, _params)
|
287
287
|
@total = @response['total']
|
288
288
|
SolveBio::logger.
|
289
289
|
debug("query response took: #{@response['took']} ms, " +
|
@@ -379,8 +379,7 @@ class SolveBio::BatchQuery
|
|
379
379
|
def execute(params={})
|
380
380
|
_params = build_query()
|
381
381
|
_params.merge!(params)
|
382
|
-
response = SolveBio::Client.
|
383
|
-
client.request('post', '/v1/batch_query', _params)
|
382
|
+
response = SolveBio::Client.client.post('/v1/batch_query', _params)
|
384
383
|
return response
|
385
384
|
end
|
386
385
|
end
|