intermine 0.98.01
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|