1 import weakref
2 import urllib
3
4 from intermine.results import JSONIterator, EnrichmentLine
5 from intermine.model import ConstraintNode
6 from intermine.errors import ServiceError
9 """
10 Class for representing a List on an InterMine Webservice
11 ========================================================
12
13 Lists represent stored collections of data and saved result
14 sets in an InterMine data warehouse. This class is an abstraction
15 of this information, and provides mechanisms for managing the
16 data.
17
18 SYNOPSIS
19 --------
20
21 example::
22
23 >>> from intermine.webservice import Service
24 >>>
25 >>> flymine = Service("www.flymine.org/query", "SOMETOKEN")
26 >>> new_list = flymine.create_list(["h", "zen", "eve", "bib"], "Gene", name="My New List")
27 >>>
28 >>> another_list = flymine.get_list("Some other list")
29 >>> combined_list = new_list | another_list # Same syntax as for sets
30 >>> combined_list.name = "Union of the other lists"
31 >>>
32 >>> print "The combination of the two lists has %d elements" % combined_list.size
33 >>> print "The combination of the two lists has %d elements" % len(combined_list)
34 >>>
35 >>> for row in combined_list:
36 ... print row
37
38 OVERVIEW
39 --------
40
41 Lists are created from a webservice, and can be manipulated in various ways.
42 The operations are::
43 * Union: this | that
44 * Intersection: this & that
45 * Symmetric Difference: this ^ that
46 * Asymmetric Difference (subtraction): this - that
47 * Appending: this += that
48
49 Lists can be created from a list of identifiers that could be::
50 * stored in a file
51 * held in a list or set
52 * contained in a string
53 In all these cases the syntax is the same:
54
55 >>> new_list = service.create_list(content, type, name="Some name", description="Some description", tags=["some", "tags"])
56
57 Lists can also be created from a query's result with the exact
58 same syntax. In the case of queries, the type is not required,
59 but the query should have just one view, and it should be an id.
60
61 >>> query = service.new_query()
62 >>> query.add_view("Gene.id")
63 >>> query.add_constraint("Gene.length", "<", 100)
64 >>> new_list = service.create_list(query, name="Short Genes")
65
66 """
67
69 """
70 Constructor
71 ===========
72
73 Do not construct these objects yourself. They should be
74 fetched from a service or constructed using the "create_list"
75 method.
76 """
77 try:
78 self._service = args["service"]
79 self._manager = weakref.proxy(args["manager"])
80 self._name = args["name"]
81 self._title = args["title"]
82 self._description = args.get("description")
83 self._list_type = args["type"]
84 self._size = int(args["size"])
85 self._date_created = args.get("dateCreated")
86 self._is_authorized = args.get("authorized")
87 self._status = args.get("status")
88
89 if self._is_authorized is None: self._is_authorized = True
90
91 if "tags" in args:
92 tags = args["tags"]
93 else:
94 tags = []
95
96 self._tags = frozenset(tags)
97 except KeyError:
98 raise ValueError("Missing argument")
99 self.unmatched_identifiers = set([])
100
101 @property
103 """When this list was originally created"""
104 return self._date_created
105
106 @property
110
111 @property
113 """The human readable description of this list"""
114 return self._description
115
116 @property
118 """The fixed title of this list"""
119 return self._title
120
121 @property
123 """The upgrade status of this list"""
124 return self._status
125
126 @property
128 """Whether or not the current user is authorised to make changes to this list"""
129 return self._is_authorized
130
131 @property
133 """The type of the InterMine objects this list can contain"""
134 return self._list_type
135
137 """The name of the list used to access it programmatically"""
138 return self._name
139
141 """
142 Set the name of the list
143 ========================
144
145 Setting the list's name causes the list's name to be updated on the server.
146 """
147 if self._name == new_name:
148 return
149 uri = self._service.root + self._service.LIST_RENAME_PATH
150 params = {
151 "oldname": self._name,
152 "newname": new_name
153 }
154 uri += "?" + urllib.urlencode(params)
155 resp = self._service.opener.open(uri)
156 data = resp.read()
157 resp.close()
158 new_list = self._manager.parse_list_upload_response(data)
159 self._name = new_name
160
162 """Raises an error - lists must always have a name"""
163 raise AttributeError("List names cannot be deleted, only changed")
164
165 @property
167 """Return the number of elements in the list. Also available as len(obj)"""
168 return self._size
169
170 @property
172 """Alias for obj.size. Also available as len(obj)"""
173 return self.size
174
176 """Returns the number of elements in the object"""
177 return self.size
178
179 name = property(get_name, set_name, del_name, "The name of this list")
180
182 if ids is not None:
183 self.unmatched_identifiers.update(ids)
184
192
194 """
195 Delete this list from the webservice
196 ====================================
197
198 Calls the webservice to delete this list immediately. This
199 object should not be used after this method is called - attempts
200 to do so will raise errors.
201 """
202 self._manager.delete_lists([self])
203
205 """
206 Construct a query to fetch the items in this list
207 =================================================
208
209 Return a new query constrained to the objects in this list,
210 and with a single view column of the objects ids.
211
212 @rtype: intermine.query.Query
213 """
214 q = self._service.new_query(self.list_type)
215 q.add_constraint(self.list_type, "IN", self.name)
216 return q
217
219 """
220 Implementation of trait that allows use of these objects in list constraints
221 """
222 return ConstraintNode(path, op, self.name)
223
225 """Return an iterator over the objects in this list, with all attributes selected for output"""
226 return iter(self.to_query())
227
229 """Get a member of this list by index"""
230 if not isinstance(index, int):
231 raise IndexError("Expected an integer key - got %s" % (index))
232 if index < 0:
233 i = self.size + index
234 else:
235 i = index
236
237 if i not in range(self.size):
238 raise IndexError("%d is not a valid index for a list of size %d" % (index, self.size))
239
240 return self.to_query().first(start=i, row="jsonobjects")
241
243 """
244 Intersect this list and another
245 """
246 return self._manager.intersect([self, other])
247
249 """
250 Intersect this list and another, and replace this list with the result of the
251 intersection
252 """
253 intersection = self._manager.intersect([self, other], description=self.description, tags=self.tags)
254 self.delete()
255 intersection.name = self.name
256 return intersection
257
259 """
260 Return the union of this list and another
261 """
262 return self._manager.union([self, other])
263
265 """
266 Return the union of this list and another
267 """
268 return self._manager.union([self, other])
269
271 """
272 Append other to this list.
273 """
274 return self.append(other)
275
277 name = self.name
278 data = None
279
280 try:
281 ids = open(content).read()
282 except (TypeError, IOError):
283 if isinstance(content, basestring):
284 ids = content
285 else:
286 try:
287 ids = "\n".join(map(lambda x: '"' + x + '"', iter(content)))
288 except TypeError:
289 content = self._manager._get_listable_query(content)
290 uri = content.get_list_append_uri()
291 params = content.to_query_params()
292 params["listName"] = name
293 params["path"] = None
294 form = urllib.urlencode(params)
295 resp = self._service.opener.open(uri, form)
296 data = resp.read()
297
298 if data is None:
299 uri = self._service.root + self._service.LIST_APPENDING_PATH
300 query_form = {'name': name}
301 uri += "?" + urllib.urlencode(query_form)
302 data = self._service.opener.post_plain_text(uri, ids)
303
304 new_list = self._manager.parse_list_upload_response(data)
305 self.unmatched_identifiers.update(new_list.unmatched_identifiers)
306 self._size = new_list.size
307 return self
308
310 """Append the arguments to this list"""
311 try:
312 return self._do_append(self._manager.union(appendix))
313 except:
314 return self._do_append(appendix)
315
316 - def calculate_enrichment(self, widget, background = None, correction = "Holm-Bonferroni", maxp = 0.05, filter = ''):
317 """
318 Perform an enrichment calculation on this list
319 ==============================================
320
321 example::
322
323 >>> for item in service.get_list("some list").calculate_enrichment("thingy_enrichment"):
324 ... print item.identifier, item.p_value
325
326 Gets an iterator over the rows for an enrichment calculation. Each row represents
327 a record with the following properties:
328 * identifier {str}
329 * p-value {float}
330 * matches {int}
331 * description {str}
332
333 The enrichment row object may be treated as an object with property access, or as
334 a dictionary, supporting key lookup with the [] operator:
335
336 >>> p_value = row['p-value']
337
338 """
339 if self._service.version < 8:
340 raise ServiceError("This service does not support enrichment requests")
341 params = dict(list = self.name, widget = widget, correction = correction, maxp = maxp, filter = filter)
342 if background is not None:
343 if self._service.version < 11:
344 raise ServiceError("This service does not support custom background populations")
345 params["population"] = background
346 form = urllib.urlencode(params)
347 uri = self._service.root + self._service.LIST_ENRICHMENT_PATH
348 resp = self._service.opener.open(uri, form)
349 return JSONIterator(resp, EnrichmentLine)
350
352 """Calculate the symmetric difference of this list and another"""
353 return self._manager.xor([self, other])
354
356 """Calculate the symmetric difference of this list and another and replace this list with the result"""
357 diff = self._manager.xor([self, other], description=self.description, tags=self.tags)
358 self.delete()
359 diff.name = self.name
360 return diff
361
363 """Subtract the other from this list"""
364 return self._manager.subtract([self], [other])
365
372
381
390
399