comparison webapollo.py @ 7:f9a6e151b3b4 draft

planemo upload for repository https://github.com/TAMU-CPT/galaxy-webapollo commit 52b9e5bf6a6efb09a5cb845ee48703651c644174
author eric-rasche
date Tue, 27 Jun 2017 04:05:17 -0400
parents 7610987e0c48
children
comparison
equal deleted inserted replaced
6:8f76685cdfc8 7:f9a6e151b3b4
1 from __future__ import print_function
2 import argparse
3 import collections
4 import json
5 import logging
6 import os
1 import requests 7 import requests
2 import json
3 import os
4 import collections
5 try:
6 import StringIO as io
7 except:
8 import io
9 import logging
10 import time 8 import time
11 import argparse 9 from future import standard_library
10 from builtins import next
11 from builtins import str
12 from builtins import object
12 from abc import abstractmethod 13 from abc import abstractmethod
13 from BCBio import GFF 14 from BCBio import GFF
14 from Bio import SeqIO 15 from Bio import SeqIO
16 standard_library.install_aliases()
17 try:
18 import StringIO as io
19 except BaseException:
20 import io
15 logging.getLogger("requests").setLevel(logging.CRITICAL) 21 logging.getLogger("requests").setLevel(logging.CRITICAL)
16 log = logging.getLogger() 22 log = logging.getLogger()
17 23
18 24
19 ############################################# 25 #############################################
20 ###### BEGIN IMPORT OF CACHING LIBRARY ###### 26 # BEGIN IMPORT OF CACHING LIBRARY #
21 ############################################# 27 #############################################
22 # This code is licensed under the MIT # 28 # This code is licensed under the MIT #
23 # License and is a copy of code publicly # 29 # License and is a copy of code publicly #
24 # available in rev. # 30 # available in rev. #
25 # e27332bc82f4e327aedaec17c9b656ae719322ed # 31 # e27332bc82f4e327aedaec17c9b656ae719322ed #
26 # of https://github.com/tkem/cachetools/ # 32 # of https://github.com/tkem/cachetools/ #
27 ############################################# 33 #############################################
34
28 class DefaultMapping(collections.MutableMapping): 35 class DefaultMapping(collections.MutableMapping):
29 36
30 __slots__ = () 37 __slots__ = ()
31 38
32 @abstractmethod 39 @abstractmethod
62 if key in self: 69 if key in self:
63 value = self[key] 70 value = self[key]
64 else: 71 else:
65 self[key] = value = default 72 self[key] = value = default
66 return value 73 return value
74
67 75
68 DefaultMapping.register(dict) 76 DefaultMapping.register(dict)
69 77
70 78
71 class _DefaultSize(object): 79 class _DefaultSize(object):
379 self.__links[key] = value 387 self.__links[key] = value
380 return value 388 return value
381 389
382 390
383 ############################################# 391 #############################################
384 ###### END IMPORT OF CACHING LIBRARY ###### 392 # END IMPORT OF CACHING LIBRARY #
385 ############################################# 393 #############################################
386 394
395
387 cache = TTLCache( 396 cache = TTLCache(
388 100, # Up to 100 items 397 100, # Up to 100 items
389 5 * 60 # 5 minute cache life 398 5 * 60 # 5 minute cache life
390 ) 399 )
391 userCache = TTLCache( 400 userCache = TTLCache(
392 2, # Up to 2 items 401 2, # Up to 2 items
393 60 # 1 minute cache life 402 60 # 1 minute cache life
394 ) 403 )
404
395 405
396 class UnknownUserException(Exception): 406 class UnknownUserException(Exception):
397 pass 407 pass
408
398 409
399 def WAAuth(parser): 410 def WAAuth(parser):
400 parser.add_argument('apollo', help='Complete Apollo URL') 411 parser.add_argument('apollo', help='Complete Apollo URL')
401 parser.add_argument('username', help='WA Username') 412 parser.add_argument('username', help='WA Username')
402 parser.add_argument('password', help='WA Password') 413 parser.add_argument('password', help='WA Password')
466 477
467 def __init__(self, url, username, password): 478 def __init__(self, url, username, password):
468 self.apollo_url = url 479 self.apollo_url = url
469 self.username = username 480 self.username = username
470 self.password = password 481 self.password = password
471 # TODO: Remove after apollo 2.0.6.
472 self.clientToken = time.time()
473 482
474 self.annotations = AnnotationsClient(self) 483 self.annotations = AnnotationsClient(self)
475 self.groups = GroupsClient(self) 484 self.groups = GroupsClient(self)
476 self.io = IOClient(self) 485 self.io = IOClient(self)
477 self.organisms = OrganismsClient(self) 486 self.organisms = OrganismsClient(self)
478 self.users = UsersClient(self) 487 self.users = UsersClient(self)
479 self.metrics = MetricsClient(self) 488 self.metrics = MetricsClient(self)
480 self.bio = RemoteRecord(self) 489 self.bio = RemoteRecord(self)
490 self.status = StatusClient(self)
491 self.canned_comments = CannedCommentsClient(self)
492 self.canned_keys = CannedKeysClient(self)
493 self.canned_values = CannedValuesClient(self)
481 494
482 def __str__(self): 495 def __str__(self):
483 return '<WebApolloInstance at %s>' % self.apollo_url 496 return '<WebApolloInstance at %s>' % self.apollo_url
484 497
485 def requireUser(self, email): 498 def requireUser(self, email):
537 data = {} 550 data = {}
538 for prop in self.__props: 551 for prop in self.__props:
539 data[prop] = getattr(self, prop) 552 data[prop] = getattr(self, prop)
540 return data 553 return data
541 554
555 def orgPerms(self):
556 for orgPer in self.organismPermissions:
557 if len(orgPer['permissions']) > 2:
558 orgPer['permissions'] = json.loads(orgPer['permissions'])
559 yield orgPer
560
542 def __str__(self): 561 def __str__(self):
543 return '<User %s: %s %s <%s>>' % (self.userId, self.firstName, 562 return '<User %s: %s %s <%s>>' % (self.userId, self.firstName,
544 self.lastName, self.username) 563 self.lastName, self.username)
545 564
546 565
563 } 582 }
564 583
565 data.update({ 584 data.update({
566 'username': self._wa.username, 585 'username': self._wa.username,
567 'password': self._wa.password, 586 'password': self._wa.password,
568 'clientToken': self._wa.clientToken,
569 }) 587 })
570 588
571 r = requests.post(url, data=json.dumps(data), headers=headers, 589 r = requests.post(url, data=json.dumps(data), headers=headers,
572 verify=self.__verify, params=post_params, allow_redirects=False, **self._requestArgs) 590 verify=self.__verify, params=post_params, allow_redirects=False, **self._requestArgs)
573 591
675 'features': [{'uniquename': feature_id}], 693 'features': [{'uniquename': feature_id}],
676 } 694 }
677 data = self._update_data(data) 695 data = self._update_data(data)
678 return self.request('getComments', data) 696 return self.request('getComments', data)
679 697
680 def addComments(self, feature_id, comment): 698 def addComments(self, feature_id, comments):
681 #TODO: This is probably not great and will delete comments, if I had to guess... 699 # TODO: This is probably not great and will delete comments, if I had to guess...
682 data = { 700 data = {
683 'features': [ 701 'features': [
684 { 702 {
685 'uniquename': feature_id, 703 'uniquename': feature_id,
686 'comments': [comment] 704 'comments': comments
687 } 705 }
688 ], 706 ],
689 } 707 }
690 data = self._update_data(data) 708 data = self._update_data(data)
691 return self.request('getComments', data) 709 return self.request('addComments', data)
692 710
693 def addAttribute(self, features): 711 def addAttributes(self, feature_id, attributes):
694 data = { 712 nrps = []
695 'features': features, 713 for (key, values) in attributes.items():
714 for value in values:
715 nrps.append({
716 'tag': key,
717 'value': value
718 })
719
720 data = {
721 'features': [
722 {
723 'uniquename': feature_id,
724 'non_reserved_properties': nrps
725 }
726 ]
727 }
728 data = self._update_data(data)
729 return self.request('addAttribute', data)
730
731 def deleteAttribute(self, feature_id, key, value):
732 data = {
733 'features': [
734 {
735 'uniquename': feature_id,
736 'non_reserved_properties': [
737 {'tag': key, 'value': value}
738 ]
739 }
740 ]
696 } 741 }
697 data = self._update_data(data) 742 data = self._update_data(data)
698 return self.request('addAttribute', data) 743 return self.request('addAttribute', data)
699 744
700 def getFeatures(self): 745 def getFeatures(self):
987 data = { 1032 data = {
988 'format': outputFormat, 1033 'format': outputFormat,
989 'uuid': uuid, 1034 'uuid': uuid,
990 } 1035 }
991 return self.request('write', data) 1036 return self.request('write', data)
1037
1038
1039 class StatusClient(Client):
1040 CLIENT_BASE = '/availableStatus/'
1041
1042 def addStatus(self, value):
1043 data = {
1044 'value': value
1045 }
1046
1047 return self.request('createStatus', data)
1048
1049 def findAllStatuses(self):
1050 return self.request('showStatus', {})
1051
1052 def findStatusByValue(self, value):
1053 statuses = self.findAllStatuses()
1054 statuses = [x for x in statuses if x['value'] == value]
1055 if len(statuses) == 0:
1056 raise Exception("Unknown status value")
1057 else:
1058 return statuses[0]
1059
1060 def findStatusById(self, id_number):
1061 statuses = self.findAllStatuses()
1062 statuses = [x for x in statuses if str(x['id']) == str(id_number)]
1063 if len(statuses) == 0:
1064 raise Exception("Unknown ID")
1065 else:
1066 return statuses[0]
1067
1068 def updateStatus(self, id_number, new_value):
1069 data = {
1070 'id': id_number,
1071 'new_value': new_value
1072 }
1073
1074 return self.request('updateStatus', data)
1075
1076 def deleteStatus(self, id_number):
1077 data = {
1078 'id': id_number
1079 }
1080
1081 return self.request('deleteStatus', data)
1082
1083
1084 class CannedCommentsClient(Client):
1085 CLIENT_BASE = '/cannedComment/'
1086
1087 def addComment(self, comment, metadata=""):
1088 data = {
1089 'comment': comment,
1090 'metadata': metadata
1091 }
1092
1093 return self.request('createComment', data)
1094
1095 def findAllComments(self):
1096 return self.request('showComment', {})
1097
1098 def findCommentByValue(self, value):
1099 comments = self.findAllComments()
1100 comments = [x for x in comments if x['comment'] == value]
1101 if len(comments) == 0:
1102 raise Exception("Unknown comment")
1103 else:
1104 return comments[0]
1105
1106 def findCommentById(self, id_number):
1107 comments = self.findAllComments()
1108 comments = [x for x in comments if str(x['id']) == str(id_number)]
1109 if len(comments) == 0:
1110 raise Exception("Unknown ID")
1111 else:
1112 return comments[0]
1113
1114 def updateComment(self, id_number, new_value, metadata=None):
1115 data = {
1116 'id': id_number,
1117 'new_comment': new_value
1118 }
1119
1120 if metadata is not None:
1121 data['metadata'] = metadata
1122
1123 return self.request('updateComment', data)
1124
1125 def deleteComment(self, id_number):
1126 data = {
1127 'id': id_number
1128 }
1129
1130 return self.request('deleteComment', data)
1131
1132
1133 class CannedKeysClient(Client):
1134 CLIENT_BASE = '/cannedKey/'
1135
1136 def addKey(self, key, metadata=""):
1137 data = {
1138 'key': key,
1139 'metadata': metadata
1140 }
1141
1142 return self.request('createKey', data)
1143
1144 def findAllKeys(self):
1145 return self.request('showKey', {})
1146
1147 def findKeyByValue(self, value):
1148 keys = self.findAllKeys()
1149 keys = [x for x in keys if x['label'] == value]
1150 if len(keys) == 0:
1151 raise Exception("Unknown key")
1152 else:
1153 return keys[0]
1154
1155 def findKeyById(self, id_number):
1156 keys = self.findAllKeys()
1157 keys = [x for x in keys if str(x['id']) == str(id_number)]
1158 if len(keys) == 0:
1159 raise Exception("Unknown ID")
1160 else:
1161 return keys[0]
1162
1163 def updateKey(self, id_number, new_key, metadata=None):
1164 data = {
1165 'id': id_number,
1166 'new_key': new_key
1167 }
1168
1169 if metadata is not None:
1170 data['metadata'] = metadata
1171
1172 return self.request('updateKey', data)
1173
1174 def deleteKey(self, id_number):
1175 data = {
1176 'id': id_number
1177 }
1178
1179 return self.request('deleteKey', data)
1180
1181
1182 class CannedValuesClient(Client):
1183 CLIENT_BASE = '/cannedValue/'
1184
1185 def addValue(self, value, metadata=""):
1186 data = {
1187 'value': value,
1188 'metadata': metadata
1189 }
1190
1191 return self.request('createValue', data)
1192
1193 def findAllValues(self):
1194 return self.request('showValue', {})
1195
1196 def findValueByValue(self, value):
1197 values = self.findAllValues()
1198 values = [x for x in values if x['label'] == value]
1199 if len(values) == 0:
1200 raise Exception("Unknown value")
1201 else:
1202 return values[0]
1203
1204 def findValueById(self, id_number):
1205 values = self.findAllValues()
1206 values = [x for x in values if str(x['id']) == str(id_number)]
1207 if len(values) == 0:
1208 raise Exception("Unknown ID")
1209 else:
1210 return values[0]
1211
1212 def updateValue(self, id_number, new_value, metadata=None):
1213 data = {
1214 'id': id_number,
1215 'new_value': new_value
1216 }
1217
1218 if metadata is not None:
1219 data['metadata'] = metadata
1220
1221 return self.request('updateValue', data)
1222
1223 def deleteValue(self, id_number):
1224 data = {
1225 'id': id_number
1226 }
1227
1228 return self.request('deleteValue', data)
992 1229
993 1230
994 class OrganismsClient(Client): 1231 class OrganismsClient(Client):
995 CLIENT_BASE = '/organism/' 1232 CLIENT_BASE = '/organism/'
996 1233
1220 else: 1457 else:
1221 self._sf.__dict__[key] = value 1458 self._sf.__dict__[key] = value
1222 1459
1223 1460
1224 def _tnType(feature): 1461 def _tnType(feature):
1225 if feature.type in ('gene', 'mRNA', 'exon', 'CDS'): 1462 if feature.type in ('gene', 'mRNA', 'exon', 'CDS', 'terminator', 'tRNA'):
1226 return feature.type 1463 return feature.type
1227 else: 1464 else:
1228 return 'exon' 1465 return 'exon'
1229 1466
1230 1467
1363 # Figure out which are accessible to the user 1600 # Figure out which are accessible to the user
1364 orgs = accessible_organisms(gx_user, all_orgs) 1601 orgs = accessible_organisms(gx_user, all_orgs)
1365 # Return org list 1602 # Return org list
1366 return orgs 1603 return orgs
1367 1604
1368 ## This is all for implementing the command line interface for testing. 1605
1369 1606 def galaxy_list_users(trans, *args, **kwargs):
1607 email = trans.get_user().email
1608 wa = WebApolloInstance(
1609 os.environ['GALAXY_WEBAPOLLO_URL'],
1610 os.environ['GALAXY_WEBAPOLLO_USER'],
1611 os.environ['GALAXY_WEBAPOLLO_PASSWORD']
1612 )
1613 # Assert that the email exists in apollo
1614 try:
1615 gx_user = wa.requireUser(email)
1616 except UnknownUserException:
1617 return []
1618
1619 # Key for cached data
1620 cacheKey = 'users-' + email
1621 # We don't want to trust "if key in cache" because between asking and fetch
1622 # it might through key error.
1623 if cacheKey not in cache:
1624 # However if it ISN'T there, we know we're safe to fetch + put in
1625 # there.
1626 data = _galaxy_list_users(wa, gx_user, *args, **kwargs)
1627 cache[cacheKey] = data
1628 return data
1629 try:
1630 # The cache key may or may not be in the cache at this point, it
1631 # /likely/ is. However we take no chances that it wasn't evicted between
1632 # when we checked above and now, so we reference the object from the
1633 # cache in preparation to return.
1634 data = cache[cacheKey]
1635 return data
1636 except KeyError:
1637 # If access fails due to eviction, we will fail over and can ensure that
1638 # data is inserted.
1639 data = _galaxy_list_users(wa, gx_user, *args, **kwargs)
1640 cache[cacheKey] = data
1641 return data
1642
1643
1644 def _galaxy_list_users(wa, gx_user, *args, **kwargs):
1645 # Fetch the users.
1646 user_data = []
1647 for user in wa.users.loadUsers():
1648 # Reformat
1649 user_data.append((user.username, user.username, False))
1650 return user_data
1651
1652
1653 # This is all for implementing the command line interface for testing.
1370 class obj(object): 1654 class obj(object):
1371 pass 1655 pass
1372 1656
1373 1657
1374 class fakeTrans(object): 1658 class fakeTrans(object):
1379 def get_user(self): 1663 def get_user(self):
1380 o = obj() 1664 o = obj()
1381 o.email = self.un 1665 o.email = self.un
1382 return o 1666 return o
1383 1667
1668
1669 def retry(closure, sleep=1, limit=5):
1670 """
1671 Apollo has the bad habit of returning 500 errors if you call APIs
1672 too quickly, largely because of the unholy things that happen in
1673 grails.
1674
1675 To deal with the fact that we cannot send an addComments call too
1676 quickly after a createFeature call, we have this function that will
1677 keep calling a closure until it works.
1678 """
1679 count = 0
1680 while True:
1681 count += 1
1682
1683 if count >= limit:
1684 return False
1685 try:
1686 # Try calling it
1687 closure()
1688 # If successful, exit
1689 return True
1690 except Exception as e:
1691 log.info(str(e)[0:100])
1692 time.sleep(sleep)
1693
1694
1384 if __name__ == '__main__': 1695 if __name__ == '__main__':
1385 parser = argparse.ArgumentParser(description='Test access to apollo server') 1696 parser = argparse.ArgumentParser(description='Test access to apollo server')
1386 parser.add_argument('email', help='Email of user to test') 1697 parser.add_argument('email', help='Email of user to test')
1387 parser.add_argument('--action', choices=['org', 'group'], default='org', help='Data set to test, fetch a list of groups or users known to the requesting user.') 1698 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.')
1388 args = parser.parse_args() 1699 args = parser.parse_args()
1389 1700
1390 trans = fakeTrans(args.email) 1701 trans = fakeTrans(args.email)
1391 if args.action == 'org': 1702 if args.action == 'org':
1392 for f in galaxy_list_orgs(trans): 1703 for f in galaxy_list_orgs(trans):
1393 print(f) 1704 print(f)
1394 else: 1705 elif args.action == 'group':
1395 for f in galaxy_list_groups(trans): 1706 for f in galaxy_list_groups(trans):
1396 print(f) 1707 print(f)
1708 else:
1709 for f in galaxy_list_users(trans):
1710 print(f)