comparison webapollo.py @ 10:c3b5bc8b4080 draft

"planemo upload for repository https://github.com/galaxy-genome-annotation/galaxy-tools/tree/master/tools/apollo commit 08015be1ee8a784e0619f961aaa724857debfd6f"
author gga
date Mon, 02 Dec 2019 05:43:20 -0500
parents 0e47d146590b
children f6e09a37bc8b
comparison
equal deleted inserted replaced
9:f45ad96c9e08 10:c3b5bc8b4080
3 import argparse 3 import argparse
4 import collections 4 import collections
5 import json 5 import json
6 import logging 6 import logging
7 import os 7 import os
8 import random
9 import time 8 import time
10 from abc import abstractmethod 9 from abc import abstractmethod
11 10
12 from BCBio import GFF
13
14 from Bio import SeqIO
15
16 import requests 11 import requests
17 12
18 from six.moves.builtins import next 13 from six.moves.builtins import next
19 from six.moves.builtins import object 14 from six.moves.builtins import object
20 from six.moves.builtins import str 15
21 16 import yaml
22 17
23 try:
24 import StringIO as io
25 except BaseException:
26 import io
27 logging.getLogger("requests").setLevel(logging.CRITICAL) 18 logging.getLogger("requests").setLevel(logging.CRITICAL)
28 log = logging.getLogger() 19 log = logging.getLogger()
29 20
30 21
31 ############################################# 22 #############################################
429 OrgOrGuess(parser) 420 OrgOrGuess(parser)
430 parser.add_argument('--seq_fasta', type=argparse.FileType("r"), help='Fasta file, IDs used as sequence sources') 421 parser.add_argument('--seq_fasta', type=argparse.FileType("r"), help='Fasta file, IDs used as sequence sources')
431 parser.add_argument('--seq_raw', nargs='*', help='Sequence Names') 422 parser.add_argument('--seq_raw', nargs='*', help='Sequence Names')
432 423
433 424
434 def GuessOrg(args, wa):
435 if args.org_json:
436 orgs = [x.get('commonName', None)
437 for x in json.load(args.org_json)]
438 orgs = [x for x in orgs if x is not None]
439 return orgs
440 elif args.org_raw:
441 org = args.org_raw.strip()
442 if len(org) > 0:
443 return [org]
444 else:
445 raise Exception("Organism Common Name not provided")
446 elif args.org_id:
447 return [wa.organisms.findOrganismById(args.org_id).get('commonName', None)]
448 else:
449 raise Exception("Organism Common Name not provided")
450
451
452 def GuessCn(args, wa):
453 org = GuessOrg(args, wa)
454 seqs = []
455 if args.seq_fasta:
456 # If we have a fasta, pull all rec ids from that.
457 for rec in SeqIO.parse(args.seq_fasta, 'fasta'):
458 seqs.append(rec.id)
459 elif args.seq_raw:
460 # Otherwise raw list.
461 seqs = [x.strip() for x in args.seq_raw if len(x.strip()) > 0]
462
463 return org, seqs
464
465
466 def AssertUser(user_list): 425 def AssertUser(user_list):
467 if len(user_list) == 0: 426 if len(user_list) == 0:
468 raise UnknownUserException() 427 raise UnknownUserException()
469 elif len(user_list) == 1: 428 elif len(user_list) == 1:
470 return user_list[0] 429 return user_list[0]
471 else: 430 else:
472 raise Exception("Too many users!") 431 raise Exception("Too many users!")
473 432
474 433
475 def AssertAdmin(user):
476 if user.role == 'ADMIN':
477 return True
478 else:
479 raise Exception("User is not an administrator. Permission denied")
480
481
482 def PermissionCheck(user, org_cn, permission_type):
483 return any(org["organism"] == org_cn and permission_type in org["permissions"] for org in user.organismPermissions)
484
485
486 def PasswordGenerator(length):
487 chars = list('qwrtpsdfghjklzxcvbnm')
488 return ''.join(random.choice(chars) for _ in range(length))
489
490
491 def IsRemoteUser():
492 if 'GALAXY_WEBAPOLLO_REMOTE_USER' not in os.environ:
493 return False
494 value = os.environ['GALAXY_WEBAPOLLO_REMOTE_USER']
495 if value.lower() in ('true', 't', '1'):
496 return True
497 else:
498 return False
499
500
501 class WebApolloInstance(object): 434 class WebApolloInstance(object):
502 435
503 def __init__(self, url, username, password): 436 def __init__(self):
504 self.apollo_url = url 437
505 self.username = username 438 if 'ARROW_GLOBAL_CONFIG_PATH' in os.environ:
506 self.password = password 439
507 440 with open(os.environ['ARROW_GLOBAL_CONFIG_PATH'], 'r') as config:
508 self.annotations = AnnotationsClient(self) 441 conf = yaml.safe_load(config)
442 try:
443 instance_name = conf['__default']
444 except KeyError:
445 raise Exception("Unknown Apollo instance and no __default provided")
446 self.apollo_url = conf[instance_name]['url']
447 self.username = conf[instance_name]['username']
448 self.password = conf[instance_name]['password']
449 else:
450 self.apollo_url = os.environ['GALAXY_WEBAPOLLO_URL']
451 self.username = os.environ['GALAXY_WEBAPOLLO_USER']
452 self.password = os.environ['GALAXY_WEBAPOLLO_PASSWORD']
453
509 self.groups = GroupsClient(self) 454 self.groups = GroupsClient(self)
510 self.io = IOClient(self)
511 self.organisms = OrganismsClient(self) 455 self.organisms = OrganismsClient(self)
512 self.users = UsersClient(self) 456 self.users = UsersClient(self)
513 self.metrics = MetricsClient(self)
514 self.bio = RemoteRecord(self)
515 self.status = StatusClient(self)
516 self.canned_comments = CannedCommentsClient(self)
517 self.canned_keys = CannedKeysClient(self)
518 self.canned_values = CannedValuesClient(self)
519 457
520 def __str__(self): 458 def __str__(self):
521 return '<WebApolloInstance at %s>' % self.apollo_url 459 return '<WebApolloInstance at %s>' % self.apollo_url
522 460
523 def requireUser(self, email): 461 def requireUser(self, email):
557 for groupData in kwargs['groups']: 495 for groupData in kwargs['groups']:
558 groups.append(GroupObj(**groupData)) 496 groups.append(GroupObj(**groupData))
559 self.groups = groups 497 self.groups = groups
560 498
561 self.__props = kwargs.keys() 499 self.__props = kwargs.keys()
562
563 def isAdmin(self):
564 if hasattr(self, 'role'):
565 return self.role == self.ROLE_ADMIN
566 return False
567
568 def refresh(self, wa):
569 # This method requires some sleeping usually.
570 newU = wa.users.loadUser(self).toDict()
571 for prop in newU:
572 setattr(self, prop, newU[prop])
573
574 def toDict(self):
575 data = {}
576 for prop in self.__props:
577 data[prop] = getattr(self, prop)
578 return data
579
580 def orgPerms(self):
581 for orgPer in self.organismPermissions:
582 if len(orgPer['permissions']) > 2:
583 orgPer['permissions'] = json.loads(orgPer['permissions'])
584 yield orgPer
585 500
586 def __str__(self): 501 def __str__(self):
587 return '<User %s: %s %s <%s>>' % (self.userId, self.firstName, 502 return '<User %s: %s %s <%s>>' % (self.userId, self.firstName,
588 self.lastName, self.username) 503 self.lastName, self.username)
589 504
645 # @see self.body for HTTP response body 560 # @see self.body for HTTP response body
646 raise Exception("Unexpected response from apollo %s: %s" % 561 raise Exception("Unexpected response from apollo %s: %s" %
647 (r.status_code, r.text)) 562 (r.status_code, r.text))
648 563
649 564
650 class MetricsClient(Client):
651 CLIENT_BASE = '/metrics/'
652
653 def getServerMetrics(self):
654 return self.get('metrics', {})
655
656
657 class AnnotationsClient(Client):
658 CLIENT_BASE = '/annotationEditor/'
659
660 def _update_data(self, data):
661 if not hasattr(self, '_extra_data'):
662 raise Exception("Please call setSequence first")
663
664 data.update(self._extra_data)
665 return data
666
667 def setSequence(self, sequence, organism):
668 self._extra_data = {
669 'sequence': sequence,
670 'organism': organism,
671 }
672
673 def setDescription(self, featureDescriptions):
674 data = {
675 'features': featureDescriptions,
676 }
677 data = self._update_data(data)
678 return self.request('setDescription', data)
679
680 def setName(self, uniquename, name):
681 # TODO
682 data = {
683 'features': [
684 {
685 'uniquename': uniquename,
686 'name': name,
687 }
688 ],
689 }
690 data = self._update_data(data)
691 return self.request('setName', data)
692
693 def setNames(self, features):
694 # TODO
695 data = {
696 'features': features,
697 }
698 data = self._update_data(data)
699 return self.request('setName', data)
700
701 def setStatus(self, statuses):
702 # TODO
703 data = {
704 'features': statuses,
705 }
706 data = self._update_data(data)
707 return self.request('setStatus', data)
708
709 def setSymbol(self, symbols):
710 data = {
711 'features': symbols,
712 }
713 data.update(self._extra_data)
714 return self.request('setSymbol', data)
715
716 def getComments(self, feature_id):
717 data = {
718 'features': [{'uniquename': feature_id}],
719 }
720 data = self._update_data(data)
721 return self.request('getComments', data)
722
723 def addComments(self, feature_id, comments):
724 # TODO: This is probably not great and will delete comments, if I had to guess...
725 data = {
726 'features': [
727 {
728 'uniquename': feature_id,
729 'comments': comments
730 }
731 ],
732 }
733 data = self._update_data(data)
734 return self.request('addComments', data)
735
736 def addAttributes(self, feature_id, attributes):
737 nrps = []
738 for (key, values) in attributes.items():
739 for value in values:
740 nrps.append({
741 'tag': key,
742 'value': value
743 })
744
745 data = {
746 'features': [
747 {
748 'uniquename': feature_id,
749 'non_reserved_properties': nrps
750 }
751 ]
752 }
753 data = self._update_data(data)
754 return self.request('addAttribute', data)
755
756 def deleteAttribute(self, feature_id, key, value):
757 data = {
758 'features': [
759 {
760 'uniquename': feature_id,
761 'non_reserved_properties': [
762 {'tag': key, 'value': value}
763 ]
764 }
765 ]
766 }
767 data = self._update_data(data)
768 return self.request('addAttribute', data)
769
770 def getFeatures(self):
771 data = self._update_data({})
772 return self.request('getFeatures', data)
773
774 def getSequence(self, uniquename):
775 data = {
776 'features': [
777 {'uniquename': uniquename}
778 ]
779 }
780 data = self._update_data(data)
781 return self.request('getSequence', data)
782
783 def addFeature(self, feature, trustme=False):
784 if not trustme:
785 raise NotImplementedError("Waiting on better docs from project. If you know what you are doing, pass trustme=True to this function.")
786
787 data = {
788 'features': feature,
789 }
790 data = self._update_data(data)
791 return self.request('addFeature', data)
792
793 def addTranscript(self, transcript, trustme=False):
794 if not trustme:
795 raise NotImplementedError("Waiting on better docs from project. If you know what you are doing, pass trustme=True to this function.")
796
797 data = {}
798 data.update(transcript)
799 data = self._update_data(data)
800 return self.request('addTranscript', data)
801
802 # addExon, add/delete/updateComments, addTranscript skipped due to docs
803
804 def duplicateTranscript(self, transcriptId):
805 data = {
806 'features': [{'uniquename': transcriptId}]
807 }
808
809 data = self._update_data(data)
810 return self.request('duplicateTranscript', data)
811
812 def setTranslationStart(self, uniquename, start):
813 data = {
814 'features': [{
815 'uniquename': uniquename,
816 'location': {
817 'fmin': start
818 }
819 }]
820 }
821 data = self._update_data(data)
822 return self.request('setTranslationStart', data)
823
824 def setTranslationEnd(self, uniquename, end):
825 data = {
826 'features': [{
827 'uniquename': uniquename,
828 'location': {
829 'fmax': end
830 }
831 }]
832 }
833 data = self._update_data(data)
834 return self.request('setTranslationEnd', data)
835
836 def setLongestOrf(self, uniquename):
837 data = {
838 'features': [{
839 'uniquename': uniquename,
840 }]
841 }
842 data = self._update_data(data)
843 return self.request('setLongestOrf', data)
844
845 def setBoundaries(self, uniquename, start, end):
846 data = {
847 'features': [{
848 'uniquename': uniquename,
849 'location': {
850 'fmin': start,
851 'fmax': end,
852 }
853 }]
854 }
855 data = self._update_data(data)
856 return self.request('setBoundaries', data)
857
858 def getSequenceAlterations(self):
859 data = {
860 }
861 data = self._update_data(data)
862 return self.request('getSequenceAlterations', data)
863
864 def setReadthroughStopCodon(self, uniquename):
865 data = {
866 'features': [{
867 'uniquename': uniquename,
868 }]
869 }
870 data = self._update_data(data)
871 return self.request('setReadthroughStopCodon', data)
872
873 def deleteSequenceAlteration(self, uniquename):
874 data = {
875 'features': [{
876 'uniquename': uniquename,
877 }]
878 }
879 data = self._update_data(data)
880 return self.request('deleteSequenceAlteration', data)
881
882 def flipStrand(self, uniquenames):
883 data = {
884 'features': [
885 {'uniquename': x} for x in uniquenames
886 ]
887 }
888 data = self._update_data(data)
889 return self.request('flipStrand', data)
890
891 def mergeExons(self, exonA, exonB):
892 data = {
893 'features': [
894 {'uniquename': exonA},
895 {'uniquename': exonB},
896 ]
897 }
898 data = self._update_data(data)
899 return self.request('mergeExons', data)
900
901 # def splitExon(): pass
902
903 def deleteFeatures(self, uniquenames):
904 assert isinstance(uniquenames, collections.Iterable)
905 data = {
906 'features': [
907 {'uniquename': x} for x in uniquenames
908 ]
909 }
910 data = self._update_data(data)
911 return self.request('deleteFeature', data)
912
913 # def deleteExon(): pass
914
915 # def makeIntron(self, uniquename, ): pass
916
917 def getSequenceSearchTools(self):
918 return self.get('getSequenceSearchTools', {})
919
920 def getCannedComments(self):
921 return self.get('getCannedComments', {})
922
923 def searchSequence(self, searchTool, sequence, database):
924 data = {
925 'key': searchTool,
926 'residues': sequence,
927 'database_id': database,
928 }
929 return self.request('searchSequences', data)
930
931 def getGff3(self, uniquenames):
932 assert isinstance(uniquenames, collections.Iterable)
933 data = {
934 'features': [
935 {'uniquename': x} for x in uniquenames
936 ]
937 }
938 data = self._update_data(data)
939 return self.request('getGff3', data, isJson=False)
940
941
942 class GroupsClient(Client): 565 class GroupsClient(Client):
943 CLIENT_BASE = '/group/' 566 CLIENT_BASE = '/group/'
944
945 def createGroup(self, name):
946 data = {'name': name}
947 return self.request('createGroup', data)
948
949 def getOrganismPermissionsForGroup(self, group):
950 data = {
951 'id': group.groupId,
952 'name': group.name,
953 }
954 return self.request('getOrganismPermissionsForGroup', data)
955
956 def loadGroup(self, group):
957 return self.loadGroupById(group.groupId)
958
959 def loadGroupById(self, groupId):
960 res = self.request('loadGroups', {'groupId': groupId})
961 if isinstance(res, list):
962 # We can only match one, right?
963 return GroupObj(**res[0])
964 else:
965 return res
966
967 def loadGroupByName(self, name):
968 res = self.request('loadGroups', {'name': name})
969 if isinstance(res, list):
970 # We can only match one, right?
971 return GroupObj(**res[0])
972 else:
973 return res
974 567
975 def loadGroups(self, group=None): 568 def loadGroups(self, group=None):
976 res = self.request('loadGroups', {}) 569 res = self.request('loadGroups', {})
977 data = [GroupObj(**x) for x in res] 570 data = [GroupObj(**x) for x in res]
978 if group is not None: 571 if group is not None:
979 data = [x for x in data if x.name == group] 572 data = [x for x in data if x.name == group]
980 573
981 return data 574 return data
982 575
983 def deleteGroup(self, group):
984 data = {
985 'id': group.groupId,
986 'name': group.name,
987 }
988 return self.request('deleteGroup', data)
989
990 def updateGroup(self, group, newName):
991 # TODO: Sure would be nice if modifying ``group.name`` would invoke
992 # this?
993 data = {
994 'id': group.groupId,
995 'name': newName,
996 }
997 return self.request('updateGroup', data)
998
999 def updateOrganismPermission(self, group, organismName,
1000 administrate=False, write=False, read=False,
1001 export=False):
1002 data = {
1003 'groupId': group.groupId,
1004 'organism': organismName,
1005 'ADMINISTRATE': administrate,
1006 'WRITE': write,
1007 'EXPORT': export,
1008 'READ': read,
1009 }
1010 return self.request('updateOrganismPermission', data)
1011
1012 def updateMembership(self, group, users):
1013 data = {
1014 'groupId': group.groupId,
1015 'user': [user.email for user in users]
1016 }
1017 return self.request('updateMembership', data)
1018
1019
1020 class IOClient(Client):
1021 CLIENT_BASE = '/IOService/'
1022
1023 def write(self, exportType='FASTA', seqType='peptide',
1024 exportFormat='text', sequences=None, organism=None,
1025 output='text', exportAllSequences=False,
1026 exportGff3Fasta=False):
1027 if exportType not in ('FASTA', 'GFF3'):
1028 raise Exception("exportType must be one of FASTA, GFF3")
1029
1030 if seqType not in ('peptide', 'cds', 'cdna', 'genomic'):
1031 raise Exception("seqType must be one of peptide, cds, dna, genomic")
1032
1033 if exportFormat not in ('gzip', 'text'):
1034 raise Exception("exportFormat must be one of gzip, text")
1035
1036 if output not in ('file', 'text'):
1037 raise Exception("output must be one of file, text")
1038
1039 data = {
1040 'type': exportType,
1041 'seqType': seqType,
1042 'format': exportFormat,
1043 'sequences': sequences,
1044 'organism': organism,
1045 'output': output,
1046 'exportAllSequences': exportAllSequences,
1047 'exportGff3Fasta': exportGff3Fasta,
1048 }
1049
1050 return self.request('write', data, isJson=output == 'file')
1051
1052 def download(self, uuid, outputFormat='gzip'):
1053
1054 if outputFormat.lower() not in ('gzip', 'text'):
1055 raise Exception("outputFormat must be one of file, text")
1056
1057 data = {
1058 'format': outputFormat,
1059 'uuid': uuid,
1060 }
1061 return self.request('write', data)
1062
1063
1064 class StatusClient(Client):
1065 CLIENT_BASE = '/availableStatus/'
1066
1067 def addStatus(self, value):
1068 data = {
1069 'value': value
1070 }
1071
1072 return self.request('createStatus', data)
1073
1074 def findAllStatuses(self):
1075 return self.request('showStatus', {})
1076
1077 def findStatusByValue(self, value):
1078 statuses = self.findAllStatuses()
1079 statuses = [x for x in statuses if x['value'] == value]
1080 if len(statuses) == 0:
1081 raise Exception("Unknown status value")
1082 else:
1083 return statuses[0]
1084
1085 def findStatusById(self, id_number):
1086 statuses = self.findAllStatuses()
1087 statuses = [x for x in statuses if str(x['id']) == str(id_number)]
1088 if len(statuses) == 0:
1089 raise Exception("Unknown ID")
1090 else:
1091 return statuses[0]
1092
1093 def updateStatus(self, id_number, new_value):
1094 data = {
1095 'id': id_number,
1096 'new_value': new_value
1097 }
1098
1099 return self.request('updateStatus', data)
1100
1101 def deleteStatus(self, id_number):
1102 data = {
1103 'id': id_number
1104 }
1105
1106 return self.request('deleteStatus', data)
1107
1108
1109 class CannedCommentsClient(Client):
1110 CLIENT_BASE = '/cannedComment/'
1111
1112 def addComment(self, comment, metadata=""):
1113 data = {
1114 'comment': comment,
1115 'metadata': metadata
1116 }
1117
1118 return self.request('createComment', data)
1119
1120 def findAllComments(self):
1121 return self.request('showComment', {})
1122
1123 def findCommentByValue(self, value):
1124 comments = self.findAllComments()
1125 comments = [x for x in comments if x['comment'] == value]
1126 if len(comments) == 0:
1127 raise Exception("Unknown comment")
1128 else:
1129 return comments[0]
1130
1131 def findCommentById(self, id_number):
1132 comments = self.findAllComments()
1133 comments = [x for x in comments if str(x['id']) == str(id_number)]
1134 if len(comments) == 0:
1135 raise Exception("Unknown ID")
1136 else:
1137 return comments[0]
1138
1139 def updateComment(self, id_number, new_value, metadata=None):
1140 data = {
1141 'id': id_number,
1142 'new_comment': new_value
1143 }
1144
1145 if metadata is not None:
1146 data['metadata'] = metadata
1147
1148 return self.request('updateComment', data)
1149
1150 def deleteComment(self, id_number):
1151 data = {
1152 'id': id_number
1153 }
1154
1155 return self.request('deleteComment', data)
1156
1157
1158 class CannedKeysClient(Client):
1159 CLIENT_BASE = '/cannedKey/'
1160
1161 def addKey(self, key, metadata=""):
1162 data = {
1163 'key': key,
1164 'metadata': metadata
1165 }
1166
1167 return self.request('createKey', data)
1168
1169 def findAllKeys(self):
1170 return self.request('showKey', {})
1171
1172 def findKeyByValue(self, value):
1173 keys = self.findAllKeys()
1174 keys = [x for x in keys if x['label'] == value]
1175 if len(keys) == 0:
1176 raise Exception("Unknown key")
1177 else:
1178 return keys[0]
1179
1180 def findKeyById(self, id_number):
1181 keys = self.findAllKeys()
1182 keys = [x for x in keys if str(x['id']) == str(id_number)]
1183 if len(keys) == 0:
1184 raise Exception("Unknown ID")
1185 else:
1186 return keys[0]
1187
1188 def updateKey(self, id_number, new_key, metadata=None):
1189 data = {
1190 'id': id_number,
1191 'new_key': new_key
1192 }
1193
1194 if metadata is not None:
1195 data['metadata'] = metadata
1196
1197 return self.request('updateKey', data)
1198
1199 def deleteKey(self, id_number):
1200 data = {
1201 'id': id_number
1202 }
1203
1204 return self.request('deleteKey', data)
1205
1206
1207 class CannedValuesClient(Client):
1208 CLIENT_BASE = '/cannedValue/'
1209
1210 def addValue(self, value, metadata=""):
1211 data = {
1212 'value': value,
1213 'metadata': metadata
1214 }
1215
1216 return self.request('createValue', data)
1217
1218 def findAllValues(self):
1219 return self.request('showValue', {})
1220
1221 def findValueByValue(self, value):
1222 values = self.findAllValues()
1223 values = [x for x in values if x['label'] == value]
1224 if len(values) == 0:
1225 raise Exception("Unknown value")
1226 else:
1227 return values[0]
1228
1229 def findValueById(self, id_number):
1230 values = self.findAllValues()
1231 values = [x for x in values if str(x['id']) == str(id_number)]
1232 if len(values) == 0:
1233 raise Exception("Unknown ID")
1234 else:
1235 return values[0]
1236
1237 def updateValue(self, id_number, new_value, metadata=None):
1238 data = {
1239 'id': id_number,
1240 'new_value': new_value
1241 }
1242
1243 if metadata is not None:
1244 data['metadata'] = metadata
1245
1246 return self.request('updateValue', data)
1247
1248 def deleteValue(self, id_number):
1249 data = {
1250 'id': id_number
1251 }
1252
1253 return self.request('deleteValue', data)
1254
1255 576
1256 class OrganismsClient(Client): 577 class OrganismsClient(Client):
1257 CLIENT_BASE = '/organism/' 578 CLIENT_BASE = '/organism/'
1258
1259 def addOrganism(self, commonName, directory, blatdb=None, species=None,
1260 genus=None, public=False):
1261 data = {
1262 'commonName': commonName,
1263 'directory': directory,
1264 'publicMode': public,
1265 }
1266
1267 if blatdb is not None:
1268 data['blatdb'] = blatdb
1269 if genus is not None:
1270 data['genus'] = genus
1271 if species is not None:
1272 data['species'] = species
1273
1274 return self.request('addOrganism', data)
1275 579
1276 def findAllOrganisms(self): 580 def findAllOrganisms(self):
1277 orgs = self.request('findAllOrganisms', {}) 581 orgs = self.request('findAllOrganisms', {})
1278 if not isinstance(orgs, (list,)): 582 if not isinstance(orgs, (list,)):
1279 orgs = [] 583 orgs = []
1280 return orgs 584 return orgs
1281 585
1282 def findOrganismByCn(self, cn):
1283 orgs = self.findAllOrganisms()
1284 orgs = [x for x in orgs if x['commonName'] == cn]
1285 if len(orgs) == 0:
1286 raise Exception("Unknown common name")
1287 else:
1288 return orgs[0]
1289
1290 def findOrganismById(self, id_number):
1291 orgs = self.findAllOrganisms()
1292 orgs = [x for x in orgs if str(x['id']) == str(id_number)]
1293 if len(orgs) == 0:
1294 raise Exception("Unknown ID")
1295 else:
1296 return orgs[0]
1297
1298 def deleteOrganism(self, organismId):
1299 return self.request('deleteOrganism', {'id': organismId})
1300
1301 def deleteOrganismFeatures(self, organismId):
1302 return self.request('deleteOrganismFeatures', {'id': organismId})
1303
1304 def getSequencesForOrganism(self, commonName):
1305 return self.request('getSequencesForOrganism', {'organism': commonName})
1306
1307 def updateOrganismInfo(self, organismId, commonName, directory, blatdb=None, species=None, genus=None, public=False):
1308 data = {
1309 'id': organismId,
1310 'name': commonName,
1311 'directory': directory,
1312 'publicMode': public,
1313 }
1314
1315 if blatdb is not None:
1316 data['blatdb'] = blatdb
1317 if genus is not None:
1318 data['genus'] = genus
1319 if species is not None:
1320 data['species'] = species
1321
1322 return self.request('updateOrganismInfo', data)
1323
1324 586
1325 class UsersClient(Client): 587 class UsersClient(Client):
1326 CLIENT_BASE = '/user/' 588 CLIENT_BASE = '/user/'
1327 589
1328 # Real one 590 def loadUsers(self):
1329 # def getOrganismPermissionsForUser(self, user):
1330 # data = {
1331 # 'userId': user.userId,
1332 # }
1333 # return self.request('getOrganismPermissionsForUser', data)
1334
1335 # Utter frigging hack
1336 def getOrganismPermissionsForUser(self, user):
1337 return self.loadUser(user).organismPermissions
1338
1339 def updateOrganismPermission(self, user, organism, administrate=False,
1340 write=False, export=False, read=False):
1341 data = {
1342 'userId': user.userId,
1343 'organism': organism,
1344 'ADMINISTRATE': administrate,
1345 'WRITE': write,
1346 'EXPORT': export,
1347 'READ': read,
1348 }
1349 return self.request('updateOrganismPermission', data)
1350
1351 def loadUser(self, user):
1352 return self.loadUserById(user.userId)
1353
1354 def loadUserById(self, userId):
1355 res = self.request('loadUsers', {'userId': userId})
1356 if isinstance(res, list):
1357 # We can only match one, right?
1358 return UserObj(**res[0])
1359 else:
1360 return res
1361
1362 def loadUsers(self, email=None):
1363 res = self.request('loadUsers', {}) 591 res = self.request('loadUsers', {})
592
1364 data = [UserObj(**x) for x in res] 593 data = [UserObj(**x) for x in res]
1365 if email is not None:
1366 data = [x for x in data if x.username == email]
1367 594
1368 return data 595 return data
1369 596
1370 def addUserToGroup(self, group, user): 597
1371 data = {'group': group.name, 'userId': user.userId} 598 def handle_credentials(user):
1372 return self.request('addUserToGroup', data) 599 if hasattr(user, 'new_password'):
1373 600 f = open("Apollo_credentials.txt", "w")
1374 def removeUserFromGroup(self, group, user): 601 f.write('Username:\t%s\nPassword:\t%s' % (user.username, user.new_password))
1375 data = {'group': group.name, 'userId': user.userId}
1376 return self.request('removeUserFromGroup', data)
1377
1378 def createUser(self, email, firstName, lastName, newPassword, role="user", groups=None, addToHistory=False):
1379 data = {
1380 'firstName': firstName,
1381 'lastName': lastName,
1382 'email': email,
1383 'role': role,
1384 'groups': [] if groups is None else groups,
1385 # 'availableGroups': [],
1386 'newPassword': newPassword,
1387 # 'organismPermissions': [],
1388 }
1389 returnData = self.request('createUser', data)
1390 if addToHistory and not IsRemoteUser():
1391 f = open("Apollo_credentials.txt", "w")
1392 f.write('Username: %s\tPassword: %s' % (email, newPassword))
1393 return returnData
1394
1395 def assertOrCreateUser(self, email):
1396 try:
1397 gx_user = AssertUser(self.loadUsers(email))
1398 except Exception:
1399 self.createUser(email, email, email, PasswordGenerator(12), role='user', addToHistory=True)
1400 gx_user = AssertUser(self.loadUsers(email))
1401 return gx_user
1402
1403 def deleteUser(self, user):
1404 return self.request('deleteUser', {'userId': user.userId})
1405
1406 def updateUser(self, user, email, firstName, lastName, newPassword):
1407 data = {
1408 'userId': user.userId,
1409 'email': email,
1410 'firstName': firstName,
1411 'lastName': lastName,
1412 'newPassword': newPassword,
1413 }
1414 return self.request('updateUser', data)
1415
1416
1417 class RemoteRecord(Client):
1418 CLIENT_BASE = None
1419
1420 def ParseRecord(self, cn):
1421 org = self._wa.organisms.findOrganismByCn(cn)
1422 self._wa.annotations.setSequence(org['commonName'], org['id'])
1423
1424 data = io.StringIO(self._wa.io.write(
1425 exportType='GFF3',
1426 seqType='genomic',
1427 exportAllSequences=False,
1428 exportGff3Fasta=True,
1429 output="text",
1430 exportFormat="text",
1431 sequences=cn,
1432 ))
1433 data.seek(0)
1434
1435 for record in GFF.parse(data):
1436 yield WebApolloSeqRecord(record, self._wa)
1437
1438
1439 class WebApolloSeqRecord(object):
1440 def __init__(self, sr, wa):
1441 self._sr = sr
1442 self._wa = wa
1443
1444 def __dir__(self):
1445 return dir(self._sr)
1446
1447 def __getattr__(self, key):
1448 if key in ('_sr', '_wa'):
1449 return self.__dict__[key]
1450 else:
1451 if key == 'features':
1452 return (WebApolloSeqFeature(x, self._wa)
1453 for x in self._sr.__dict__[key])
1454 else:
1455 return self._sr.__dict__[key]
1456
1457 def __setattr__(self, key, value):
1458 if key in ('_sd', '_wa'):
1459 self.__dict__[key] = value
1460 else:
1461 self._sr.__dict__[key] = value
1462 # Methods acting on the SeqRecord object
1463
1464
1465 class WebApolloSeqFeature(object):
1466 def __init__(self, sf, wa):
1467 self._sf = sf
1468 self._wa = wa
1469
1470 def __dir__(self):
1471 return dir(self._sf)
1472
1473 def __getattr__(self, key):
1474 if key in ('_sf', '_wa'):
1475 return self.__dict__[key]
1476 else:
1477 return self._sf.__dict__[key]
1478
1479 def __setattr__(self, key, value):
1480 if key in ('_sf', '_wa'):
1481 self.__dict__[key] = value
1482 else:
1483 # Methods acting on the SeqFeature object
1484 if key == 'location':
1485 if value.strand != self._sf.location.strand:
1486 self.wa.annotations.flipStrand(
1487 self._sf.qualifiers['ID'][0]
1488 )
1489
1490 self.wa.annotations.setBoundaries(
1491 self._sf.qualifiers['ID'][0],
1492 value.start,
1493 value.end,
1494 )
1495
1496 self._sf.__dict__[key] = value
1497 else:
1498 self._sf.__dict__[key] = value
1499
1500
1501 def _tnType(feature):
1502 if feature.type in ('gene', 'mRNA', 'exon', 'CDS', 'terminator', 'tRNA'):
1503 return feature.type
1504 else:
1505 return 'exon'
1506
1507
1508 def _yieldFeatData(features):
1509 for f in features:
1510 current = {
1511 'location': {
1512 'strand': f.strand,
1513 'fmin': int(f.location.start),
1514 'fmax': int(f.location.end),
1515 },
1516 'type': {
1517 'name': _tnType(f),
1518 'cv': {
1519 'name': 'sequence',
1520 }
1521 },
1522 }
1523 if f.type in ('gene', 'mRNA'):
1524 current['name'] = f.qualifiers.get('Name', [f.id])[0]
1525 if hasattr(f, 'sub_features') and len(f.sub_features) > 0:
1526 current['children'] = [x for x in _yieldFeatData(f.sub_features)]
1527
1528 yield current
1529
1530
1531 def featuresToFeatureSchema(features):
1532 compiled = []
1533 for feature in features:
1534 # if feature.type != 'gene':
1535 # log.warn("Not able to handle %s features just yet...", feature.type)
1536 # continue
1537
1538 for x in _yieldFeatData([feature]):
1539 compiled.append(x)
1540 return compiled
1541 602
1542 603
1543 def accessible_organisms(user, orgs): 604 def accessible_organisms(user, orgs):
1544 permissionMap = { 605 permissionMap = {
1545 x['organism']: x['permissions'] 606 x['organism']: x['permissions']
1557 ] 618 ]
1558 619
1559 620
1560 def galaxy_list_groups(trans, *args, **kwargs): 621 def galaxy_list_groups(trans, *args, **kwargs):
1561 email = trans.get_user().email 622 email = trans.get_user().email
1562 wa = WebApolloInstance( 623 wa = WebApolloInstance()
1563 os.environ['GALAXY_WEBAPOLLO_URL'],
1564 os.environ['GALAXY_WEBAPOLLO_USER'],
1565 os.environ['GALAXY_WEBAPOLLO_PASSWORD']
1566 )
1567 624
1568 # Key for cached data 625 # Key for cached data
1569 cacheKey = 'groups-' + email 626 cacheKey = 'groups-' + email
1570 # We don't want to trust "if key in cache" because between asking and fetch 627 # We don't want to trust "if key in cache" because between asking and fetch
1571 # it might through key error. 628 # it might through key error.
1599 return group_data 656 return group_data
1600 657
1601 658
1602 def galaxy_list_orgs(trans, *args, **kwargs): 659 def galaxy_list_orgs(trans, *args, **kwargs):
1603 email = trans.get_user().email 660 email = trans.get_user().email
1604 wa = WebApolloInstance( 661 wa = WebApolloInstance()
1605 os.environ['GALAXY_WEBAPOLLO_URL'],
1606 os.environ['GALAXY_WEBAPOLLO_USER'],
1607 os.environ['GALAXY_WEBAPOLLO_PASSWORD']
1608 )
1609 try: 662 try:
1610 gx_user = wa.requireUser(email) 663 gx_user = wa.requireUser(email)
1611 except UnknownUserException: 664 except UnknownUserException:
1612 return [] 665 return []
1613 666
1633 orgs = accessible_organisms(gx_user, all_orgs) 686 orgs = accessible_organisms(gx_user, all_orgs)
1634 # Return org list 687 # Return org list
1635 return orgs 688 return orgs
1636 689
1637 690
1638 def galaxy_list_users(trans, *args, **kwargs):
1639 email = trans.get_user().email
1640 wa = WebApolloInstance(
1641 os.environ['GALAXY_WEBAPOLLO_URL'],
1642 os.environ['GALAXY_WEBAPOLLO_USER'],
1643 os.environ['GALAXY_WEBAPOLLO_PASSWORD']
1644 )
1645 # Assert that the email exists in apollo
1646 try:
1647 gx_user = wa.requireUser(email)
1648 except UnknownUserException:
1649 return []
1650
1651 # Key for cached data
1652 cacheKey = 'users-' + email
1653 # We don't want to trust "if key in cache" because between asking and fetch
1654 # it might through key error.
1655 if cacheKey not in cache:
1656 # However if it ISN'T there, we know we're safe to fetch + put in
1657 # there.
1658 data = _galaxy_list_users(wa, gx_user, *args, **kwargs)
1659 cache[cacheKey] = data
1660 return data
1661 try:
1662 # The cache key may or may not be in the cache at this point, it
1663 # /likely/ is. However we take no chances that it wasn't evicted between
1664 # when we checked above and now, so we reference the object from the
1665 # cache in preparation to return.
1666 data = cache[cacheKey]
1667 return data
1668 except KeyError:
1669 # If access fails due to eviction, we will fail over and can ensure that
1670 # data is inserted.
1671 data = _galaxy_list_users(wa, gx_user, *args, **kwargs)
1672 cache[cacheKey] = data
1673 return data
1674
1675
1676 def _galaxy_list_users(wa, gx_user, *args, **kwargs):
1677 # Fetch the users.
1678 user_data = []
1679 for user in wa.users.loadUsers():
1680 # Reformat
1681 user_data.append((user.username, user.username, False))
1682 return user_data
1683
1684
1685 # This is all for implementing the command line interface for testing. 691 # This is all for implementing the command line interface for testing.
1686 class obj(object): 692 class obj(object):
1687 pass 693 pass
1688 694
1689 695
1696 o = obj() 702 o = obj()
1697 o.email = self.un 703 o.email = self.un
1698 return o 704 return o
1699 705
1700 706
1701 def retry(closure, sleep=1, limit=5):
1702 """
1703 Apollo has the bad habit of returning 500 errors if you call APIs
1704 too quickly, largely because of the unholy things that happen in
1705 grails.
1706
1707 To deal with the fact that we cannot send an addComments call too
1708 quickly after a createFeature call, we have this function that will
1709 keep calling a closure until it works.
1710 """
1711 count = 0
1712 while True:
1713 count += 1
1714
1715 if count >= limit:
1716 return False
1717 try:
1718 # Try calling it
1719 closure()
1720 # If successful, exit
1721 return True
1722 except Exception as e:
1723 log.info(str(e)[0:100])
1724 time.sleep(sleep)
1725
1726
1727 if __name__ == '__main__': 707 if __name__ == '__main__':
1728 parser = argparse.ArgumentParser(description='Test access to apollo server') 708 parser = argparse.ArgumentParser(description='Test access to apollo server')
1729 parser.add_argument('email', help='Email of user to test') 709 parser.add_argument('email', help='Email of user to test')
1730 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.') 710 parser.add_argument('--action', choices=['org', 'group'], default='org', help='Data set to test, fetch a list of groups or orgs known to the requesting user.')
1731 args = parser.parse_args() 711 args = parser.parse_args()
1732 712
1733 trans = fakeTrans(args.email) 713 trans = fakeTrans(args.email)
1734 if args.action == 'org': 714 if args.action == 'org':
1735 for f in galaxy_list_orgs(trans): 715 print(galaxy_list_orgs(trans))
1736 print(f)
1737 elif args.action == 'group': 716 elif args.action == 'group':
1738 for f in galaxy_list_groups(trans): 717 print(galaxy_list_groups(trans))
1739 print(f)
1740 else:
1741 for f in galaxy_list_users(trans):
1742 print(f)