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.
Files changed (46) hide show
  1. data/.travis.yml +13 -8
  2. data/Gemfile +4 -2
  3. data/README.md +5 -3
  4. data/demo/cheatsheet.rb +31 -0
  5. data/lib/cli/auth.rb +6 -6
  6. data/lib/cli/irbrc.rb +2 -1
  7. data/lib/cli/options.rb +1 -1
  8. data/lib/client.rb +85 -83
  9. data/lib/credentials.rb +2 -2
  10. data/lib/main.rb +11 -2
  11. data/lib/query.rb +5 -6
  12. data/lib/resource/annotation.rb +23 -0
  13. data/lib/resource/apiresource.rb +241 -0
  14. data/lib/resource/dataset.rb +91 -0
  15. data/lib/resource/datasetfield.rb +37 -0
  16. data/lib/resource/depository.rb +50 -0
  17. data/lib/resource/depositoryversion.rb +69 -0
  18. data/lib/resource/main.rb +123 -0
  19. data/lib/resource/sample.rb +75 -0
  20. data/lib/{solveobject.rb → resource/solveobject.rb} +43 -22
  21. data/lib/resource/user.rb +5 -0
  22. data/lib/solvebio.rb +1 -1
  23. data/lib/util.rb +29 -0
  24. data/solvebio.gemspec +7 -4
  25. data/test/Makefile +9 -0
  26. data/test/data/sample.vcf.gz +0 -0
  27. data/test/helper.rb +9 -2
  28. data/test/test-annotation.rb +46 -0
  29. data/test/test-auth.rb +8 -4
  30. data/test/test-client.rb +6 -6
  31. data/test/test-conversion.rb +13 -0
  32. data/test/test-dataset.rb +42 -0
  33. data/test/test-depository.rb +35 -0
  34. data/test/test-netrc.rb +13 -3
  35. data/test/test-query-batch.rb +26 -46
  36. data/test/test-query-paging.rb +77 -98
  37. data/test/test-query.rb +47 -64
  38. data/test/test-resource.rb +8 -15
  39. data/test/test-sample-access.rb +59 -0
  40. data/test/test-sample-download.rb +20 -0
  41. data/test/test-tabulate.rb +27 -23
  42. data/test/{test-solveobject.rb → test-util.rb} +17 -2
  43. metadata +128 -56
  44. data/lib/apiresource.rb +0 -130
  45. data/lib/help.rb +0 -46
  46. data/lib/resource.rb +0 -414
data/.travis.yml CHANGED
@@ -1,13 +1,18 @@
1
1
  language: ruby
2
2
  before_script:
3
- - gem install netrc
4
- - touch ~/.netrc
5
- - chmod 0600 ~/.netrc
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
- - "COLUMNS=80"
8
+ global:
9
+ - COLUMNS=80
8
10
  script: rake
9
11
  rvm:
10
- - 1.9.3
11
- - 2.0.0
12
- - 2.1.1
13
- - 2.1.2
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
@@ -1,4 +1,6 @@
1
- # A sample Gemfile
1
+ # A bundler Gemfile for specifying dependencies. See http://bundler.io/
2
2
  source "https://rubygems.org"
3
3
 
4
- gem "netrc"
4
+ gem 'addressable'
5
+ gem 'rest_client'
6
+ gem 'netrc', '~>0.7.7' # rest_client needs netrc 0.7.7
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
- Right now we only support installing from git:
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
 
@@ -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.request('post',
41
- '/v1/reports/install',
42
- data) rescue nil
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, password=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
- client.request('post', '/v1/auth/token', data)
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 [, password]]"'
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 the SolveBio Python shell
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 'net/http'
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
- 'Content-Type' => 'application/json',
27
- 'Accept' => 'application/json',
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
- def request(method, url, params=nil, raw=false)
46
+ DEFAULT_REQUEST_OPTS = {
47
+ :raw => false,
48
+ :default_headers => true
49
+ }
38
50
 
39
- if not @api_host
40
- raise SolveBio::Error.new(nil, 'No SolveBio API host is set')
41
- elsif not url.start_with?(@api_host)
42
- url = URI.join(@api_host, url).to_s
43
- end
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
- uri = URI.parse(url)
46
- http = Net::HTTP.new(uri.host, uri.port)
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
- # Note: there's also read_timeout and ssl_timeout
49
- http.open_timeout = 80 # in seconds
69
+ # Issues an HTTP Request across the wire via the Ruby 'rest-client'
70
+ # library.
71
+ def request(method, url, opts={})
50
72
 
51
- if uri.scheme == 'https'
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
- http.set_debug_output($stderr) if $DEBUG
59
- SolveBio::logger.debug('API %s Request: %s' % [method.upcase, url])
75
+ # Expand URL with API host if none was given
76
+ api_host = @api_host or SolveBio::API_HOST
60
77
 
61
- request = nil
62
- if ['POST', 'PUT', 'PATCH'].member?(method.upcase)
63
- # FIXME? do we need to do something different for
64
- # PUT and PATCH?
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
- status_code = response.code.to_i
94
- if status_code < 200 or status_code >= 300
95
- handle_api_error(response)
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
- if raw
99
- return response.body
100
- else
101
- return JSON.parse(response.body)
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 = 'Unexpected error communicating with SolveBio. ' +
121
+ msg = "Unexpected error communicating with SolveBio.\n" +
113
122
  "It looks like there's probably a configuration " +
114
- 'issue locally. If this problem persists, let us ' +
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
- if [400, 401, 403, 404].member?(response.code.to_i)
123
- raise SolveBio::Error.new(response)
124
- else
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.5.0'
14
+ VERSION = '1.6.1'
14
15
  @api_key = ENV['SOLVEBIO_API_KEY']
15
- @logger = Logger.new('/tmp/solvebio.log')
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
- [SolveBio::Tabulate.tabulate(self[0].to_a,
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.request('post', @data_url, _params)
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