Mercurial > repos > gga > apollo_create_or_update
comparison webapollo.py @ 0:cd595b5d890a draft
planemo upload for repository https://github.com/galaxy-genome-annotation/galaxy-tools/tree/master/tools/apollo commit f745b23c84a615bf434d717c8c0e553a012f0268
author | gga |
---|---|
date | Mon, 11 Sep 2017 05:46:00 -0400 |
parents | |
children | aac9ef1f72e7 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:cd595b5d890a |
---|---|
1 from __future__ import print_function | |
2 | |
3 import argparse | |
4 import collections | |
5 import json | |
6 import logging | |
7 import os | |
8 import time | |
9 | |
10 from abc import abstractmethod | |
11 from builtins import next | |
12 from builtins import object | |
13 from builtins import str | |
14 | |
15 from BCBio import GFF | |
16 | |
17 from Bio import SeqIO | |
18 | |
19 from future import standard_library | |
20 | |
21 import requests | |
22 | |
23 | |
24 standard_library.install_aliases() | |
25 try: | |
26 import StringIO as io | |
27 except BaseException: | |
28 import io | |
29 logging.getLogger("requests").setLevel(logging.CRITICAL) | |
30 log = logging.getLogger() | |
31 | |
32 | |
33 ############################################# | |
34 # BEGIN IMPORT OF CACHING LIBRARY # | |
35 ############################################# | |
36 # This code is licensed under the MIT # | |
37 # License and is a copy of code publicly # | |
38 # available in rev. # | |
39 # e27332bc82f4e327aedaec17c9b656ae719322ed # | |
40 # of https://github.com/tkem/cachetools/ # | |
41 ############################################# | |
42 | |
43 class DefaultMapping(collections.MutableMapping): | |
44 | |
45 __slots__ = () | |
46 | |
47 @abstractmethod | |
48 def __contains__(self, key): # pragma: nocover | |
49 return False | |
50 | |
51 @abstractmethod | |
52 def __getitem__(self, key): # pragma: nocover | |
53 if hasattr(self.__class__, '__missing__'): | |
54 return self.__class__.__missing__(self, key) | |
55 else: | |
56 raise KeyError(key) | |
57 | |
58 def get(self, key, default=None): | |
59 if key in self: | |
60 return self[key] | |
61 else: | |
62 return default | |
63 | |
64 __marker = object() | |
65 | |
66 def pop(self, key, default=__marker): | |
67 if key in self: | |
68 value = self[key] | |
69 del self[key] | |
70 elif default is self.__marker: | |
71 raise KeyError(key) | |
72 else: | |
73 value = default | |
74 return value | |
75 | |
76 def setdefault(self, key, default=None): | |
77 if key in self: | |
78 value = self[key] | |
79 else: | |
80 self[key] = value = default | |
81 return value | |
82 | |
83 | |
84 DefaultMapping.register(dict) | |
85 | |
86 | |
87 class _DefaultSize(object): | |
88 def __getitem__(self, _): | |
89 return 1 | |
90 | |
91 def __setitem__(self, _, value): | |
92 assert value == 1 | |
93 | |
94 def pop(self, _): | |
95 return 1 | |
96 | |
97 | |
98 class Cache(DefaultMapping): | |
99 """Mutable mapping to serve as a simple cache or cache base class.""" | |
100 | |
101 __size = _DefaultSize() | |
102 | |
103 def __init__(self, maxsize, missing=None, getsizeof=None): | |
104 if missing: | |
105 self.__missing = missing | |
106 if getsizeof: | |
107 self.__getsizeof = getsizeof | |
108 self.__size = dict() | |
109 self.__data = dict() | |
110 self.__currsize = 0 | |
111 self.__maxsize = maxsize | |
112 | |
113 def __repr__(self): | |
114 return '%s(%r, maxsize=%r, currsize=%r)' % ( | |
115 self.__class__.__name__, | |
116 list(self.__data.items()), | |
117 self.__maxsize, | |
118 self.__currsize, | |
119 ) | |
120 | |
121 def __getitem__(self, key): | |
122 try: | |
123 return self.__data[key] | |
124 except KeyError: | |
125 return self.__missing__(key) | |
126 | |
127 def __setitem__(self, key, value): | |
128 maxsize = self.__maxsize | |
129 size = self.getsizeof(value) | |
130 if size > maxsize: | |
131 raise ValueError('value too large') | |
132 if key not in self.__data or self.__size[key] < size: | |
133 while self.__currsize + size > maxsize: | |
134 self.popitem() | |
135 if key in self.__data: | |
136 diffsize = size - self.__size[key] | |
137 else: | |
138 diffsize = size | |
139 self.__data[key] = value | |
140 self.__size[key] = size | |
141 self.__currsize += diffsize | |
142 | |
143 def __delitem__(self, key): | |
144 size = self.__size.pop(key) | |
145 del self.__data[key] | |
146 self.__currsize -= size | |
147 | |
148 def __contains__(self, key): | |
149 return key in self.__data | |
150 | |
151 def __missing__(self, key): | |
152 value = self.__missing(key) | |
153 try: | |
154 self.__setitem__(key, value) | |
155 except ValueError: | |
156 pass # value too large | |
157 return value | |
158 | |
159 def __iter__(self): | |
160 return iter(self.__data) | |
161 | |
162 def __len__(self): | |
163 return len(self.__data) | |
164 | |
165 @staticmethod | |
166 def __getsizeof(value): | |
167 return 1 | |
168 | |
169 @staticmethod | |
170 def __missing(key): | |
171 raise KeyError(key) | |
172 | |
173 @property | |
174 def maxsize(self): | |
175 """The maximum size of the cache.""" | |
176 return self.__maxsize | |
177 | |
178 @property | |
179 def currsize(self): | |
180 """The current size of the cache.""" | |
181 return self.__currsize | |
182 | |
183 def getsizeof(self, value): | |
184 """Return the size of a cache element's value.""" | |
185 return self.__getsizeof(value) | |
186 | |
187 | |
188 class _Link(object): | |
189 | |
190 __slots__ = ('key', 'expire', 'next', 'prev') | |
191 | |
192 def __init__(self, key=None, expire=None): | |
193 self.key = key | |
194 self.expire = expire | |
195 | |
196 def __reduce__(self): | |
197 return _Link, (self.key, self.expire) | |
198 | |
199 def unlink(self): | |
200 next = self.next | |
201 prev = self.prev | |
202 prev.next = next | |
203 next.prev = prev | |
204 | |
205 | |
206 class _Timer(object): | |
207 | |
208 def __init__(self, timer): | |
209 self.__timer = timer | |
210 self.__nesting = 0 | |
211 | |
212 def __call__(self): | |
213 if self.__nesting == 0: | |
214 return self.__timer() | |
215 else: | |
216 return self.__time | |
217 | |
218 def __enter__(self): | |
219 if self.__nesting == 0: | |
220 self.__time = time = self.__timer() | |
221 else: | |
222 time = self.__time | |
223 self.__nesting += 1 | |
224 return time | |
225 | |
226 def __exit__(self, *exc): | |
227 self.__nesting -= 1 | |
228 | |
229 def __reduce__(self): | |
230 return _Timer, (self.__timer,) | |
231 | |
232 def __getattr__(self, name): | |
233 return getattr(self.__timer, name) | |
234 | |
235 | |
236 class TTLCache(Cache): | |
237 """LRU Cache implementation with per-item time-to-live (TTL) value.""" | |
238 | |
239 def __init__(self, maxsize, ttl, timer=time.time, missing=None, | |
240 getsizeof=None): | |
241 Cache.__init__(self, maxsize, missing, getsizeof) | |
242 self.__root = root = _Link() | |
243 root.prev = root.next = root | |
244 self.__links = collections.OrderedDict() | |
245 self.__timer = _Timer(timer) | |
246 self.__ttl = ttl | |
247 | |
248 def __contains__(self, key): | |
249 try: | |
250 link = self.__links[key] # no reordering | |
251 except KeyError: | |
252 return False | |
253 else: | |
254 return not (link.expire < self.__timer()) | |
255 | |
256 def __getitem__(self, key, cache_getitem=Cache.__getitem__): | |
257 try: | |
258 link = self.__getlink(key) | |
259 except KeyError: | |
260 expired = False | |
261 else: | |
262 expired = link.expire < self.__timer() | |
263 if expired: | |
264 return self.__missing__(key) | |
265 else: | |
266 return cache_getitem(self, key) | |
267 | |
268 def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): | |
269 with self.__timer as time: | |
270 self.expire(time) | |
271 cache_setitem(self, key, value) | |
272 try: | |
273 link = self.__getlink(key) | |
274 except KeyError: | |
275 self.__links[key] = link = _Link(key) | |
276 else: | |
277 link.unlink() | |
278 link.expire = time + self.__ttl | |
279 link.next = root = self.__root | |
280 link.prev = prev = root.prev | |
281 prev.next = root.prev = link | |
282 | |
283 def __delitem__(self, key, cache_delitem=Cache.__delitem__): | |
284 cache_delitem(self, key) | |
285 link = self.__links.pop(key) | |
286 link.unlink() | |
287 if link.expire < self.__timer(): | |
288 raise KeyError(key) | |
289 | |
290 def __iter__(self): | |
291 root = self.__root | |
292 curr = root.next | |
293 while curr is not root: | |
294 # "freeze" time for iterator access | |
295 with self.__timer as time: | |
296 if not (curr.expire < time): | |
297 yield curr.key | |
298 curr = curr.next | |
299 | |
300 def __len__(self): | |
301 root = self.__root | |
302 curr = root.next | |
303 time = self.__timer() | |
304 count = len(self.__links) | |
305 while curr is not root and curr.expire < time: | |
306 count -= 1 | |
307 curr = curr.next | |
308 return count | |
309 | |
310 def __setstate__(self, state): | |
311 self.__dict__.update(state) | |
312 root = self.__root | |
313 root.prev = root.next = root | |
314 for link in sorted(self.__links.values(), key=lambda obj: obj.expire): | |
315 link.next = root | |
316 link.prev = prev = root.prev | |
317 prev.next = root.prev = link | |
318 self.expire(self.__timer()) | |
319 | |
320 def __repr__(self, cache_repr=Cache.__repr__): | |
321 with self.__timer as time: | |
322 self.expire(time) | |
323 return cache_repr(self) | |
324 | |
325 @property | |
326 def currsize(self): | |
327 with self.__timer as time: | |
328 self.expire(time) | |
329 return super(TTLCache, self).currsize | |
330 | |
331 @property | |
332 def timer(self): | |
333 """The timer function used by the cache.""" | |
334 return self.__timer | |
335 | |
336 @property | |
337 def ttl(self): | |
338 """The time-to-live value of the cache's items.""" | |
339 return self.__ttl | |
340 | |
341 def expire(self, time=None): | |
342 """Remove expired items from the cache.""" | |
343 if time is None: | |
344 time = self.__timer() | |
345 root = self.__root | |
346 curr = root.next | |
347 links = self.__links | |
348 cache_delitem = Cache.__delitem__ | |
349 while curr is not root and curr.expire < time: | |
350 cache_delitem(self, curr.key) | |
351 del links[curr.key] | |
352 next = curr.next | |
353 curr.unlink() | |
354 curr = next | |
355 | |
356 def clear(self): | |
357 with self.__timer as time: | |
358 self.expire(time) | |
359 Cache.clear(self) | |
360 | |
361 def get(self, *args, **kwargs): | |
362 with self.__timer: | |
363 return Cache.get(self, *args, **kwargs) | |
364 | |
365 def pop(self, *args, **kwargs): | |
366 with self.__timer: | |
367 return Cache.pop(self, *args, **kwargs) | |
368 | |
369 def setdefault(self, *args, **kwargs): | |
370 with self.__timer: | |
371 return Cache.setdefault(self, *args, **kwargs) | |
372 | |
373 def popitem(self): | |
374 """Remove and return the `(key, value)` pair least recently used that | |
375 has not already expired. | |
376 | |
377 """ | |
378 with self.__timer as time: | |
379 self.expire(time) | |
380 try: | |
381 key = next(iter(self.__links)) | |
382 except StopIteration: | |
383 raise KeyError('%s is empty' % self.__class__.__name__) | |
384 else: | |
385 return (key, self.pop(key)) | |
386 | |
387 if hasattr(collections.OrderedDict, 'move_to_end'): | |
388 def __getlink(self, key): | |
389 value = self.__links[key] | |
390 self.__links.move_to_end(key) | |
391 return value | |
392 else: | |
393 def __getlink(self, key): | |
394 value = self.__links.pop(key) | |
395 self.__links[key] = value | |
396 return value | |
397 | |
398 | |
399 ############################################# | |
400 # END IMPORT OF CACHING LIBRARY # | |
401 ############################################# | |
402 | |
403 | |
404 cache = TTLCache( | |
405 100, # Up to 100 items | |
406 5 * 60 # 5 minute cache life | |
407 ) | |
408 userCache = TTLCache( | |
409 2, # Up to 2 items | |
410 60 # 1 minute cache life | |
411 ) | |
412 | |
413 | |
414 class UnknownUserException(Exception): | |
415 pass | |
416 | |
417 | |
418 def WAAuth(parser): | |
419 parser.add_argument('apollo', help='Complete Apollo URL') | |
420 parser.add_argument('username', help='WA Username') | |
421 parser.add_argument('password', help='WA Password') | |
422 | |
423 | |
424 def OrgOrGuess(parser): | |
425 parser.add_argument('--org_json', type=argparse.FileType("r"), help='Apollo JSON output, source for common name') | |
426 parser.add_argument('--org_raw', help='Common Name') | |
427 parser.add_argument('--org_id', help='Organism ID') | |
428 | |
429 | |
430 def CnOrGuess(parser): | |
431 OrgOrGuess(parser) | |
432 parser.add_argument('--seq_fasta', type=argparse.FileType("r"), help='Fasta file, IDs used as sequence sources') | |
433 parser.add_argument('--seq_raw', nargs='*', help='Sequence Names') | |
434 | |
435 | |
436 def GuessOrg(args, wa): | |
437 if args.org_json: | |
438 orgs = [x.get('commonName', None) | |
439 for x in json.load(args.org_json)] | |
440 orgs = [x for x in orgs if x is not None] | |
441 return orgs | |
442 elif args.org_raw: | |
443 org = args.org_raw.strip() | |
444 if len(org) > 0: | |
445 return [org] | |
446 else: | |
447 raise Exception("Organism Common Name not provided") | |
448 elif args.org_id: | |
449 return [wa.organisms.findOrganismById(args.org_id).get('commonName', None)] | |
450 else: | |
451 raise Exception("Organism Common Name not provided") | |
452 | |
453 | |
454 def GuessCn(args, wa): | |
455 org = GuessOrg(args, wa) | |
456 seqs = [] | |
457 if args.seq_fasta: | |
458 # If we have a fasta, pull all rec ids from that. | |
459 for rec in SeqIO.parse(args.seq_fasta, 'fasta'): | |
460 seqs.append(rec.id) | |
461 elif args.seq_raw: | |
462 # Otherwise raw list. | |
463 seqs = [x.strip() for x in args.seq_raw if len(x.strip()) > 0] | |
464 | |
465 return org, seqs | |
466 | |
467 | |
468 def AssertUser(user_list): | |
469 if len(user_list) == 0: | |
470 raise UnknownUserException() | |
471 elif len(user_list) == 1: | |
472 return user_list[0] | |
473 else: | |
474 raise Exception("Too many users!") | |
475 | |
476 | |
477 def AssertAdmin(user): | |
478 if user.role == 'ADMIN': | |
479 return True | |
480 else: | |
481 raise Exception("User is not an administrator. Permission denied") | |
482 | |
483 | |
484 class WebApolloInstance(object): | |
485 | |
486 def __init__(self, url, username, password): | |
487 self.apollo_url = url | |
488 self.username = username | |
489 self.password = password | |
490 | |
491 self.annotations = AnnotationsClient(self) | |
492 self.groups = GroupsClient(self) | |
493 self.io = IOClient(self) | |
494 self.organisms = OrganismsClient(self) | |
495 self.users = UsersClient(self) | |
496 self.metrics = MetricsClient(self) | |
497 self.bio = RemoteRecord(self) | |
498 self.status = StatusClient(self) | |
499 self.canned_comments = CannedCommentsClient(self) | |
500 self.canned_keys = CannedKeysClient(self) | |
501 self.canned_values = CannedValuesClient(self) | |
502 | |
503 def __str__(self): | |
504 return '<WebApolloInstance at %s>' % self.apollo_url | |
505 | |
506 def requireUser(self, email): | |
507 cacheKey = 'user-list' | |
508 try: | |
509 # Get the cached value | |
510 data = userCache[cacheKey] | |
511 except KeyError: | |
512 # If we hit a key error above, indicating that | |
513 # we couldn't find the key, we'll simply re-request | |
514 # the data | |
515 data = self.users.loadUsers() | |
516 userCache[cacheKey] = data | |
517 | |
518 return AssertUser([x for x in data if x.username == email]) | |
519 | |
520 | |
521 class GroupObj(object): | |
522 def __init__(self, **kwargs): | |
523 self.name = kwargs['name'] | |
524 | |
525 if 'id' in kwargs: | |
526 self.groupId = kwargs['id'] | |
527 | |
528 | |
529 class UserObj(object): | |
530 ROLE_USER = 'USER' | |
531 ROLE_ADMIN = 'ADMIN' | |
532 | |
533 def __init__(self, **kwargs): | |
534 # Generally expect 'userId', 'firstName', 'lastName', 'username' (email) | |
535 for attr in kwargs.keys(): | |
536 setattr(self, attr, kwargs[attr]) | |
537 | |
538 if 'groups' in kwargs: | |
539 groups = [] | |
540 for groupData in kwargs['groups']: | |
541 groups.append(GroupObj(**groupData)) | |
542 self.groups = groups | |
543 | |
544 self.__props = kwargs.keys() | |
545 | |
546 def isAdmin(self): | |
547 if hasattr(self, 'role'): | |
548 return self.role == self.ROLE_ADMIN | |
549 return False | |
550 | |
551 def refresh(self, wa): | |
552 # This method requires some sleeping usually. | |
553 newU = wa.users.loadUser(self).toDict() | |
554 for prop in newU: | |
555 setattr(self, prop, newU[prop]) | |
556 | |
557 def toDict(self): | |
558 data = {} | |
559 for prop in self.__props: | |
560 data[prop] = getattr(self, prop) | |
561 return data | |
562 | |
563 def orgPerms(self): | |
564 for orgPer in self.organismPermissions: | |
565 if len(orgPer['permissions']) > 2: | |
566 orgPer['permissions'] = json.loads(orgPer['permissions']) | |
567 yield orgPer | |
568 | |
569 def __str__(self): | |
570 return '<User %s: %s %s <%s>>' % (self.userId, self.firstName, | |
571 self.lastName, self.username) | |
572 | |
573 | |
574 class Client(object): | |
575 | |
576 def __init__(self, webapolloinstance, **requestArgs): | |
577 self._wa = webapolloinstance | |
578 | |
579 self.__verify = requestArgs.get('verify', True) | |
580 self._requestArgs = requestArgs | |
581 | |
582 if 'verify' in self._requestArgs: | |
583 del self._requestArgs['verify'] | |
584 | |
585 def request(self, clientMethod, data, post_params={}, isJson=True): | |
586 url = self._wa.apollo_url + self.CLIENT_BASE + clientMethod | |
587 | |
588 headers = { | |
589 'Content-Type': 'application/json' | |
590 } | |
591 | |
592 data.update({ | |
593 'username': self._wa.username, | |
594 'password': self._wa.password, | |
595 }) | |
596 | |
597 r = requests.post(url, data=json.dumps(data), headers=headers, | |
598 verify=self.__verify, params=post_params, allow_redirects=False, **self._requestArgs) | |
599 | |
600 if r.status_code == 200 or r.status_code == 302: | |
601 if isJson: | |
602 d = r.json() | |
603 if 'username' in d: | |
604 del d['username'] | |
605 if 'password' in d: | |
606 del d['password'] | |
607 return d | |
608 else: | |
609 return r.text | |
610 | |
611 # @see self.body for HTTP response body | |
612 raise Exception("Unexpected response from apollo %s: %s" % | |
613 (r.status_code, r.text)) | |
614 | |
615 def get(self, clientMethod, get_params): | |
616 url = self._wa.apollo_url + self.CLIENT_BASE + clientMethod | |
617 headers = {} | |
618 | |
619 r = requests.get(url, headers=headers, verify=self.__verify, | |
620 params=get_params, **self._requestArgs) | |
621 if r.status_code == 200: | |
622 d = r.json() | |
623 if 'username' in d: | |
624 del d['username'] | |
625 if 'password' in d: | |
626 del d['password'] | |
627 return d | |
628 # @see self.body for HTTP response body | |
629 raise Exception("Unexpected response from apollo %s: %s" % | |
630 (r.status_code, r.text)) | |
631 | |
632 | |
633 class MetricsClient(Client): | |
634 CLIENT_BASE = '/metrics/' | |
635 | |
636 def getServerMetrics(self): | |
637 return self.get('metrics', {}) | |
638 | |
639 | |
640 class AnnotationsClient(Client): | |
641 CLIENT_BASE = '/annotationEditor/' | |
642 | |
643 def _update_data(self, data): | |
644 if not hasattr(self, '_extra_data'): | |
645 raise Exception("Please call setSequence first") | |
646 | |
647 data.update(self._extra_data) | |
648 return data | |
649 | |
650 def setSequence(self, sequence, organism): | |
651 self._extra_data = { | |
652 'sequence': sequence, | |
653 'organism': organism, | |
654 } | |
655 | |
656 def setDescription(self, featureDescriptions): | |
657 data = { | |
658 'features': featureDescriptions, | |
659 } | |
660 data = self._update_data(data) | |
661 return self.request('setDescription', data) | |
662 | |
663 def setName(self, uniquename, name): | |
664 # TODO | |
665 data = { | |
666 'features': [ | |
667 { | |
668 'uniquename': uniquename, | |
669 'name': name, | |
670 } | |
671 ], | |
672 } | |
673 data = self._update_data(data) | |
674 return self.request('setName', data) | |
675 | |
676 def setNames(self, features): | |
677 # TODO | |
678 data = { | |
679 'features': features, | |
680 } | |
681 data = self._update_data(data) | |
682 return self.request('setName', data) | |
683 | |
684 def setStatus(self, statuses): | |
685 # TODO | |
686 data = { | |
687 'features': statuses, | |
688 } | |
689 data = self._update_data(data) | |
690 return self.request('setStatus', data) | |
691 | |
692 def setSymbol(self, symbols): | |
693 data = { | |
694 'features': symbols, | |
695 } | |
696 data.update(self._extra_data) | |
697 return self.request('setSymbol', data) | |
698 | |
699 def getComments(self, feature_id): | |
700 data = { | |
701 'features': [{'uniquename': feature_id}], | |
702 } | |
703 data = self._update_data(data) | |
704 return self.request('getComments', data) | |
705 | |
706 def addComments(self, feature_id, comments): | |
707 # TODO: This is probably not great and will delete comments, if I had to guess... | |
708 data = { | |
709 'features': [ | |
710 { | |
711 'uniquename': feature_id, | |
712 'comments': comments | |
713 } | |
714 ], | |
715 } | |
716 data = self._update_data(data) | |
717 return self.request('addComments', data) | |
718 | |
719 def addAttributes(self, feature_id, attributes): | |
720 nrps = [] | |
721 for (key, values) in attributes.items(): | |
722 for value in values: | |
723 nrps.append({ | |
724 'tag': key, | |
725 'value': value | |
726 }) | |
727 | |
728 data = { | |
729 'features': [ | |
730 { | |
731 'uniquename': feature_id, | |
732 'non_reserved_properties': nrps | |
733 } | |
734 ] | |
735 } | |
736 data = self._update_data(data) | |
737 return self.request('addAttribute', data) | |
738 | |
739 def deleteAttribute(self, feature_id, key, value): | |
740 data = { | |
741 'features': [ | |
742 { | |
743 'uniquename': feature_id, | |
744 'non_reserved_properties': [ | |
745 {'tag': key, 'value': value} | |
746 ] | |
747 } | |
748 ] | |
749 } | |
750 data = self._update_data(data) | |
751 return self.request('addAttribute', data) | |
752 | |
753 def getFeatures(self): | |
754 data = self._update_data({}) | |
755 return self.request('getFeatures', data) | |
756 | |
757 def getSequence(self, uniquename): | |
758 data = { | |
759 'features': [ | |
760 {'uniquename': uniquename} | |
761 ] | |
762 } | |
763 data = self._update_data(data) | |
764 return self.request('getSequence', data) | |
765 | |
766 def addFeature(self, feature, trustme=False): | |
767 if not trustme: | |
768 raise NotImplementedError("Waiting on better docs from project. If you know what you are doing, pass trustme=True to this function.") | |
769 | |
770 data = { | |
771 'features': feature, | |
772 } | |
773 data = self._update_data(data) | |
774 return self.request('addFeature', data) | |
775 | |
776 def addTranscript(self, transcript, trustme=False): | |
777 if not trustme: | |
778 raise NotImplementedError("Waiting on better docs from project. If you know what you are doing, pass trustme=True to this function.") | |
779 | |
780 data = {} | |
781 data.update(transcript) | |
782 data = self._update_data(data) | |
783 return self.request('addTranscript', data) | |
784 | |
785 # addExon, add/delete/updateComments, addTranscript skipped due to docs | |
786 | |
787 def duplicateTranscript(self, transcriptId): | |
788 data = { | |
789 'features': [{'uniquename': transcriptId}] | |
790 } | |
791 | |
792 data = self._update_data(data) | |
793 return self.request('duplicateTranscript', data) | |
794 | |
795 def setTranslationStart(self, uniquename, start): | |
796 data = { | |
797 'features': [{ | |
798 'uniquename': uniquename, | |
799 'location': { | |
800 'fmin': start | |
801 } | |
802 }] | |
803 } | |
804 data = self._update_data(data) | |
805 return self.request('setTranslationStart', data) | |
806 | |
807 def setTranslationEnd(self, uniquename, end): | |
808 data = { | |
809 'features': [{ | |
810 'uniquename': uniquename, | |
811 'location': { | |
812 'fmax': end | |
813 } | |
814 }] | |
815 } | |
816 data = self._update_data(data) | |
817 return self.request('setTranslationEnd', data) | |
818 | |
819 def setLongestOrf(self, uniquename): | |
820 data = { | |
821 'features': [{ | |
822 'uniquename': uniquename, | |
823 }] | |
824 } | |
825 data = self._update_data(data) | |
826 return self.request('setLongestOrf', data) | |
827 | |
828 def setBoundaries(self, uniquename, start, end): | |
829 data = { | |
830 'features': [{ | |
831 'uniquename': uniquename, | |
832 'location': { | |
833 'fmin': start, | |
834 'fmax': end, | |
835 } | |
836 }] | |
837 } | |
838 data = self._update_data(data) | |
839 return self.request('setBoundaries', data) | |
840 | |
841 def getSequenceAlterations(self): | |
842 data = { | |
843 } | |
844 data = self._update_data(data) | |
845 return self.request('getSequenceAlterations', data) | |
846 | |
847 def setReadthroughStopCodon(self, uniquename): | |
848 data = { | |
849 'features': [{ | |
850 'uniquename': uniquename, | |
851 }] | |
852 } | |
853 data = self._update_data(data) | |
854 return self.request('setReadthroughStopCodon', data) | |
855 | |
856 def deleteSequenceAlteration(self, uniquename): | |
857 data = { | |
858 'features': [{ | |
859 'uniquename': uniquename, | |
860 }] | |
861 } | |
862 data = self._update_data(data) | |
863 return self.request('deleteSequenceAlteration', data) | |
864 | |
865 def flipStrand(self, uniquenames): | |
866 data = { | |
867 'features': [ | |
868 {'uniquename': x} for x in uniquenames | |
869 ] | |
870 } | |
871 data = self._update_data(data) | |
872 return self.request('flipStrand', data) | |
873 | |
874 def mergeExons(self, exonA, exonB): | |
875 data = { | |
876 'features': [ | |
877 {'uniquename': exonA}, | |
878 {'uniquename': exonB}, | |
879 ] | |
880 } | |
881 data = self._update_data(data) | |
882 return self.request('mergeExons', data) | |
883 | |
884 # def splitExon(): pass | |
885 | |
886 def deleteFeatures(self, uniquenames): | |
887 assert isinstance(uniquenames, collections.Iterable) | |
888 data = { | |
889 'features': [ | |
890 {'uniquename': x} for x in uniquenames | |
891 ] | |
892 } | |
893 data = self._update_data(data) | |
894 return self.request('deleteFeature', data) | |
895 | |
896 # def deleteExon(): pass | |
897 | |
898 # def makeIntron(self, uniquename, ): pass | |
899 | |
900 def getSequenceSearchTools(self): | |
901 return self.get('getSequenceSearchTools', {}) | |
902 | |
903 def getCannedComments(self): | |
904 return self.get('getCannedComments', {}) | |
905 | |
906 def searchSequence(self, searchTool, sequence, database): | |
907 data = { | |
908 'key': searchTool, | |
909 'residues': sequence, | |
910 'database_id': database, | |
911 } | |
912 return self.request('searchSequences', data) | |
913 | |
914 def getGff3(self, uniquenames): | |
915 assert isinstance(uniquenames, collections.Iterable) | |
916 data = { | |
917 'features': [ | |
918 {'uniquename': x} for x in uniquenames | |
919 ] | |
920 } | |
921 data = self._update_data(data) | |
922 return self.request('getGff3', data, isJson=False) | |
923 | |
924 | |
925 class GroupsClient(Client): | |
926 CLIENT_BASE = '/group/' | |
927 | |
928 def createGroup(self, name): | |
929 data = {'name': name} | |
930 return self.request('createGroup', data) | |
931 | |
932 def getOrganismPermissionsForGroup(self, group): | |
933 data = { | |
934 'id': group.groupId, | |
935 'name': group.name, | |
936 } | |
937 return self.request('getOrganismPermissionsForGroup', data) | |
938 | |
939 def loadGroup(self, group): | |
940 return self.loadGroupById(group.groupId) | |
941 | |
942 def loadGroupById(self, groupId): | |
943 res = self.request('loadGroups', {'groupId': groupId}) | |
944 if isinstance(res, list): | |
945 # We can only match one, right? | |
946 return GroupObj(**res[0]) | |
947 else: | |
948 return res | |
949 | |
950 def loadGroupByName(self, name): | |
951 res = self.request('loadGroups', {'name': name}) | |
952 if isinstance(res, list): | |
953 # We can only match one, right? | |
954 return GroupObj(**res[0]) | |
955 else: | |
956 return res | |
957 | |
958 def loadGroups(self, group=None): | |
959 res = self.request('loadGroups', {}) | |
960 data = [GroupObj(**x) for x in res] | |
961 if group is not None: | |
962 data = [x for x in data if x.name == group] | |
963 | |
964 return data | |
965 | |
966 def deleteGroup(self, group): | |
967 data = { | |
968 'id': group.groupId, | |
969 'name': group.name, | |
970 } | |
971 return self.request('deleteGroup', data) | |
972 | |
973 def updateGroup(self, group, newName): | |
974 # TODO: Sure would be nice if modifying ``group.name`` would invoke | |
975 # this? | |
976 data = { | |
977 'id': group.groupId, | |
978 'name': newName, | |
979 } | |
980 return self.request('updateGroup', data) | |
981 | |
982 def updateOrganismPermission(self, group, organismName, | |
983 administrate=False, write=False, read=False, | |
984 export=False): | |
985 data = { | |
986 'groupId': group.groupId, | |
987 'organism': organismName, | |
988 'ADMINISTRATE': administrate, | |
989 'WRITE': write, | |
990 'EXPORT': export, | |
991 'READ': read, | |
992 } | |
993 return self.request('updateOrganismPermission', data) | |
994 | |
995 def updateMembership(self, group, users): | |
996 data = { | |
997 'groupId': group.groupId, | |
998 'user': [user.email for user in users] | |
999 } | |
1000 return self.request('updateMembership', data) | |
1001 | |
1002 | |
1003 class IOClient(Client): | |
1004 CLIENT_BASE = '/IOService/' | |
1005 | |
1006 def write(self, exportType='FASTA', seqType='peptide', | |
1007 exportFormat='text', sequences=None, organism=None, | |
1008 output='text', exportAllSequences=False, | |
1009 exportGff3Fasta=False): | |
1010 if exportType not in ('FASTA', 'GFF3'): | |
1011 raise Exception("exportType must be one of FASTA, GFF3") | |
1012 | |
1013 if seqType not in ('peptide', 'cds', 'cdna', 'genomic'): | |
1014 raise Exception("seqType must be one of peptide, cds, dna, genomic") | |
1015 | |
1016 if exportFormat not in ('gzip', 'text'): | |
1017 raise Exception("exportFormat must be one of gzip, text") | |
1018 | |
1019 if output not in ('file', 'text'): | |
1020 raise Exception("output must be one of file, text") | |
1021 | |
1022 data = { | |
1023 'type': exportType, | |
1024 'seqType': seqType, | |
1025 'format': exportFormat, | |
1026 'sequences': sequences, | |
1027 'organism': organism, | |
1028 'output': output, | |
1029 'exportAllSequences': exportAllSequences, | |
1030 'exportGff3Fasta': exportGff3Fasta, | |
1031 } | |
1032 | |
1033 return self.request('write', data, isJson=output == 'file') | |
1034 | |
1035 def download(self, uuid, outputFormat='gzip'): | |
1036 | |
1037 if outputFormat.lower() not in ('gzip', 'text'): | |
1038 raise Exception("outputFormat must be one of file, text") | |
1039 | |
1040 data = { | |
1041 'format': outputFormat, | |
1042 'uuid': uuid, | |
1043 } | |
1044 return self.request('write', data) | |
1045 | |
1046 | |
1047 class StatusClient(Client): | |
1048 CLIENT_BASE = '/availableStatus/' | |
1049 | |
1050 def addStatus(self, value): | |
1051 data = { | |
1052 'value': value | |
1053 } | |
1054 | |
1055 return self.request('createStatus', data) | |
1056 | |
1057 def findAllStatuses(self): | |
1058 return self.request('showStatus', {}) | |
1059 | |
1060 def findStatusByValue(self, value): | |
1061 statuses = self.findAllStatuses() | |
1062 statuses = [x for x in statuses if x['value'] == value] | |
1063 if len(statuses) == 0: | |
1064 raise Exception("Unknown status value") | |
1065 else: | |
1066 return statuses[0] | |
1067 | |
1068 def findStatusById(self, id_number): | |
1069 statuses = self.findAllStatuses() | |
1070 statuses = [x for x in statuses if str(x['id']) == str(id_number)] | |
1071 if len(statuses) == 0: | |
1072 raise Exception("Unknown ID") | |
1073 else: | |
1074 return statuses[0] | |
1075 | |
1076 def updateStatus(self, id_number, new_value): | |
1077 data = { | |
1078 'id': id_number, | |
1079 'new_value': new_value | |
1080 } | |
1081 | |
1082 return self.request('updateStatus', data) | |
1083 | |
1084 def deleteStatus(self, id_number): | |
1085 data = { | |
1086 'id': id_number | |
1087 } | |
1088 | |
1089 return self.request('deleteStatus', data) | |
1090 | |
1091 | |
1092 class CannedCommentsClient(Client): | |
1093 CLIENT_BASE = '/cannedComment/' | |
1094 | |
1095 def addComment(self, comment, metadata=""): | |
1096 data = { | |
1097 'comment': comment, | |
1098 'metadata': metadata | |
1099 } | |
1100 | |
1101 return self.request('createComment', data) | |
1102 | |
1103 def findAllComments(self): | |
1104 return self.request('showComment', {}) | |
1105 | |
1106 def findCommentByValue(self, value): | |
1107 comments = self.findAllComments() | |
1108 comments = [x for x in comments if x['comment'] == value] | |
1109 if len(comments) == 0: | |
1110 raise Exception("Unknown comment") | |
1111 else: | |
1112 return comments[0] | |
1113 | |
1114 def findCommentById(self, id_number): | |
1115 comments = self.findAllComments() | |
1116 comments = [x for x in comments if str(x['id']) == str(id_number)] | |
1117 if len(comments) == 0: | |
1118 raise Exception("Unknown ID") | |
1119 else: | |
1120 return comments[0] | |
1121 | |
1122 def updateComment(self, id_number, new_value, metadata=None): | |
1123 data = { | |
1124 'id': id_number, | |
1125 'new_comment': new_value | |
1126 } | |
1127 | |
1128 if metadata is not None: | |
1129 data['metadata'] = metadata | |
1130 | |
1131 return self.request('updateComment', data) | |
1132 | |
1133 def deleteComment(self, id_number): | |
1134 data = { | |
1135 'id': id_number | |
1136 } | |
1137 | |
1138 return self.request('deleteComment', data) | |
1139 | |
1140 | |
1141 class CannedKeysClient(Client): | |
1142 CLIENT_BASE = '/cannedKey/' | |
1143 | |
1144 def addKey(self, key, metadata=""): | |
1145 data = { | |
1146 'key': key, | |
1147 'metadata': metadata | |
1148 } | |
1149 | |
1150 return self.request('createKey', data) | |
1151 | |
1152 def findAllKeys(self): | |
1153 return self.request('showKey', {}) | |
1154 | |
1155 def findKeyByValue(self, value): | |
1156 keys = self.findAllKeys() | |
1157 keys = [x for x in keys if x['label'] == value] | |
1158 if len(keys) == 0: | |
1159 raise Exception("Unknown key") | |
1160 else: | |
1161 return keys[0] | |
1162 | |
1163 def findKeyById(self, id_number): | |
1164 keys = self.findAllKeys() | |
1165 keys = [x for x in keys if str(x['id']) == str(id_number)] | |
1166 if len(keys) == 0: | |
1167 raise Exception("Unknown ID") | |
1168 else: | |
1169 return keys[0] | |
1170 | |
1171 def updateKey(self, id_number, new_key, metadata=None): | |
1172 data = { | |
1173 'id': id_number, | |
1174 'new_key': new_key | |
1175 } | |
1176 | |
1177 if metadata is not None: | |
1178 data['metadata'] = metadata | |
1179 | |
1180 return self.request('updateKey', data) | |
1181 | |
1182 def deleteKey(self, id_number): | |
1183 data = { | |
1184 'id': id_number | |
1185 } | |
1186 | |
1187 return self.request('deleteKey', data) | |
1188 | |
1189 | |
1190 class CannedValuesClient(Client): | |
1191 CLIENT_BASE = '/cannedValue/' | |
1192 | |
1193 def addValue(self, value, metadata=""): | |
1194 data = { | |
1195 'value': value, | |
1196 'metadata': metadata | |
1197 } | |
1198 | |
1199 return self.request('createValue', data) | |
1200 | |
1201 def findAllValues(self): | |
1202 return self.request('showValue', {}) | |
1203 | |
1204 def findValueByValue(self, value): | |
1205 values = self.findAllValues() | |
1206 values = [x for x in values if x['label'] == value] | |
1207 if len(values) == 0: | |
1208 raise Exception("Unknown value") | |
1209 else: | |
1210 return values[0] | |
1211 | |
1212 def findValueById(self, id_number): | |
1213 values = self.findAllValues() | |
1214 values = [x for x in values if str(x['id']) == str(id_number)] | |
1215 if len(values) == 0: | |
1216 raise Exception("Unknown ID") | |
1217 else: | |
1218 return values[0] | |
1219 | |
1220 def updateValue(self, id_number, new_value, metadata=None): | |
1221 data = { | |
1222 'id': id_number, | |
1223 'new_value': new_value | |
1224 } | |
1225 | |
1226 if metadata is not None: | |
1227 data['metadata'] = metadata | |
1228 | |
1229 return self.request('updateValue', data) | |
1230 | |
1231 def deleteValue(self, id_number): | |
1232 data = { | |
1233 'id': id_number | |
1234 } | |
1235 | |
1236 return self.request('deleteValue', data) | |
1237 | |
1238 | |
1239 class OrganismsClient(Client): | |
1240 CLIENT_BASE = '/organism/' | |
1241 | |
1242 def addOrganism(self, commonName, directory, blatdb=None, species=None, | |
1243 genus=None, public=False): | |
1244 data = { | |
1245 'commonName': commonName, | |
1246 'directory': directory, | |
1247 'publicMode': public, | |
1248 } | |
1249 | |
1250 if blatdb is not None: | |
1251 data['blatdb'] = blatdb | |
1252 if genus is not None: | |
1253 data['genus'] = genus | |
1254 if species is not None: | |
1255 data['species'] = species | |
1256 | |
1257 return self.request('addOrganism', data) | |
1258 | |
1259 def findAllOrganisms(self): | |
1260 return self.request('findAllOrganisms', {}) | |
1261 | |
1262 def findOrganismByCn(self, cn): | |
1263 orgs = self.findAllOrganisms() | |
1264 orgs = [x for x in orgs if x['commonName'] == cn] | |
1265 if len(orgs) == 0: | |
1266 raise Exception("Unknown common name") | |
1267 else: | |
1268 return orgs[0] | |
1269 | |
1270 def findOrganismById(self, id_number): | |
1271 orgs = self.findAllOrganisms() | |
1272 orgs = [x for x in orgs if str(x['id']) == str(id_number)] | |
1273 if len(orgs) == 0: | |
1274 raise Exception("Unknown ID") | |
1275 else: | |
1276 return orgs[0] | |
1277 | |
1278 def deleteOrganism(self, organismId): | |
1279 return self.request('deleteOrganism', {'id': organismId}) | |
1280 | |
1281 def deleteOrganismFeatures(self, organismId): | |
1282 return self.request('deleteOrganismFeatures', {'id': organismId}) | |
1283 | |
1284 def getSequencesForOrganism(self, commonName): | |
1285 return self.request('getSequencesForOrganism', {'organism': commonName}) | |
1286 | |
1287 def updateOrganismInfo(self, organismId, commonName, directory, blatdb=None, species=None, genus=None, public=False): | |
1288 data = { | |
1289 'id': organismId, | |
1290 'name': commonName, | |
1291 'directory': directory, | |
1292 'publicMode': public, | |
1293 } | |
1294 | |
1295 if blatdb is not None: | |
1296 data['blatdb'] = blatdb | |
1297 if genus is not None: | |
1298 data['genus'] = genus | |
1299 if species is not None: | |
1300 data['species'] = species | |
1301 | |
1302 return self.request('updateOrganismInfo', data) | |
1303 | |
1304 | |
1305 class UsersClient(Client): | |
1306 CLIENT_BASE = '/user/' | |
1307 | |
1308 # Real one | |
1309 # def getOrganismPermissionsForUser(self, user): | |
1310 # data = { | |
1311 # 'userId': user.userId, | |
1312 # } | |
1313 # return self.request('getOrganismPermissionsForUser', data) | |
1314 | |
1315 # Utter frigging hack | |
1316 def getOrganismPermissionsForUser(self, user): | |
1317 return self.loadUser(user).organismPermissions | |
1318 | |
1319 def updateOrganismPermission(self, user, organism, administrate=False, | |
1320 write=False, export=False, read=False): | |
1321 data = { | |
1322 'userId': user.userId, | |
1323 'organism': organism, | |
1324 'ADMINISTRATE': administrate, | |
1325 'WRITE': write, | |
1326 'EXPORT': export, | |
1327 'READ': read, | |
1328 } | |
1329 return self.request('updateOrganismPermission', data) | |
1330 | |
1331 def loadUser(self, user): | |
1332 return self.loadUserById(user.userId) | |
1333 | |
1334 def loadUserById(self, userId): | |
1335 res = self.request('loadUsers', {'userId': userId}) | |
1336 if isinstance(res, list): | |
1337 # We can only match one, right? | |
1338 return UserObj(**res[0]) | |
1339 else: | |
1340 return res | |
1341 | |
1342 def loadUsers(self, email=None): | |
1343 res = self.request('loadUsers', {}) | |
1344 data = [UserObj(**x) for x in res] | |
1345 if email is not None: | |
1346 data = [x for x in data if x.username == email] | |
1347 | |
1348 return data | |
1349 | |
1350 def addUserToGroup(self, group, user): | |
1351 data = {'group': group.name, 'userId': user.userId} | |
1352 return self.request('addUserToGroup', data) | |
1353 | |
1354 def removeUserFromGroup(self, group, user): | |
1355 data = {'group': group.name, 'userId': user.userId} | |
1356 return self.request('removeUserFromGroup', data) | |
1357 | |
1358 def createUser(self, email, firstName, lastName, newPassword, role="user", groups=None): | |
1359 data = { | |
1360 'firstName': firstName, | |
1361 'lastName': lastName, | |
1362 'email': email, | |
1363 'role': role, | |
1364 'groups': [] if groups is None else groups, | |
1365 # 'availableGroups': [], | |
1366 'newPassword': newPassword, | |
1367 # 'organismPermissions': [], | |
1368 } | |
1369 return self.request('createUser', data) | |
1370 | |
1371 def deleteUser(self, user): | |
1372 return self.request('deleteUser', {'userId': user.userId}) | |
1373 | |
1374 def updateUser(self, user, email, firstName, lastName, newPassword): | |
1375 data = { | |
1376 'userId': user.userId, | |
1377 'email': email, | |
1378 'firstName': firstName, | |
1379 'lastName': lastName, | |
1380 'newPassword': newPassword, | |
1381 } | |
1382 return self.request('updateUser', data) | |
1383 | |
1384 | |
1385 class RemoteRecord(Client): | |
1386 CLIENT_BASE = None | |
1387 | |
1388 def ParseRecord(self, cn): | |
1389 org = self._wa.organisms.findOrganismByCn(cn) | |
1390 self._wa.annotations.setSequence(org['commonName'], org['id']) | |
1391 | |
1392 data = io.StringIO(self._wa.io.write( | |
1393 exportType='GFF3', | |
1394 seqType='genomic', | |
1395 exportAllSequences=False, | |
1396 exportGff3Fasta=True, | |
1397 output="text", | |
1398 exportFormat="text", | |
1399 sequences=cn, | |
1400 )) | |
1401 data.seek(0) | |
1402 | |
1403 for record in GFF.parse(data): | |
1404 yield WebApolloSeqRecord(record, self._wa) | |
1405 | |
1406 | |
1407 class WebApolloSeqRecord(object): | |
1408 def __init__(self, sr, wa): | |
1409 self._sr = sr | |
1410 self._wa = wa | |
1411 | |
1412 def __dir__(self): | |
1413 return dir(self._sr) | |
1414 | |
1415 def __getattr__(self, key): | |
1416 if key in ('_sr', '_wa'): | |
1417 return self.__dict__[key] | |
1418 else: | |
1419 if key == 'features': | |
1420 return (WebApolloSeqFeature(x, self._wa) | |
1421 for x in self._sr.__dict__[key]) | |
1422 else: | |
1423 return self._sr.__dict__[key] | |
1424 | |
1425 def __setattr__(self, key, value): | |
1426 if key in ('_sd', '_wa'): | |
1427 self.__dict__[key] = value | |
1428 else: | |
1429 self._sr.__dict__[key] = value | |
1430 # Methods acting on the SeqRecord object | |
1431 | |
1432 | |
1433 class WebApolloSeqFeature(object): | |
1434 def __init__(self, sf, wa): | |
1435 self._sf = sf | |
1436 self._wa = wa | |
1437 | |
1438 def __dir__(self): | |
1439 return dir(self._sf) | |
1440 | |
1441 def __getattr__(self, key): | |
1442 if key in ('_sf', '_wa'): | |
1443 return self.__dict__[key] | |
1444 else: | |
1445 return self._sf.__dict__[key] | |
1446 | |
1447 def __setattr__(self, key, value): | |
1448 if key in ('_sf', '_wa'): | |
1449 self.__dict__[key] = value | |
1450 else: | |
1451 # Methods acting on the SeqFeature object | |
1452 if key == 'location': | |
1453 if value.strand != self._sf.location.strand: | |
1454 self.wa.annotations.flipStrand( | |
1455 self._sf.qualifiers['ID'][0] | |
1456 ) | |
1457 | |
1458 self.wa.annotations.setBoundaries( | |
1459 self._sf.qualifiers['ID'][0], | |
1460 value.start, | |
1461 value.end, | |
1462 ) | |
1463 | |
1464 self._sf.__dict__[key] = value | |
1465 else: | |
1466 self._sf.__dict__[key] = value | |
1467 | |
1468 | |
1469 def _tnType(feature): | |
1470 if feature.type in ('gene', 'mRNA', 'exon', 'CDS', 'terminator', 'tRNA'): | |
1471 return feature.type | |
1472 else: | |
1473 return 'exon' | |
1474 | |
1475 | |
1476 def _yieldFeatData(features): | |
1477 for f in features: | |
1478 current = { | |
1479 'location': { | |
1480 'strand': f.strand, | |
1481 'fmin': int(f.location.start), | |
1482 'fmax': int(f.location.end), | |
1483 }, | |
1484 'type': { | |
1485 'name': _tnType(f), | |
1486 'cv': { | |
1487 'name': 'sequence', | |
1488 } | |
1489 }, | |
1490 } | |
1491 if f.type in ('gene', 'mRNA'): | |
1492 current['name'] = f.qualifiers.get('Name', [f.id])[0] | |
1493 if hasattr(f, 'sub_features') and len(f.sub_features) > 0: | |
1494 current['children'] = [x for x in _yieldFeatData(f.sub_features)] | |
1495 | |
1496 yield current | |
1497 | |
1498 | |
1499 def featuresToFeatureSchema(features): | |
1500 compiled = [] | |
1501 for feature in features: | |
1502 # if feature.type != 'gene': | |
1503 # log.warn("Not able to handle %s features just yet...", feature.type) | |
1504 # continue | |
1505 | |
1506 for x in _yieldFeatData([feature]): | |
1507 compiled.append(x) | |
1508 return compiled | |
1509 | |
1510 | |
1511 def accessible_organisms(user, orgs): | |
1512 permissionMap = { | |
1513 x['organism']: x['permissions'] | |
1514 for x in user.organismPermissions | |
1515 if 'WRITE' in x['permissions'] or | |
1516 'READ' in x['permissions'] or | |
1517 'ADMINISTRATE' in x['permissions'] or | |
1518 user.role == 'ADMIN' | |
1519 } | |
1520 | |
1521 if 'error' in orgs: | |
1522 raise Exception("Error received from Apollo server: \"%s\"" % orgs['error']) | |
1523 | |
1524 return [ | |
1525 (org['commonName'], org['id'], False) | |
1526 for org in sorted(orgs, key=lambda x: x['commonName']) | |
1527 if org['commonName'] in permissionMap | |
1528 ] | |
1529 | |
1530 | |
1531 def galaxy_list_groups(trans, *args, **kwargs): | |
1532 email = trans.get_user().email | |
1533 wa = WebApolloInstance( | |
1534 os.environ['GALAXY_WEBAPOLLO_URL'], | |
1535 os.environ['GALAXY_WEBAPOLLO_USER'], | |
1536 os.environ['GALAXY_WEBAPOLLO_PASSWORD'] | |
1537 ) | |
1538 # Assert that the email exists in apollo | |
1539 try: | |
1540 gx_user = wa.requireUser(email) | |
1541 except UnknownUserException: | |
1542 return [] | |
1543 | |
1544 # Key for cached data | |
1545 cacheKey = 'groups-' + email | |
1546 # We don't want to trust "if key in cache" because between asking and fetch | |
1547 # it might through key error. | |
1548 if cacheKey not in cache: | |
1549 # However if it ISN'T there, we know we're safe to fetch + put in | |
1550 # there. | |
1551 data = _galaxy_list_groups(wa, gx_user, *args, **kwargs) | |
1552 cache[cacheKey] = data | |
1553 return data | |
1554 try: | |
1555 # The cache key may or may not be in the cache at this point, it | |
1556 # /likely/ is. However we take no chances that it wasn't evicted between | |
1557 # when we checked above and now, so we reference the object from the | |
1558 # cache in preparation to return. | |
1559 data = cache[cacheKey] | |
1560 return data | |
1561 except KeyError: | |
1562 # If access fails due to eviction, we will fail over and can ensure that | |
1563 # data is inserted. | |
1564 data = _galaxy_list_groups(wa, gx_user, *args, **kwargs) | |
1565 cache[cacheKey] = data | |
1566 return data | |
1567 | |
1568 | |
1569 def _galaxy_list_groups(wa, gx_user, *args, **kwargs): | |
1570 # Fetch the groups. | |
1571 group_data = [] | |
1572 for group in wa.groups.loadGroups(): | |
1573 # Reformat | |
1574 group_data.append((group.name, group.groupId, False)) | |
1575 return group_data | |
1576 | |
1577 | |
1578 def galaxy_list_orgs(trans, *args, **kwargs): | |
1579 email = trans.get_user().email | |
1580 wa = WebApolloInstance( | |
1581 os.environ['GALAXY_WEBAPOLLO_URL'], | |
1582 os.environ['GALAXY_WEBAPOLLO_USER'], | |
1583 os.environ['GALAXY_WEBAPOLLO_PASSWORD'] | |
1584 ) | |
1585 try: | |
1586 gx_user = wa.requireUser(email) | |
1587 except UnknownUserException: | |
1588 return [] | |
1589 | |
1590 # Key for cached data | |
1591 cacheKey = 'orgs-' + email | |
1592 if cacheKey not in cache: | |
1593 data = _galaxy_list_orgs(wa, gx_user, *args, **kwargs) | |
1594 cache[cacheKey] = data | |
1595 return data | |
1596 try: | |
1597 data = cache[cacheKey] | |
1598 return data | |
1599 except KeyError: | |
1600 data = _galaxy_list_orgs(wa, gx_user, *args, **kwargs) | |
1601 cache[cacheKey] = data | |
1602 return data | |
1603 | |
1604 | |
1605 def _galaxy_list_orgs(wa, gx_user, *args, **kwargs): | |
1606 # Fetch all organisms | |
1607 all_orgs = wa.organisms.findAllOrganisms() | |
1608 # Figure out which are accessible to the user | |
1609 orgs = accessible_organisms(gx_user, all_orgs) | |
1610 # Return org list | |
1611 return orgs | |
1612 | |
1613 | |
1614 def galaxy_list_users(trans, *args, **kwargs): | |
1615 email = trans.get_user().email | |
1616 wa = WebApolloInstance( | |
1617 os.environ['GALAXY_WEBAPOLLO_URL'], | |
1618 os.environ['GALAXY_WEBAPOLLO_USER'], | |
1619 os.environ['GALAXY_WEBAPOLLO_PASSWORD'] | |
1620 ) | |
1621 # Assert that the email exists in apollo | |
1622 try: | |
1623 gx_user = wa.requireUser(email) | |
1624 except UnknownUserException: | |
1625 return [] | |
1626 | |
1627 # Key for cached data | |
1628 cacheKey = 'users-' + email | |
1629 # We don't want to trust "if key in cache" because between asking and fetch | |
1630 # it might through key error. | |
1631 if cacheKey not in cache: | |
1632 # However if it ISN'T there, we know we're safe to fetch + put in | |
1633 # there. | |
1634 data = _galaxy_list_users(wa, gx_user, *args, **kwargs) | |
1635 cache[cacheKey] = data | |
1636 return data | |
1637 try: | |
1638 # The cache key may or may not be in the cache at this point, it | |
1639 # /likely/ is. However we take no chances that it wasn't evicted between | |
1640 # when we checked above and now, so we reference the object from the | |
1641 # cache in preparation to return. | |
1642 data = cache[cacheKey] | |
1643 return data | |
1644 except KeyError: | |
1645 # If access fails due to eviction, we will fail over and can ensure that | |
1646 # data is inserted. | |
1647 data = _galaxy_list_users(wa, gx_user, *args, **kwargs) | |
1648 cache[cacheKey] = data | |
1649 return data | |
1650 | |
1651 | |
1652 def _galaxy_list_users(wa, gx_user, *args, **kwargs): | |
1653 # Fetch the users. | |
1654 user_data = [] | |
1655 for user in wa.users.loadUsers(): | |
1656 # Reformat | |
1657 user_data.append((user.username, user.username, False)) | |
1658 return user_data | |
1659 | |
1660 | |
1661 # This is all for implementing the command line interface for testing. | |
1662 class obj(object): | |
1663 pass | |
1664 | |
1665 | |
1666 class fakeTrans(object): | |
1667 | |
1668 def __init__(self, username): | |
1669 self.un = username | |
1670 | |
1671 def get_user(self): | |
1672 o = obj() | |
1673 o.email = self.un | |
1674 return o | |
1675 | |
1676 | |
1677 def retry(closure, sleep=1, limit=5): | |
1678 """ | |
1679 Apollo has the bad habit of returning 500 errors if you call APIs | |
1680 too quickly, largely because of the unholy things that happen in | |
1681 grails. | |
1682 | |
1683 To deal with the fact that we cannot send an addComments call too | |
1684 quickly after a createFeature call, we have this function that will | |
1685 keep calling a closure until it works. | |
1686 """ | |
1687 count = 0 | |
1688 while True: | |
1689 count += 1 | |
1690 | |
1691 if count >= limit: | |
1692 return False | |
1693 try: | |
1694 # Try calling it | |
1695 closure() | |
1696 # If successful, exit | |
1697 return True | |
1698 except Exception as e: | |
1699 log.info(str(e)[0:100]) | |
1700 time.sleep(sleep) | |
1701 | |
1702 | |
1703 if __name__ == '__main__': | |
1704 parser = argparse.ArgumentParser(description='Test access to apollo server') | |
1705 parser.add_argument('email', help='Email of user to test') | |
1706 parser.add_argument('--action', choices=['org', 'group', 'users'], default='org', help='Data set to test, fetch a list of groups or users known to the requesting user.') | |
1707 args = parser.parse_args() | |
1708 | |
1709 trans = fakeTrans(args.email) | |
1710 if args.action == 'org': | |
1711 for f in galaxy_list_orgs(trans): | |
1712 print(f) | |
1713 elif args.action == 'group': | |
1714 for f in galaxy_list_groups(trans): | |
1715 print(f) | |
1716 else: | |
1717 for f in galaxy_list_users(trans): | |
1718 print(f) |