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.
@@ -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