intermine 0.98.01
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/Gemfile +4 -0
- data/LICENCE +165 -0
- data/MANIFEST +0 -0
- data/README.rdoc +79 -0
- data/Rakefile +67 -0
- data/lib/intermine/lists.rb +716 -0
- data/lib/intermine/model.rb +867 -0
- data/lib/intermine/query.rb +1569 -0
- data/lib/intermine/results.rb +196 -0
- data/lib/intermine/service.rb +253 -0
- data/lib/intermine/version.rb +3 -0
- data/test/data/lists.json +29 -0
- data/test/data/model.json +3 -0
- data/test/data/resultobjs.json +3 -0
- data/test/data/resultrow.json +1 -0
- data/test/data/resultset.json +3 -0
- data/test/data/testmodel_model.xml +94 -0
- data/test/live_test.rb +35 -0
- data/test/test.rb +84 -0
- data/test/test_helper.rb +67 -0
- data/test/test_lists.rb +68 -0
- data/test/test_model.rb +417 -0
- data/test/test_query.rb +1202 -0
- data/test/test_result_row.rb +114 -0
- data/test/test_results.rb +22 -0
- data/test/test_service.rb +86 -0
- data/test/test_sugar.rb +219 -0
- data/test/unit_tests.rb +6 -0
- metadata +192 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require "json"
|
3
|
+
require 'stringio'
|
4
|
+
require 'net/http'
|
5
|
+
|
6
|
+
module InterMine::Results
|
7
|
+
|
8
|
+
class ResultsRow
|
9
|
+
|
10
|
+
def initialize(results, columns)
|
11
|
+
@results = results.is_a?(Array) ? results : JSON.parse(results)
|
12
|
+
unless @results.is_a?(Array)
|
13
|
+
raise ArgumentError, "Bad results format: #{results}"
|
14
|
+
end
|
15
|
+
unless @results.size == columns.size
|
16
|
+
raise ArgumentError, "Column size (#{columns.size}) != results size (#{@results.size})"
|
17
|
+
end
|
18
|
+
|
19
|
+
@columns = columns
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
if key.is_a?(Integer)
|
24
|
+
idx = key
|
25
|
+
else
|
26
|
+
idx = index_for(key)
|
27
|
+
end
|
28
|
+
if idx.nil?
|
29
|
+
raise IndexError, "Bad key: #{key}"
|
30
|
+
end
|
31
|
+
begin
|
32
|
+
result = @results[idx]["value"]
|
33
|
+
rescue NoMethodError
|
34
|
+
raise IndexError, "Bad key: #{key}"
|
35
|
+
end
|
36
|
+
return result
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_a
|
40
|
+
return @results.map {|x| x["value"]}
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_h
|
44
|
+
hash = {}
|
45
|
+
@results.each_index do |x|
|
46
|
+
key = @columns[x].to_s
|
47
|
+
hash[key] = self[x]
|
48
|
+
end
|
49
|
+
return hash
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
bufs = []
|
54
|
+
@results.each_index do |idx|
|
55
|
+
buffer = ""
|
56
|
+
buffer << @columns[idx].to_headless_s
|
57
|
+
buffer << "="
|
58
|
+
buffer << self[idx].to_s
|
59
|
+
bufs << buffer
|
60
|
+
end
|
61
|
+
return @columns.first.rootClass.name + ": " + bufs.join(",\t")
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_csv
|
65
|
+
return @results.map {|x| x["value"].to_s}.join("\t")
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def index_for(key)
|
71
|
+
if @indexes.nil?
|
72
|
+
@indexes = {}
|
73
|
+
@results.each_index do |idx|
|
74
|
+
idx_key = @columns[idx]
|
75
|
+
@indexes[idx_key.to_s] = idx
|
76
|
+
|
77
|
+
## Include root-less paths as aliases
|
78
|
+
# But allow for string columns
|
79
|
+
if idx_key.respond_to?(:to_headless_s)
|
80
|
+
@indexes[idx_key.to_headless_s] = idx
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
return @indexes[key]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
class ResultsReader
|
90
|
+
|
91
|
+
def initialize(uri, query, start, size)
|
92
|
+
@uri = URI(uri)
|
93
|
+
@query = query
|
94
|
+
@start = start
|
95
|
+
@size = size
|
96
|
+
end
|
97
|
+
|
98
|
+
def params(format)
|
99
|
+
p = @query.params.merge("start" => @start, "format" => format)
|
100
|
+
p["size"] = @size unless @size.nil?
|
101
|
+
return p
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_size
|
105
|
+
query = params("jsoncount")
|
106
|
+
res = Net::HTTP.post_form(@uri, query)
|
107
|
+
case res
|
108
|
+
when Net::HTTPSuccess
|
109
|
+
return check_result_set(res.body)["count"]
|
110
|
+
else
|
111
|
+
check_result_set(res.body)
|
112
|
+
res.error!
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def each_line(data)
|
117
|
+
req = Net::HTTP::Post.new(@uri.path)
|
118
|
+
req.set_form_data(data)
|
119
|
+
Net::HTTP.new(@uri.host, @uri.port).start {|http|
|
120
|
+
http.request(req) {|resp|
|
121
|
+
holdover = ""
|
122
|
+
resp.read_body {|chunk|
|
123
|
+
sock = StringIO.new(holdover + chunk)
|
124
|
+
sock.each_line {|line|
|
125
|
+
if sock.eof?
|
126
|
+
holdover = line
|
127
|
+
else
|
128
|
+
yield line
|
129
|
+
end
|
130
|
+
}
|
131
|
+
sock.close
|
132
|
+
}
|
133
|
+
yield holdover
|
134
|
+
}
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
def each_row
|
139
|
+
container = ''
|
140
|
+
self.each_line(params("jsonrows")) do |line|
|
141
|
+
if line.start_with?("[")
|
142
|
+
begin
|
143
|
+
row = ResultsRow.new(line.chomp("," + $/), @query.views)
|
144
|
+
rescue => e
|
145
|
+
raise ServiceError, "Error parsing #{line}: #{e.message}"
|
146
|
+
end
|
147
|
+
yield row
|
148
|
+
else
|
149
|
+
container << line
|
150
|
+
end
|
151
|
+
end
|
152
|
+
check_result_set(container)
|
153
|
+
end
|
154
|
+
|
155
|
+
def each_result
|
156
|
+
model = @query.model
|
157
|
+
container = ''
|
158
|
+
self.each_line(params("jsonobjects")) do |line|
|
159
|
+
line.chomp!("," + $/)
|
160
|
+
if line.start_with?("{") and line.end_with?("}")
|
161
|
+
begin
|
162
|
+
data = JSON.parse(line)
|
163
|
+
result = model.make_new(data)
|
164
|
+
rescue JSON::ParserError => e
|
165
|
+
raise ServiceError, "Error parsing #{line}: #{e.message}"
|
166
|
+
rescue => e
|
167
|
+
raise ServiceError, "Could not instantiate this result object: #{e.message}"
|
168
|
+
end
|
169
|
+
yield result
|
170
|
+
else
|
171
|
+
container << line
|
172
|
+
end
|
173
|
+
end
|
174
|
+
check_result_set(container)
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def check_result_set(container)
|
180
|
+
begin
|
181
|
+
result_set = JSON.parse(container)
|
182
|
+
rescue JSON::ParserError => e
|
183
|
+
raise "Error parsing container: #{container}, #{e.message}"
|
184
|
+
end
|
185
|
+
unless result_set["wasSuccessful"]
|
186
|
+
raise ServiceError, result_set["error"]
|
187
|
+
end
|
188
|
+
result_set
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class ServiceError < RuntimeError
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'intermine/model'
|
3
|
+
require "intermine/query"
|
4
|
+
require "intermine/lists"
|
5
|
+
require "rexml/document"
|
6
|
+
require "forwardable"
|
7
|
+
require "cgi"
|
8
|
+
|
9
|
+
module InterMine
|
10
|
+
|
11
|
+
# A Representation of Connection to an InterMine Webservice
|
12
|
+
#
|
13
|
+
# == Synopsis
|
14
|
+
#
|
15
|
+
# require "intermine/service"
|
16
|
+
# service = Service.new("www.flymine.org/query")
|
17
|
+
#
|
18
|
+
# query = service.query("Gene")
|
19
|
+
# list = service.list("My-Favourite-List")
|
20
|
+
# template = service.template("Probe_Gene")
|
21
|
+
# model = service.model
|
22
|
+
#
|
23
|
+
# == Description
|
24
|
+
#
|
25
|
+
# The service object is the gateway to all InterMine webservice
|
26
|
+
# functionality, and the mechanism by which resources such as
|
27
|
+
# queries, lists, templates and models can be obtained. In itself, it
|
28
|
+
# doen't do much, but it facilitates all the operations that can be achieved
|
29
|
+
# with the InterMine API.
|
30
|
+
#
|
31
|
+
# == Using Queries
|
32
|
+
#
|
33
|
+
# service.query("Gene").
|
34
|
+
# select("*", "proteins.proteinDomains.*").
|
35
|
+
# where(:symbol => %{h bib r eve zen}).
|
36
|
+
# order_by(:molecularWeight).
|
37
|
+
# each_result do |gene|
|
38
|
+
# handle_result(gene)
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# Queries are arbitrarily complex requests for data over the whole resources
|
42
|
+
# of the data-warehouse, similar in scope and design to the SQL queries that
|
43
|
+
# they are eventually run as in the webservice. The PathQuery::Query object
|
44
|
+
# can be obtained directly from the service with the Service#query constructor.
|
45
|
+
# See PathQuery::Query for more details.
|
46
|
+
#
|
47
|
+
# == Using Templates
|
48
|
+
#
|
49
|
+
# service.template("Probe_Genes").each_row(:A => "probeid") do |row|
|
50
|
+
# puts row["gene.primaryIdentifier"]
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# Templates are a simpler way of running queries, as they are mostly
|
54
|
+
# predefined, and simply require a parameter or two to be changed to get
|
55
|
+
# different results. They are an effective way of saving and repeating workflows,
|
56
|
+
# and can be precomputed to improve response times for queries you run frequently.
|
57
|
+
#
|
58
|
+
# See PathQuery::Template
|
59
|
+
#
|
60
|
+
# == Using Lists
|
61
|
+
#
|
62
|
+
# new_list = service.create_list("my/gene_list.txt", "Gene", ["experimentA", "projectB"])
|
63
|
+
# existing_list = service.list("Genes I'm interested in")
|
64
|
+
# intersection = new_list & existing_list
|
65
|
+
# intersection.name = "My genes from experimentA"
|
66
|
+
#
|
67
|
+
# Lists are saved result-sets and user curated collections of objects
|
68
|
+
# of a particular type. You may have a list of Genes you are particularly
|
69
|
+
# interested in, or Pathways that concern your research. Using lists simplifies
|
70
|
+
# queries against large groups of objects at once, and allows for
|
71
|
+
# more streamlined analysis. Unlike queries and (to a degree) Templates,
|
72
|
+
# Lists are not just about reading in information - they can be created, renamed,
|
73
|
+
# modified, deleted. You can manage your lists effectively and quickly using the API.
|
74
|
+
#
|
75
|
+
# == Inspecting the model
|
76
|
+
#
|
77
|
+
# model = service.model
|
78
|
+
# puts "Classes in the model:"
|
79
|
+
# model.classes.each do |c|
|
80
|
+
# puts c.name
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# The data model, which defines what queries are possible in the data-warehouse,
|
84
|
+
# is fully introspectible and queriable. This allows for sophisticated meta-programming
|
85
|
+
# and dynamic query generation.
|
86
|
+
#
|
87
|
+
class Service
|
88
|
+
|
89
|
+
extend Forwardable
|
90
|
+
|
91
|
+
VERSION_PATH = "/version"
|
92
|
+
MODEL_PATH = "/model/json"
|
93
|
+
TEMPLATES_PATH = "/templates"
|
94
|
+
QUERY_RESULTS_PATH = "/query/results"
|
95
|
+
QUERY_TO_LIST_PATH = "/query/tolist/json"
|
96
|
+
QUERY_APPEND_PATH = "/query/append/tolist/json"
|
97
|
+
TEMPLATE_RESULTS_PATH = "/template/results"
|
98
|
+
TEMPLATE_TO_LIST_PATH = "/template/tolist/json"
|
99
|
+
TEMPLATE_APPEND_PATH = "/template/append/tolist/json"
|
100
|
+
LISTS_PATH = "/lists/json"
|
101
|
+
LIST_APPEND_PATH = "/lists/append/json"
|
102
|
+
LIST_RENAME_PATH = "/lists/rename/json"
|
103
|
+
LIST_UNION_PATH = "/lists/union/json"
|
104
|
+
LIST_DIFFERENCE_PATH = "/lists/diff/json"
|
105
|
+
LIST_INTERSECTION_PATH = "/lists/intersect/json"
|
106
|
+
LIST_SUBTRACTION_PATH = "/lists/subtract/json"
|
107
|
+
|
108
|
+
# The webservice version. An integer that
|
109
|
+
# supplies information about what features are supported.
|
110
|
+
attr_reader :version
|
111
|
+
|
112
|
+
# The root of the query. If you supplied an abbreviated version, this
|
113
|
+
# attribute will hold the expanded URL.
|
114
|
+
attr_reader :root
|
115
|
+
|
116
|
+
# The token you supplied to the constructor.
|
117
|
+
attr_reader :token
|
118
|
+
|
119
|
+
# A collection of the names of any templates that this service was not able to parse,
|
120
|
+
# and you will thus not be able to access.
|
121
|
+
:broken_templates
|
122
|
+
|
123
|
+
def_delegators :@list_manager,
|
124
|
+
:lists, :list, :list_names, :create_list, :delete_lists, :get_lists_with_tags,
|
125
|
+
:union_of, :intersection_of, :symmetric_difference_of, :subtract
|
126
|
+
|
127
|
+
# Construct a new service.
|
128
|
+
#
|
129
|
+
# service = Service.new("www.flymine.org/query", "TOKEN")
|
130
|
+
#
|
131
|
+
# Arguments:
|
132
|
+
# [+root+] The URL to the root of the webservice. For example, for
|
133
|
+
# FlyMine this is: "http://www.flymine.org/query/service"
|
134
|
+
# For simplicity's sake, it is possible to omit the scheme
|
135
|
+
# and the "/service" section, and just pass the bare minimum:
|
136
|
+
# "www.flymine.org/query"
|
137
|
+
# [+token+] An optional API access token to authenticate your webservice access with.
|
138
|
+
# If you supply a token, you will have access to your private lists
|
139
|
+
# and templates.
|
140
|
+
#
|
141
|
+
def initialize(root, token=nil, mock_model=nil)
|
142
|
+
u = URI.parse(root)
|
143
|
+
unless u.scheme
|
144
|
+
root = "http://" + root
|
145
|
+
end
|
146
|
+
unless root.end_with?("/service") # All webservices must
|
147
|
+
root << "/service"
|
148
|
+
end
|
149
|
+
@root = root
|
150
|
+
@token = token
|
151
|
+
begin
|
152
|
+
@version = fetch(@root + VERSION_PATH).to_i
|
153
|
+
rescue => e
|
154
|
+
raise ServiceError, "Error fetching version at #{@root + VERSION_PATH}: #{e.message}"
|
155
|
+
end
|
156
|
+
@model = mock_model
|
157
|
+
@_templates = nil
|
158
|
+
@broken_templates = []
|
159
|
+
@list_manager = InterMine::Lists::ListManager.new(self)
|
160
|
+
end
|
161
|
+
|
162
|
+
# call-seq:
|
163
|
+
# model() => Model
|
164
|
+
#
|
165
|
+
# Retrieve the model from the service. This contains
|
166
|
+
# all the metadata about the data-model which defines what is
|
167
|
+
# queriable.
|
168
|
+
def model
|
169
|
+
if @model.nil?
|
170
|
+
data = fetch(@root + MODEL_PATH)
|
171
|
+
@model = InterMine::Metadata::Model.new(data, self)
|
172
|
+
end
|
173
|
+
@model
|
174
|
+
end
|
175
|
+
|
176
|
+
alias get_model model
|
177
|
+
|
178
|
+
# call-seq:
|
179
|
+
# query(rootClass=nil) => PathQuery::Query
|
180
|
+
#
|
181
|
+
# Create a new query against the data at this webservice
|
182
|
+
def query(rootClass=nil)
|
183
|
+
return InterMine::PathQuery::Query.new(self.model, rootClass, self)
|
184
|
+
end
|
185
|
+
|
186
|
+
alias new_query query
|
187
|
+
|
188
|
+
# call-seq:
|
189
|
+
# template(name) => PathQuery::Template
|
190
|
+
#
|
191
|
+
# Get a Template by name, Returns nil if there is no such template.
|
192
|
+
def template(name)
|
193
|
+
return templates[name]
|
194
|
+
end
|
195
|
+
|
196
|
+
alias get_template template
|
197
|
+
|
198
|
+
|
199
|
+
# call-seq:
|
200
|
+
# templates() => Hash{String => Template}
|
201
|
+
#
|
202
|
+
# Returns all the templates available to query against in the service
|
203
|
+
#
|
204
|
+
def templates
|
205
|
+
if @_templates.nil?
|
206
|
+
@_templates = {}
|
207
|
+
parser = InterMine::PathQuery::Template.parser(model)
|
208
|
+
template_xml = fetch(@root + TEMPLATES_PATH)
|
209
|
+
doc = REXML::Document.new(template_xml)
|
210
|
+
doc.elements.each("template-queries/template") do |t|
|
211
|
+
begin
|
212
|
+
temp = parser.parse(t)
|
213
|
+
@_templates[temp.name] = temp
|
214
|
+
rescue
|
215
|
+
@broken_templates.push(t.attribute("name").value)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
return @_templates
|
220
|
+
end
|
221
|
+
|
222
|
+
# Get all the names of the available templates, in
|
223
|
+
# alphabetical order.
|
224
|
+
def template_names
|
225
|
+
return templates.keys.sort
|
226
|
+
end
|
227
|
+
|
228
|
+
# Get the data used for constructing the lists. Called internally.
|
229
|
+
def get_list_data
|
230
|
+
return fetch(@root + LISTS_PATH)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Get the basic parameters used by all requests. This includes
|
234
|
+
# the authorization token.
|
235
|
+
def params
|
236
|
+
return @token.nil? ? {} : {"token" => @token}
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
# Retrieves data from a url with a get request.
|
242
|
+
def fetch(url)
|
243
|
+
uri = URI.parse(url)
|
244
|
+
qs = params.map { |k,v| "#{k}=#{CGI::escape(v.to_s)}" }.join('&')
|
245
|
+
return Net::HTTP.get(uri.host, uri.path + (params.empty? ? "" : "?#{qs}"))
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Errors resulting from Service failures.
|
250
|
+
class ServiceError < RuntimeError
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|