solvebio 1.6.1 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. data/.bumpversion.cfg +6 -0
  2. data/.gitignore +5 -4
  3. data/.travis.yml +1 -1
  4. data/Gemfile +3 -0
  5. data/README.md +34 -34
  6. data/Rakefile +1 -18
  7. data/bin/solvebio.rb +14 -16
  8. data/installer +64 -0
  9. data/lib/solvebio.rb +50 -11
  10. data/lib/solvebio/acccount.rb +4 -0
  11. data/lib/solvebio/annotation.rb +11 -0
  12. data/lib/solvebio/api_operations.rb +147 -0
  13. data/lib/solvebio/api_resource.rb +32 -0
  14. data/lib/solvebio/cli.rb +75 -0
  15. data/lib/solvebio/cli/auth.rb +106 -0
  16. data/lib/solvebio/cli/credentials.rb +54 -0
  17. data/lib/{cli → solvebio/cli}/irb.rb +0 -23
  18. data/lib/solvebio/cli/irbrc.rb +48 -0
  19. data/lib/solvebio/cli/tutorial.rb +12 -0
  20. data/lib/solvebio/client.rb +149 -0
  21. data/lib/solvebio/dataset.rb +60 -0
  22. data/lib/solvebio/dataset_field.rb +12 -0
  23. data/lib/solvebio/depository.rb +38 -0
  24. data/lib/solvebio/depository_version.rb +40 -0
  25. data/lib/solvebio/errors.rb +64 -0
  26. data/lib/solvebio/filter.rb +315 -0
  27. data/lib/solvebio/list_object.rb +73 -0
  28. data/lib/solvebio/locale.rb +43 -0
  29. data/lib/solvebio/query.rb +341 -0
  30. data/lib/solvebio/sample.rb +54 -0
  31. data/lib/solvebio/singleton_api_resource.rb +25 -0
  32. data/lib/solvebio/solve_object.rb +164 -0
  33. data/lib/solvebio/tabulate.rb +589 -0
  34. data/lib/solvebio/user.rb +4 -0
  35. data/lib/solvebio/util.rb +59 -0
  36. data/lib/solvebio/version.rb +3 -0
  37. data/solvebio.gemspec +10 -18
  38. data/test/helper.rb +6 -2
  39. data/test/solvebio/data/.gitignore +1 -0
  40. data/test/solvebio/data/.netrc +6 -0
  41. data/test/{data → solvebio/data}/netrc-save +0 -0
  42. data/test/solvebio/data/sample.vcf.gz +0 -0
  43. data/test/solvebio/data/test_creds +3 -0
  44. data/test/solvebio/test_annotation.rb +45 -0
  45. data/test/solvebio/test_client.rb +29 -0
  46. data/test/solvebio/test_conversion.rb +14 -0
  47. data/test/solvebio/test_credentials.rb +67 -0
  48. data/test/solvebio/test_dataset.rb +52 -0
  49. data/test/solvebio/test_depository.rb +24 -0
  50. data/test/solvebio/test_depositoryversion.rb +22 -0
  51. data/test/solvebio/test_error.rb +31 -0
  52. data/test/solvebio/test_filter.rb +86 -0
  53. data/test/solvebio/test_query.rb +282 -0
  54. data/test/solvebio/test_query_batch.rb +38 -0
  55. data/test/solvebio/test_query_init.rb +30 -0
  56. data/test/solvebio/test_query_tabulate.rb +73 -0
  57. data/test/solvebio/test_ratelimit.rb +31 -0
  58. data/test/solvebio/test_resource.rb +29 -0
  59. data/test/solvebio/test_sample_access.rb +60 -0
  60. data/test/solvebio/test_sample_download.rb +20 -0
  61. data/test/solvebio/test_tabulate.rb +129 -0
  62. data/test/solvebio/test_util.rb +39 -0
  63. metadata +100 -85
  64. data/Makefile +0 -17
  65. data/demo/README.md +0 -14
  66. data/demo/cheatsheet.rb +0 -31
  67. data/demo/dataset/facets.rb +0 -13
  68. data/demo/dataset/field.rb +0 -13
  69. data/demo/depository/README.md +0 -24
  70. data/demo/depository/all.rb +0 -13
  71. data/demo/depository/retrieve.rb +0 -13
  72. data/demo/depository/versions-all.rb +0 -13
  73. data/demo/query/query-filter.rb +0 -30
  74. data/demo/query/query.rb +0 -13
  75. data/demo/query/range-filter.rb +0 -18
  76. data/demo/test-api.rb +0 -98
  77. data/lib/cli/auth.rb +0 -122
  78. data/lib/cli/help.rb +0 -13
  79. data/lib/cli/irbrc.rb +0 -54
  80. data/lib/cli/options.rb +0 -75
  81. data/lib/client.rb +0 -154
  82. data/lib/credentials.rb +0 -67
  83. data/lib/errors.rb +0 -81
  84. data/lib/filter.rb +0 -312
  85. data/lib/locale.rb +0 -47
  86. data/lib/main.rb +0 -46
  87. data/lib/query.rb +0 -414
  88. data/lib/resource/annotation.rb +0 -23
  89. data/lib/resource/apiresource.rb +0 -241
  90. data/lib/resource/dataset.rb +0 -91
  91. data/lib/resource/datasetfield.rb +0 -37
  92. data/lib/resource/depository.rb +0 -50
  93. data/lib/resource/depositoryversion.rb +0 -69
  94. data/lib/resource/main.rb +0 -123
  95. data/lib/resource/sample.rb +0 -75
  96. data/lib/resource/solveobject.rb +0 -122
  97. data/lib/resource/user.rb +0 -5
  98. data/lib/tabulate.rb +0 -706
  99. data/lib/util.rb +0 -29
  100. data/test/Makefile +0 -9
  101. data/test/data/sample.vcf.gz +0 -0
  102. data/test/test-annotation.rb +0 -46
  103. data/test/test-auth.rb +0 -58
  104. data/test/test-client.rb +0 -27
  105. data/test/test-conversion.rb +0 -13
  106. data/test/test-dataset.rb +0 -42
  107. data/test/test-depository.rb +0 -35
  108. data/test/test-error.rb +0 -36
  109. data/test/test-filter.rb +0 -70
  110. data/test/test-netrc.rb +0 -52
  111. data/test/test-query-batch.rb +0 -40
  112. data/test/test-query-init.rb +0 -29
  113. data/test/test-query-paging.rb +0 -102
  114. data/test/test-query.rb +0 -71
  115. data/test/test-resource.rb +0 -40
  116. data/test/test-sample-access.rb +0 -59
  117. data/test/test-sample-download.rb +0 -20
  118. data/test/test-tabulate.rb +0 -131
  119. data/test/test-util.rb +0 -42
@@ -0,0 +1,32 @@
1
+ module SolveBio
2
+ class APIResource < SolveObject
3
+ def self.retrieve(id)
4
+ instance = self.new(id)
5
+ instance.refresh()
6
+ instance
7
+ end
8
+
9
+ def self.class_name
10
+ self.name.split('::')[-1]
11
+ end
12
+
13
+ def self.url
14
+ if self == APIResource
15
+ raise NotImplementedError.new('APIResource is an abstract class and has no url.')
16
+ end
17
+ "/v1/#{Util.pluralize(Util.camelcase_to_underscore(class_name))}"
18
+ end
19
+
20
+ def url
21
+ unless id = self.id
22
+ raise InvalidRequestError.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}", 'id')
23
+ end
24
+ "#{self.class.url}/#{id}"
25
+ end
26
+
27
+ def refresh
28
+ response = Client.get(url)
29
+ refresh_from(response)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require 'irb'
5
+ require 'netrc'
6
+ require 'fileutils'
7
+ require 'optparse'
8
+ require 'readline'
9
+ require 'io/console'
10
+
11
+ require 'solvebio/cli/credentials'
12
+ require 'solvebio/cli/auth'
13
+ require 'solvebio/cli/irb'
14
+ require 'solvebio/cli/tutorial'
15
+
16
+ module SolveBio
17
+ module CLI
18
+ def process_options(argv)
19
+ options = {}
20
+ opts = setup_options(options)
21
+
22
+ begin
23
+ opts.parse!(argv)
24
+ rescue OptionParser::ParseError => error
25
+ $stderr.puts error
26
+ $stderr.puts "(-h or --help will show valid options)"
27
+ exit 1
28
+ end
29
+
30
+ SolveBio.api_host = options[:api_host] if options[:api_host]
31
+ SolveBio.api_key = options[:api_key] if options[:api_key]
32
+
33
+ return options, argv
34
+ end
35
+
36
+ # Main parser for the SolveBio command line client
37
+ def setup_options(options, stdout=$stdout, stderr=$stderr)
38
+ OptionParser.new do |opts|
39
+ opts.banner = "Usage: solvebio.rb [options] <command> [<args>]"
40
+ opts.on_tail('-v', '--version',
41
+ 'print the version') do
42
+ options[:version] = true
43
+ stdout.puts "solvebio-ruby #{SolveBio::VERSION}"
44
+ exit 0
45
+ end
46
+
47
+ opts.on('--api-host STRING', String,
48
+ 'Override the default SolveBio API host') do
49
+ |api_host|
50
+ options[:api_host] = api_host
51
+ end
52
+
53
+ opts.on('--api-key STRING', String,
54
+ 'Manually provide a SolveBio API key') do
55
+ |api_key|
56
+ options[:api_key] = api_key
57
+ end
58
+
59
+ opts.on('-h', '--help', 'Display this screen') do
60
+ puts opts
61
+ puts <<-EOH
62
+
63
+ SolveBio Commands:
64
+ login Login and save credentials.
65
+ logout Logout and delete saved credentials.
66
+ whoami Show your SolveBio email address.
67
+ shell Open a SolveBio IRB shell.
68
+ tutorial Start the SolveBio Ruby tutorial.
69
+ EOH
70
+ exit
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,106 @@
1
+ module SolveBio
2
+ module CLI
3
+ module Auth
4
+ include SolveBio::CLI::Credentials
5
+
6
+ module_function
7
+ def print_message(msg)
8
+ if SolveBio.api_host != 'https://api.solvebio.com'
9
+ msg += " (#{SolveBio.api_host})"
10
+ end
11
+ puts msg + '.'
12
+ end
13
+
14
+ def ask_for_credentials()
15
+ print_message('Enter your SolveBio credentials')
16
+ email = Readline.readline('Email: ', true)
17
+ puts 'Password (typing will be hidden): '
18
+ password = STDIN.noecho(&:gets).chomp
19
+ puts
20
+ return email, password
21
+ end
22
+
23
+ def send_install_report
24
+ require 'rbconfig';
25
+ data = {
26
+ :client => 'ruby',
27
+ :client_version => SolveBio::VERSION,
28
+ :ruby_version => RbConfig::CONFIG['RUBY_PROGRAM_VERSION'],
29
+ :ruby_implementation => RbConfig::CONFIG['RUBY_SO_NAME'],
30
+ :architecture => RbConfig::CONFIG['arch'],
31
+ }
32
+ Client.request('post', '/v1/reports/install', {:payload => data}) rescue nil
33
+ end
34
+
35
+ def login
36
+ email, password = ask_for_credentials
37
+
38
+ if not email or not password
39
+ puts "Email and password are both required."
40
+ return false
41
+ end
42
+
43
+ data = {
44
+ :email => email,
45
+ :password => password
46
+ }
47
+
48
+ begin
49
+ response = Client.post('/v1/auth/token', {:payload => data})
50
+ rescue SolveBio::SolveError => e
51
+ puts "Login failed: #{e.to_s}"
52
+ return false
53
+ end
54
+
55
+ delete_credentials
56
+ save_credentials(email.downcase, response[:token])
57
+ SolveBio.api_key = response[:token]
58
+ send_install_report
59
+ print_message("You are now logged-in as #{email}")
60
+ return true
61
+ end
62
+
63
+ def logout
64
+ if get_credentials
65
+ delete_credentials
66
+ print_message('You have been logged out')
67
+ return true
68
+ end
69
+
70
+ print_message('You are not logged-in')
71
+ return false
72
+ end
73
+
74
+ def whoami
75
+ email = nil
76
+ api_key = SolveBio.api_key
77
+
78
+ # Override local credentials with existing key
79
+ if SolveBio.api_key
80
+ begin
81
+ user = Client.get('/v1/user')
82
+ email = user[:email]
83
+ rescue SolveBio::SolveError => e
84
+ SolveBio.api_key = nil
85
+ api_key = nil
86
+ print_message("Error: #{e.to_s}")
87
+ end
88
+ else
89
+ begin
90
+ email, api_key = get_credentials
91
+ rescue
92
+ nil
93
+ end
94
+ end
95
+
96
+ if not email.nil?
97
+ print_message("You are logged-in as #{email}")
98
+ else
99
+ print_message("You are not logged-in")
100
+ end
101
+
102
+ return email, api_key
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ class CredentialsError < RuntimeError
5
+ end
6
+
7
+ module SolveBio
8
+ module CLI
9
+ module Credentials
10
+ module_function
11
+ def api_host
12
+ Addressable::URI.parse(SolveBio.api_host).host
13
+ end
14
+
15
+ def netrc_path
16
+ raise IOError, "$HOME is not set and is needed for SolveBio credentials" unless
17
+ ENV['HOME']
18
+
19
+ path = File.join(ENV['HOME'], '.solvebio', 'credentials')
20
+
21
+ dirname = File.dirname(path)
22
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
23
+
24
+ # create an empty credentials file if it doesn't exist
25
+ FileUtils.touch path unless File.exist? path
26
+ FileUtils.chmod 0600, path
27
+ path
28
+ end
29
+
30
+ def get_credentials
31
+ begin
32
+ n = Netrc.read(netrc_path)
33
+ return n[api_host]
34
+ rescue Netrc::Error => e
35
+ raise CredentialsError, "Could not read credentials file: #{e}"
36
+ end
37
+ end
38
+ module_function :get_credentials
39
+
40
+ def delete_credentials
41
+ n = Netrc.read(netrc_path)
42
+ n.delete(api_host)
43
+ n.save
44
+ end
45
+
46
+ def save_credentials(email, api_key)
47
+ n = Netrc.read(netrc_path)
48
+ # Overwrites any existing credentials
49
+ n[api_host] = email, api_key
50
+ n.save
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,27 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
  # -*- coding: utf-8 -*-
3
- # Open the SolveBio shell, an IRB wrapper
4
- require 'irb'
5
- require_relative 'help'
6
3
 
7
4
  module IRB
8
-
9
- # Stuff to add IRB commands goes here
10
- # module ExtendCommand
11
- # class SolveHelp
12
- # def self.execute(conf, *opts)
13
- # SolveBio::help
14
- # end
15
- # end
16
-
17
- # end
18
-
19
5
  module_function
20
-
21
- # ExtendCommandBundle.def_extend_command 'solvehelp', :SolveHelp
22
-
23
6
  def shell
24
-
25
7
  # Set to run the standard trepan IRB profile
26
8
  irbrc = File.
27
9
  expand_path(File.join(File.dirname(__FILE__), 'irbrc.rb'))
@@ -51,8 +33,3 @@ module IRB
51
33
  end
52
34
  end
53
35
  end
54
-
55
- # Demo it.
56
- if __FILE__ == $0
57
- IRB::shell
58
- end
@@ -0,0 +1,48 @@
1
+ # -*- ruby -*-
2
+ # irbrc profile for SolveBio
3
+ IRB.conf[:PROMPT_MODE] = :SIMPLE
4
+ IRB.conf[:PROMPT][:SIMPLE] = {
5
+ :PROMPT_C => '[SolveBio] In ?: ', # Prompt when continuing a statement
6
+ :PROMPT_I => '[SolveBio] In : ', # Normal prompt
7
+ :PROMPT_N => '[SolveBio] In +: ', # Prompt when indenting code
8
+ :PROMPT_S => '[SolveBio] In %l: ', # Prompt when continuing a string
9
+ :RETURN => "[SolveBio] Out : %s\n"
10
+ }
11
+
12
+ require 'solvebio'
13
+ require 'solvebio/cli'
14
+
15
+ have_completion = nil
16
+ begin
17
+ require 'bond' and require 'bond/completion'
18
+ have_completion = 'bond'
19
+ rescue LoadError
20
+ begin
21
+ have_completion = require 'irb/completion'
22
+ rescue LoadError
23
+ have_completion = false
24
+ end
25
+ end
26
+
27
+ unless have_completion
28
+ if have_completion != 'bond'
29
+ puts "Please install the 'bond' getm for better autocompletion"
30
+ end
31
+ end
32
+
33
+ # If an API key is set in SolveBio.api_key, use that.
34
+ # Otherwise, look for credentials in the local file,
35
+ # Otherwise, ask the user to log in.
36
+
37
+ if SolveBio.api_key or SolveBio::CLI::Credentials.get_credentials
38
+ email, SolveBio.api_key = SolveBio::CLI::Auth::whoami
39
+ else
40
+ SolveBio::CLI::Auth.login
41
+ end
42
+
43
+ if not SolveBio.api_key
44
+ puts("SolveBio requires a valid account. To sign up, visit: https://www.solvebio.com/signup")
45
+ exit 1
46
+ end
47
+
48
+ include SolveBio
@@ -0,0 +1,12 @@
1
+ module SolveBio
2
+ module CLI
3
+ module Tutorial
4
+ def tutorial
5
+ puts <<-TUTORIAL
6
+ # Tutorial coming soon!
7
+ TUTORIAL
8
+ end
9
+ module_function :tutorial
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,149 @@
1
+ module SolveBio
2
+ class Client
3
+ attr_reader :headers, :api_host
4
+ attr_accessor :api_key
5
+
6
+ # Add our own kind of Authorization tokens. This has to be
7
+ # done this way, late, because the rest-client gem looks for
8
+ # .netrc and will set basic authentication if it finds a match.
9
+ RestClient.add_before_execution_proc do | req, args |
10
+ if args[:authorization]
11
+ req.instance_variable_get('@header')['authorization'] = [args[:authorization]]
12
+ end
13
+ end
14
+
15
+ def initialize(api_key=nil, api_host=nil)
16
+ @api_key = api_key || SolveBio.api_key
17
+ @api_host = api_host || SolveBio.api_host
18
+
19
+ # Mirroring comments from:
20
+ # http://ruby-doc.org/stdlib-2.1.2/libdoc/net/http/rdoc/Net/HTTP.html
21
+ # gzip compression is used in preference to deflate
22
+ # compression, which is used in preference to no compression.
23
+ @headers = {
24
+ :content_type => :json,
25
+ :accept => :json,
26
+ 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
27
+ 'User-Agent' => 'SolveBio Ruby Client %s' % [
28
+ SolveBio::VERSION,
29
+ ]
30
+ }
31
+ end
32
+
33
+ DEFAULT_REQUEST_OPTS = {
34
+ :raw => false,
35
+ :default_headers => true
36
+ }
37
+
38
+ # Issues an HTTP GET across the wire via the Ruby 'rest-client'
39
+ # library. See *request()* for information on opts.
40
+ def get(url, opts={})
41
+ request('get', url, opts)
42
+ end
43
+
44
+ # Issues an HTTP POST across the wire via the Ruby 'rest-client'
45
+ # library. See *request* for information on opts.
46
+ def post(url, data, opts={})
47
+ opts[:payload] =
48
+ if opts.member?(:no_json)
49
+ data
50
+ else
51
+ data.to_json
52
+ end
53
+ request('post', url, opts)
54
+ end
55
+
56
+ # Issues an HTTP Request across the wire via the Ruby 'rest-client'
57
+ # library.
58
+ def request(method, url, opts={})
59
+ opts = DEFAULT_REQUEST_OPTS.merge(opts)
60
+
61
+ # Expand URL with API host if none was given
62
+ api_host = @api_host or SolveBio.api_host
63
+
64
+ if not api_host
65
+ raise SolveError.new('No SolveBio API host is set')
66
+ elsif not url.start_with?(api_host)
67
+ url = Addressable::URI.join(api_host, url).to_s
68
+ end
69
+
70
+ # Handle some default options and add authorization header
71
+ if opts[:default_headers] and @api_key
72
+ headers = @headers.merge(opts[:headers]||{})
73
+ authorization = "Token #{@api_key}"
74
+ else
75
+ headers = nil
76
+ authorization = nil
77
+ end
78
+
79
+ SolveBio::logger.debug('API %s Request: %s' % [method.upcase, url])
80
+
81
+ response = nil
82
+ begin
83
+ RestClient::Request.
84
+ execute(:method => method,
85
+ :url => url,
86
+ :headers => headers,
87
+ :authorization => authorization,
88
+ :timeout => opts[:timeout] || 80,
89
+ :payload => opts[:payload]) do
90
+ |resp, request, result, &block|
91
+ response = resp
92
+ if 429 == response.code
93
+ delay = Integer(response.headers[:retry_after])
94
+ SolveBio::logger.warn("Too many requests, sleeping for #{delay}s.")
95
+ sleep(delay)
96
+ return request(method, url, opts)
97
+ elsif response.code < 200 or response.code >= 400
98
+ handle_api_error(response)
99
+ end
100
+ end
101
+ rescue RestClient::Exception => e
102
+ handle_request_error(e)
103
+ end
104
+
105
+ if opts[:raw]
106
+ return response
107
+ end
108
+
109
+ begin
110
+ response = JSON.parse(response)
111
+ rescue JSON::ParserError => e
112
+ handle_request_error(e)
113
+ end
114
+
115
+ Util.symbolize_names(response)
116
+ end
117
+
118
+ def handle_request_error(e)
119
+ err = e.inspect
120
+ SolveBio::logger.error("API Error: #{err}")
121
+ msg = "Unexpected error communicating with SolveBio.\n" +
122
+ "If this problem persists, let us " +
123
+ "know at contact@solvebio.com."
124
+ raise SolveError.new(nil, msg)
125
+ end
126
+
127
+ def handle_api_error(response)
128
+ SolveBio::logger.error("API Error: #{response}") unless
129
+ [400, 401, 403, 404].member?(response.code.to_i)
130
+ raise SolveError.new(response)
131
+ end
132
+
133
+ def self.client
134
+ @@client ||= SolveBio::Client.new()
135
+ end
136
+
137
+ def self.get(url, opts={})
138
+ client.get(url, opts)
139
+ end
140
+
141
+ def self.post(url, data, opts={})
142
+ client.post(url, data, opts)
143
+ end
144
+
145
+ def self.request(method, url, opts={})
146
+ client.request(method, url, opts)
147
+ end
148
+ end
149
+ end