Mercurial > repos > eric-rasche > apollo
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) |
