intermine 0.98.01

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
@@ -0,0 +1,3 @@
1
+ module Intermine
2
+ VERSION = "0.98.01"
3
+ end