1 import weakref
2 import urllib
3
4
5 -class List(object):
6 """
7 Class for representing a List on an InterMine Webservice
8 ========================================================
9
10 Lists represent stored collections of data and saved result
11 sets in an InterMine data warehouse. This class is an abstraction
12 of this information, and provides mechanisms for managing the
13 data.
14
15 SYNOPSIS
16 --------
17
18 example::
19
20 from intermine.webservice import Service
21
22 flymine = Service("www.flymine.org/query", "SOMETOKEN")
23 new_list = flymine.create_list(["h", "zen", "eve", "bib"], "Gene", name="My New List")
24
25 another_list = flymine.get_list("Some other list")
26 combined_list = new_list | another_list # Same syntax as for sets
27 combined_list.name = "Union of the other lists"
28
29 print "The combination of the two lists has %d elements" % combined_list.size
30 print "The combination of the two lists has %d elements" % len(combined_list)
31
32 for row in combined_list.to_attribute_query().results():
33 print row
34
35 OVERVIEW
36 --------
37
38 Lists are created from a webservice, and can be manipulated in various ways.
39 The operations are:
40 * Union: this | that
41 * Intersection: this & that
42 * Symmetric Difference: this ^ that
43 * Asymmetric Difference (subtraction): this - that
44 * Appending: this += that
45
46 Lists can be created from a list of identifiers that could be:
47 * stored in a file
48 * held in a list or set
49 * contained in a string
50 In all these cases the syntax is the same:
51
52 new_list = service.create_list(content, type, name="Some name", description="Some description", tags=["some", "tags"])
53
54 Lists can also be created from a query's result with the exact
55 same syntax. In the case of queries, the type is not required,
56 but the query should have just one view, and it should be an id.
57
58 query = service.new_query()
59 query.add_view("Gene.id")
60 query.add_constraint("Gene.length", "<", 100)
61 new_list = service.create_list(query, name="Short Genes")
62
63 """
64
66 """
67 Constructor
68 ===========
69
70 Do not construct these objects yourself. They should be
71 fetched from a service or constructed using the "create_list"
72 method.
73 """
74 try:
75 self._service = args["service"]
76 self._manager = weakref.proxy(args["manager"])
77 self._name = args["name"]
78 self._title = args["title"]
79 self._description = args.get("description")
80 self._list_type = args["type"]
81 self._size = int(args["size"])
82 self._date_created = args.get("dateCreated")
83 self._is_authorized = args.get("authorized")
84
85 if self._is_authorized is None: self._is_authorized = True
86
87 if "tags" in args:
88 tags = args["tags"]
89 else:
90 tags = []
91
92 self._tags = frozenset(tags)
93 except KeyError:
94 raise ValueError("Missing argument")
95 self.unmatched_identifiers = set([])
96
97 @property
99 """When this list was originally created"""
100 return self._date_created
101
102 @property
106
107 @property
109 """The human readable description of this list"""
110 return self._description
111
112 @property
114 """The fixed title of this list"""
115 return self._title
116
117 @property
119 """Whether or not the current user is authorised to make changes to this list"""
120 return self._is_authorized
121
122 @property
124 """The type of the InterMine objects this list can contain"""
125 return self._list_type
126
128 """The name of the list used to access it programmatically"""
129 return self._name
130
132 """
133 Set the name of the list
134 ========================
135
136 Setting the list's name causes the list's name to be updated on the server.
137 """
138 if self._name == new_name:
139 return
140 uri = self._service.root + self._service.LIST_RENAME_PATH
141 params = {
142 "oldname": self._name,
143 "newname": new_name
144 }
145 uri += "?" + urllib.urlencode(params)
146 resp = self._service.opener.open(uri)
147 data = resp.read()
148 resp.close()
149 new_list = self._manager.parse_list_upload_response(data)
150 self._name = new_name
151
153 """Raises an error - lists must always have a name"""
154 raise AttributeError("List names cannot be deleted, only changed")
155
156 @property
158 """Return the number of elements in the list. Also available as len(obj)"""
159 return self._size
160
161 @property
163 """Alias for obj.size. Also available as len(obj)"""
164 return self.size
165
167 """Returns the number of elements in the object"""
168 return self.size
169
170 name = property(get_name, set_name, del_name, "The name of this list")
171
173 if ids is not None:
174 self.unmatched_identifiers.update(ids)
175
183
185 """
186 Delete this list from the webservice
187 ====================================
188
189 Calls the webservice to delete this list immediately. This
190 object should not be used after this method is called - attempts
191 to do so will raise errors.
192 """
193 self._manager.delete_lists([self])
194
196 """
197 Construct a query to fetch the items in this list
198 =================================================
199
200 Return a new query constrained to the objects in this list,
201 and with a single view column of the objects ids.
202
203 @rtype: intermine.query.Query
204 """
205 q = self._service.new_query()
206 q.add_view(self.list_type + ".id")
207 q.add_constraint(self.list_type, "IN", self.name)
208 return q
209
211 """
212 Construct a query to fetch information about the items in this list
213 ===================================================================
214
215 Return a query constrained to contain the objects in this list, with
216 all the attributes of these objects selected for output as view columns
217
218 @rtype: intermine.query.Query
219 """
220 q = self.to_query()
221 attributes = q.model.get_class(self.list_type).attributes
222 q.clear_view()
223 q.add_view(map(lambda x: self.list_type + "." + x.name, attributes))
224 return q
225
227 """Return an iterator over the objects in this list, with all attributes selected for output"""
228 return iter(self.to_attribute_query())
229
231 """Get a member of this list by index"""
232 if not isinstance(index, int):
233 raise IndexError("Expected an integer key - got %s" % (index))
234 if index < 0:
235 i = self.size + index
236 else:
237 i = index
238
239 if i not in range(self.size):
240 raise IndexError("%d is not a valid index for a list of size %d" % (index, self.size))
241
242 return self.to_attribute_query().first(start=i, row="jsonobjects")
243
245 """
246 Intersect this list and another
247 """
248 return self._manager.intersect([self, other])
249
251 """
252 Intersect this list and another, and replace this list with the result of the
253 intersection
254 """
255 intersection = self._manager.intersect([self, other], description=self.description, tags=self.tags)
256 self.delete()
257 intersection.name = self.name
258 return intersection
259
261 """
262 Return the union of this list and another
263 """
264 return self._manager.union([self, other])
265
267 """
268 Return the union of this list and another
269 """
270 return self._manager.union([self, other])
271
273 """
274 Append other to this list.
275 """
276 return self.append(other)
277
279 name = self.name
280 data = None
281
282 try:
283 ids = open(content).read()
284 except (TypeError, IOError):
285 if isinstance(content, basestring):
286 ids = content
287 else:
288 try:
289 ids = "\n".join(map(lambda x: '"' + x + '"', iter(content)))
290 except TypeError:
291 try:
292 uri = content.get_list_append_uri()
293 except:
294 content = content.to_query()
295 uri = content.get_list_append_uri()
296 params = content.to_query_params()
297 params["listName"] = name
298 params["path"] = None
299 form = urllib.urlencode(params)
300 resp = self._service.opener.open(uri, form)
301 data = resp.read()
302
303 if data is None:
304 uri = self._service.root + self._service.LIST_APPENDING_PATH
305 query_form = {'name': name}
306 uri += "?" + urllib.urlencode(query_form)
307 data = self._service.opener.post_plain_text(uri, ids)
308
309 new_list = self._manager.parse_list_upload_response(data)
310 self.unmatched_identifiers.update(new_list.unmatched_identifiers)
311 self._size = new_list.size
312 return self
313
315 "Append the arguments to this list"
316 try:
317 return self._do_append(self._manager.union(appendix))
318 except:
319 return self._do_append(appendix)
320
322 """Calculate the symmetric difference of this list and another"""
323 return self._manager.xor([self, other])
324
326 """Calculate the symmetric difference of this list and another and replace this list with the result"""
327 diff = self._manager.xor([self, other], description=self.description, tags=self.tags)
328 self.delete()
329 diff.name = self.name
330 return diff
331
333 """Subtract the other from this list"""
334 return self._manager.subtract([self], [other])
335
342