solvebio 1.5.0

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 (54) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +13 -0
  3. data/Gemfile +4 -0
  4. data/Gemspec +3 -0
  5. data/LICENSE +21 -0
  6. data/Makefile +17 -0
  7. data/README.md +64 -0
  8. data/Rakefile +59 -0
  9. data/bin/solvebio.rb +36 -0
  10. data/demo/README.md +14 -0
  11. data/demo/dataset/facets.rb +13 -0
  12. data/demo/dataset/field.rb +13 -0
  13. data/demo/depository/README.md +24 -0
  14. data/demo/depository/all.rb +13 -0
  15. data/demo/depository/retrieve.rb +13 -0
  16. data/demo/depository/versions-all.rb +13 -0
  17. data/demo/query/query-filter.rb +30 -0
  18. data/demo/query/query.rb +13 -0
  19. data/demo/query/range-filter.rb +18 -0
  20. data/demo/test-api.rb +98 -0
  21. data/lib/apiresource.rb +130 -0
  22. data/lib/cli/auth.rb +122 -0
  23. data/lib/cli/help.rb +13 -0
  24. data/lib/cli/irb.rb +58 -0
  25. data/lib/cli/irbrc.rb +53 -0
  26. data/lib/cli/options.rb +75 -0
  27. data/lib/client.rb +152 -0
  28. data/lib/credentials.rb +67 -0
  29. data/lib/errors.rb +81 -0
  30. data/lib/filter.rb +312 -0
  31. data/lib/help.rb +46 -0
  32. data/lib/locale.rb +47 -0
  33. data/lib/main.rb +37 -0
  34. data/lib/query.rb +415 -0
  35. data/lib/resource.rb +414 -0
  36. data/lib/solvebio.rb +14 -0
  37. data/lib/solveobject.rb +101 -0
  38. data/lib/tabulate.rb +706 -0
  39. data/solvebio.gemspec +75 -0
  40. data/test/data/netrc-save +6 -0
  41. data/test/helper.rb +3 -0
  42. data/test/test-auth.rb +54 -0
  43. data/test/test-client.rb +27 -0
  44. data/test/test-error.rb +36 -0
  45. data/test/test-filter.rb +70 -0
  46. data/test/test-netrc.rb +42 -0
  47. data/test/test-query-batch.rb +60 -0
  48. data/test/test-query-init.rb +29 -0
  49. data/test/test-query-paging.rb +123 -0
  50. data/test/test-query.rb +88 -0
  51. data/test/test-resource.rb +47 -0
  52. data/test/test-solveobject.rb +27 -0
  53. data/test/test-tabulate.rb +127 -0
  54. metadata +158 -0
data/lib/resource.rb ADDED
@@ -0,0 +1,414 @@
1
+ # -*- coding: utf-8 -*-
2
+ # from utils.tabulate import tabulate
3
+
4
+ require_relative 'solveobject'
5
+ require_relative 'apiresource'
6
+ require_relative 'client'
7
+ require_relative 'query'
8
+ require_relative 'help'
9
+
10
+ class SolveBio::ListObject < SolveBio::SolveObject
11
+
12
+ include Enumerable
13
+
14
+ def all(params={})
15
+ return request('get', self['url'], params)
16
+ end
17
+
18
+ def create(params={})
19
+ return request('post', self['url'], params)
20
+ end
21
+
22
+ def next_page(params={})
23
+ if self['links']['next']
24
+ return request('get', self['links']['next'], params)
25
+ end
26
+ return nil
27
+ end
28
+
29
+ def prev_page(params={})
30
+ if self['links']['prev']
31
+ request('get', self['links']['prev'], params)
32
+ end
33
+ return nil
34
+ end
35
+
36
+ def at(i)
37
+ self.to_a[i]
38
+ end
39
+
40
+ def to_a
41
+ return to_solve_object(self['data'])
42
+ end
43
+
44
+ def each(*pass)
45
+ return self unless block_given?
46
+ i = 0
47
+ ary = self.dup
48
+ done = false
49
+ until done
50
+ if i >= ary['data'].size
51
+ ary = next_page
52
+ break unless ary
53
+ i = 0
54
+ end
55
+ yield(ary.at(i))
56
+ i += 1
57
+ end
58
+ return self
59
+ end
60
+
61
+ def first
62
+ self['data'][0]
63
+ end
64
+
65
+ # def max
66
+ # self['data'][self['total']]
67
+ # end
68
+
69
+ end
70
+
71
+
72
+ class SingletonAPIResource < SolveBio::APIResource
73
+
74
+ def self.retrieve(cls)
75
+ return super(SingletonAPIResource, cls).retrieve(nil)
76
+ end
77
+
78
+ def self.class_url(cls)
79
+ # cls_name = cls.class_name()
80
+ cls_name = cls.to_s.sub('SolveBio::', '')
81
+ cls_name = camelcase_to_underscore(cls_name)
82
+ return "/v1/%s #{cls_name}"
83
+ end
84
+
85
+ def instance_url
86
+ class_url()
87
+ end
88
+ end
89
+
90
+
91
+ # API resources
92
+
93
+ class SolveBio::User < SingletonAPIResource
94
+ end
95
+
96
+
97
+ class SolveBio::Depository < SolveBio::APIResource
98
+
99
+ include SolveBio::CreateableAPIResource
100
+ include SolveBio::ListableAPIResource
101
+ include SolveBio::SearchableAPIResource
102
+ include SolveBio::UpdateableAPIResource
103
+ include SolveBio::HelpableAPIResource
104
+
105
+ ALLOW_FULL_NAME_ID = true
106
+ FULL_NAME_REGEX = %r{^[\w\-\.]+$}
107
+
108
+ # lookup by ID or full name
109
+ def self.retrieve(id, params={})
110
+ if id.kind_of?(String)
111
+ _id = id.strip
112
+ id = nil
113
+ if _id =~ FULL_NAME_REGEX
114
+ params['full_name'] = _id
115
+ else
116
+ raise Exception, 'Unrecognized full name: "%s"' % _id
117
+ end
118
+ end
119
+
120
+ return SolveBio::APIResource.
121
+ retrieve(SolveBio::Depository, id, params)
122
+ end
123
+
124
+ def versions_url
125
+ return SolveBio::APIResource.
126
+ retrieve(SolveBio::Depository, self['id'])['versions_url']
127
+ end
128
+
129
+ def versions(name=nil, params={})
130
+ # construct the depo version full name
131
+ return SolveBio::DepositoryVersion.
132
+ retrieve("#{self['full_name']}/#{name}") if name
133
+
134
+ response = SolveBio::Client.
135
+ client.request('get', versions_url, params)
136
+ return response.to_solvebio
137
+ end
138
+
139
+ end
140
+
141
+ class SolveBio::DepositoryVersion < SolveBio::APIResource
142
+
143
+
144
+ include SolveBio::CreateableAPIResource
145
+ include SolveBio::ListableAPIResource
146
+ include SolveBio::UpdateableAPIResource
147
+ include SolveBio::HelpableAPIResource
148
+
149
+ ALLOW_FULL_NAME_ID = true
150
+
151
+ # FIXME: base off of Depository::FULL_NAME_REGEX
152
+ # Sample matches:
153
+ # 'Clinvar/2.0.0-1'
154
+ FULL_NAME_REGEX = %r{^[\w\.]+/[\w\-\.]+$}
155
+
156
+ # Supports lookup by full name
157
+ def self.retrieve(id, params={})
158
+ if id.kind_of?(String)
159
+ _id = id.strip
160
+ id = nil
161
+ if _id =~ FULL_NAME_REGEX
162
+ params['full_name'] = _id
163
+ else
164
+ raise Exception, 'Unrecognized full name.'
165
+ end
166
+ end
167
+
168
+ return SolveBio::APIResource.
169
+ retrieve(SolveBio::DepositoryVersion, id, params)
170
+ end
171
+
172
+ def datasets_url(name=nil)
173
+ name ||= self['name']
174
+ "#{self['full_name']}/#{name}"
175
+ end
176
+
177
+ def datasets(name=nil, params={})
178
+ if name
179
+ # construct the dataset full name
180
+ return SolveBio::Dataset.retrieve(datasets_url(name))
181
+ end
182
+
183
+ response = SolveBio::Client.
184
+ client.request('get', datasets_url, params)
185
+ return response.to_solvebio
186
+ end
187
+
188
+ # Set the released flag and optional release date and save
189
+ def release(released_at=nil)
190
+ if released_at
191
+ @released_at = released_at
192
+ end
193
+ @released = true
194
+ save()
195
+ end
196
+
197
+ # Unset the released flag and save
198
+ def unrelease
199
+ @released = false
200
+ save()
201
+ end
202
+
203
+ # FIXME: is there a better field to sort on?
204
+ def <=>(other)
205
+ self.id <=> other.id
206
+ end
207
+
208
+ end
209
+
210
+ class SolveBio::Dataset < SolveBio::APIResource
211
+
212
+ include SolveBio::CreateableAPIResource
213
+ include SolveBio::ListableAPIResource
214
+ include SolveBio::UpdateableAPIResource
215
+ include SolveBio::HelpableAPIResource
216
+
217
+ ALLOW_FULL_NAME_ID = true
218
+
219
+ # FIXME: base off of DepositoryVersion::FULL_NAME_REGEX
220
+ # Sample matches:
221
+ # 'Clinvar/2.0.0-1/Variants'
222
+ # 'omim/0.0.1-1/omim'
223
+ FULL_NAME_REGEX = %r{^([\w\-\.]+/){2}[\w\-\.]+$}
224
+
225
+ # Dataset lookup by full string name
226
+ def self.retrieve(id, params={})
227
+ if id.kind_of?(String)
228
+ _id = id.strip
229
+ id = nil
230
+ if _id =~ FULL_NAME_REGEX
231
+ params['full_name'] = _id
232
+ else
233
+ raise Exception, 'Unrecognized full name.'
234
+ end
235
+ end
236
+
237
+ return SolveBio::APIResource.
238
+ retrieve(SolveBio::Dataset, id, params)
239
+ end
240
+
241
+ def depository_version
242
+ return SolveBio::DepositoryVersion.
243
+ retrieve(self['depository_version'])
244
+ end
245
+
246
+ def depository
247
+ return SolveBio::Depository.retrieve(self['depository'])
248
+ end
249
+
250
+ def fields(name=nil, params={})
251
+ unless self['fields_url']
252
+ raise Exception,
253
+ 'Please use Dataset.retrieve({ID}) before doing looking ' +
254
+ 'up fields'
255
+ end
256
+
257
+ if name
258
+ # construct the field's full_name if a field name is provided
259
+ return DatasetField.retrieve("#{self['full_name']}/#{name}")
260
+ end
261
+
262
+ SolveBio::Client.
263
+ client.request('get', self['fields_url']).to_solvebio
264
+ end
265
+
266
+ def query(params={})
267
+ paging = false
268
+ if params.member?(:paging)
269
+ paging = params[:paging]
270
+ params.delete(:paging)
271
+ end
272
+ q = paging ? SolveBio::PagingQuery.new(self['id'], params) :
273
+ SolveBio::Query.new(self['id'], params)
274
+
275
+ if params[:filters]
276
+ return q.filter(params[:filters])
277
+ end
278
+ return q
279
+ end
280
+
281
+ private
282
+ def data_url
283
+ unless self['data_url']
284
+ unless self['id']
285
+ raise Exception,
286
+ 'No Dataset ID was provided. ' +
287
+ 'Please instantiate the Dataset ' +
288
+ 'object with an ID or full_name.'
289
+ end
290
+ # automatically construct the data_url from the ID
291
+ return instance_url() + '/data'
292
+ end
293
+ return self['data_url']
294
+ end
295
+
296
+ end
297
+
298
+ class SolveBio::DatasetField < SolveBio::APIResource
299
+
300
+ include SolveBio::CreateableAPIResource
301
+ include SolveBio::ListableAPIResource
302
+ include SolveBio::UpdateableAPIResource
303
+
304
+ ALLOW_FULL_NAME_ID = true
305
+ FULL_NAME_REGEX = %r{^([\w\-\.]+/){3}[\w\-\.]+$}
306
+
307
+ # Supports lookup by ID or full name
308
+ def self.retrieve(id, params={})
309
+ if id.kind_of?(String)
310
+ _id = id.strip
311
+ id = nil
312
+ if FULL_NAME_REGEX =~ _id
313
+ params['full_name'] = _id
314
+ else
315
+ raise Exception, 'Unrecognized full name.'
316
+ end
317
+ end
318
+
319
+ return SolveBio::APIResource.
320
+ retrieve(SolveBio::DatasetField, id, params)
321
+ end
322
+
323
+ def facets_url
324
+ return "/v1/dataset_fields/#{self.id}/facets"
325
+ end
326
+
327
+ def facets(params={})
328
+ response = SolveBio::Client.
329
+ client.request('get', facets_url, params)
330
+ return response.to_solvebio
331
+ end
332
+
333
+ def help
334
+ facets
335
+ end
336
+ end
337
+
338
+ SolveBio::SolveObject::CONVERSION = {
339
+ 'Depository' => SolveBio::Depository,
340
+ 'DepositoryVersion' => SolveBio::DepositoryVersion,
341
+ 'Dataset' => SolveBio::Dataset,
342
+ 'DatasetField' => SolveBio::DatasetField,
343
+ 'User' => SolveBio::User,
344
+ 'list' => SolveBio::ListObject
345
+ }
346
+
347
+ class Hash
348
+ def to_solvebio
349
+ resp = self.dup()
350
+ klass_name = resp['class_name']
351
+ if klass_name.kind_of?(String)
352
+ klass = SolveBio::SolveObject::CONVERSION[klass_name] ||
353
+ SolveBio::SolveObject
354
+ else
355
+ klass = SolveBio::SolveObject
356
+ end
357
+ SolveBio::SolveObject::construct_from(klass, resp)
358
+ end
359
+ end
360
+
361
+ class Array
362
+ def to_solvebio
363
+ return self.map{|i| to_solve_object(i)}
364
+ end
365
+ end
366
+
367
+
368
+ def to_solve_object(resp)
369
+ if resp.kind_of?(Array) or
370
+ (not resp.kind_of? SolveBio::SolveObject and resp.kind_of?(Hash))
371
+ resp.to_solvebio
372
+ else
373
+ return resp
374
+ end
375
+ end
376
+
377
+ if __FILE__ == $0
378
+ puts '-' * 50
379
+ resp = {
380
+ 'class_name' => 'Dataset',
381
+ 'data_url' => 'https://api.solvebio.com/v1/datasets/25/data',
382
+ 'depository' => 'ClinVar',
383
+ 'depository_id' => 223,
384
+ 'depository_version' => 'ClinVar/2.0.0-1',
385
+ 'depository_version_id' => 15,
386
+ 'description' => '',
387
+ 'fields_url' => 'https://api.solvebio.com/v1/datasets/25/fields',
388
+ 'full_name' => 'ClinVar/2.0.0-1/Variants',
389
+ 'id' => 25,
390
+ 'name' => 'Variants',
391
+ 'title' => 'Variants',
392
+ 'url' => 'https://api.solvebio.com/v1/datasets/25'
393
+ }
394
+ so = to_solve_object(resp)
395
+ so = resp.to_solvebio
396
+ puts so.inspect
397
+ puts so.to_s
398
+
399
+ if ARGV[0]
400
+ require_relative './cli/auth.rb'
401
+ include SolveBio::Auth
402
+ login
403
+ puts '-' * 30, ' HELP ', '-' * 30
404
+ puts SolveBio::Depository.retrieve('ClinVar').help
405
+ puts '-' * 30, ' Retrieve ClinVar ','-' * 30
406
+ puts SolveBio::Depository.retrieve('ClinVar').to_s
407
+ puts '-' * 30, ' Versions ClinVar ','-' * 30
408
+ puts SolveBio::Depository.retrieve('Clinvar').versions.to_s
409
+ puts '-' * 30, ' Dataset ','-' * 30
410
+ puts SolveBio::Dataset.retrieve('Clinvar/2.0.0-1/Variants').to_s
411
+ puts '-' * 30, ' All Depository ','-' * 30
412
+ puts SolveBio::Depository.all.to_s
413
+ end
414
+ end
data/lib/solvebio.rb ADDED
@@ -0,0 +1,14 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Something to pull in the entire SolveBio API.
3
+
4
+ require_relative 'resource'
5
+ require_relative 'query'
6
+
7
+ # cli/auth is a little nicer than credentials
8
+ # FIXME: consider moving cli/auth moving out of cli?
9
+ require_relative 'cli/auth'
10
+
11
+ # Set authentication if possible
12
+ include SolveBio::Credentials
13
+ creds = get_credentials()
14
+ SolveBio.api_key = SolveBio::Client.client.api_key = creds[1] if creds
@@ -0,0 +1,101 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'json'
4
+ require 'set'
5
+ require_relative 'client'
6
+
7
+ # Add underscore before internal uppercase letters. Also, lowercase
8
+ # all letters.
9
+ def camelcase_to_underscore(name)
10
+ # Using [[:upper:]] and [[:lower]] should help with Unicode.
11
+ s1 = name.gsub(/(.)([[:upper:]])([[:lower:]]+)/){"#{$1}_#{$2}#{$3}"}
12
+ return (s1.gsub(/([a-z0-9])([[:upper:]])/){"#{$1}_#{$2}"}).downcase
13
+ end
14
+
15
+ # Base class for all SolveBio API resource objects
16
+ class SolveBio::SolveObject < Hash
17
+
18
+ ALLOW_FULL_NAME_ID = false # Treat full_name parameter as an ID?
19
+
20
+ attr_reader :unsaved_values
21
+
22
+ def allow_full_name_id
23
+ self.class.const_get(:ALLOW_FULL_NAME_ID)
24
+ end
25
+
26
+ def initialize(id=nil, params={})
27
+
28
+ super()
29
+ # store manually updated values for partial updates
30
+ @unsaved_values = Set.new
31
+
32
+ if id
33
+ self['id'] = id
34
+ elsif allow_full_name_id and params['full_name']
35
+ self['full_name'] = params['full_name']
36
+ # no ID was provided so temporarily set the id as full_name
37
+ # this will get updated when the resource is refreshed
38
+ self['id'] = params['full_name']
39
+ end
40
+ end
41
+
42
+ # Element Reference — Retrieves the value object corresponding to the key object.
43
+ # Note: *key* is turned into a string before access, because the underlying key type
44
+ # is a string.
45
+ def [](key)
46
+ return super(key.to_s)
47
+ end
48
+
49
+ def self.construct_from(cls, values)
50
+ instance = cls.new(values['id'])
51
+ instance.refresh_from(values)
52
+ return instance
53
+ end
54
+
55
+ def refresh_from(values)
56
+ self.clear()
57
+ @unsaved_values = Set.new
58
+ values.each { |k, v| self[k] = to_solve_object(v) }
59
+ end
60
+
61
+ def request(method, url, params=nil)
62
+ response = SolveBio::Client.client.request(method, url, params)
63
+ return to_solve_object(response)
64
+ end
65
+
66
+ def inspect
67
+ ident_parts = [self.class]
68
+
69
+ if self['id'].kind_of?(Integer)
70
+ ident_parts << "id=#{self['id']}"
71
+ end
72
+
73
+ if allow_full_name_id and self['full_name']
74
+ ident_parts << "full_name=#{self['full_name']}"
75
+ end
76
+
77
+ return '<%s:%x> JSON: %s' % [ident_parts.join(' '),
78
+ self.object_id, self.to_json]
79
+
80
+ end
81
+
82
+ def to_s
83
+ # No equivalent of Python's json sort_keys?
84
+ return JSON.pretty_generate(self, :indent => ' ')
85
+ # return self.to_json json.dumps(self, sort_keys=true, indent=2)
86
+ end
87
+
88
+ # @property
89
+ def id
90
+ return self['id']
91
+ end
92
+ end
93
+
94
+ if __FILE__ == $0
95
+ %w(abc abcDef abc01Def aBcDef a1B2C3 ?Foo Dataset).each do |word|
96
+ puts word + " -> " + camelcase_to_underscore(word)
97
+ end
98
+ puts SolveBio::SolveObject.new.inspect
99
+ puts SolveBio::SolveObject.new(64).inspect
100
+
101
+ end