Mercurial > repos > eric-rasche > apollo
diff webapollo.py @ 0:6002cc0df04e draft
planemo upload for repository https://github.com/TAMU-CPT/galaxy-webapollo commit 4e5a5af7689f1713c34a6ad9a9594c205e762fdd
author | eric-rasche |
---|---|
date | Tue, 03 May 2016 13:38:55 -0400 |
parents | |
children | d4ae83dedb14 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/webapollo.py Tue May 03 13:38:55 2016 -0400 @@ -0,0 +1,745 @@ +import requests +import json +import collections +from BCBio import GFF +import StringIO +import logging +logging.getLogger("requests").setLevel(logging.CRITICAL) +log = logging.getLogger() + + + +class WebApolloInstance(object): + + def __init__(self, url, username, password): + self.apollo_url = url + self.username = username + self.password = password + + self.annotations = AnnotationsClient(self) + self.groups = GroupsClient(self) + self.io = IOClient(self) + self.organisms = OrganismsClient(self) + self.users = UsersClient(self) + self.metrics = MetricsClient(self) + self.bio = RemoteRecord(self) + + def __str__(self): + return '<WebApolloInstance at %s>' % self.apollo_url + + +class GroupObj(object): + def __init__(self, **kwargs): + self.name = kwargs['name'] + + if 'id' in kwargs: + self.groupId = kwargs['id'] + + +class UserObj(object): + ROLE_USER = 'USER' + ROLE_ADMIN = 'ADMIN' + + def __init__(self, **kwargs): + # Generally expect 'userId', 'firstName', 'lastName', 'username' (email) + for attr in kwargs.keys(): + setattr(self, attr, kwargs[attr]) + + if 'groups' in kwargs: + groups = [] + for groupData in kwargs['groups']: + groups.append(GroupObj(**groupData)) + self.groups = groups + + self.__props = kwargs.keys() + + + def isAdmin(self): + if hasattr(self, 'role'): + return self.role == self.ROLE_ADMIN + return False + + def refresh(self, wa): + # This method requires some sleeping usually. + newU = wa.users.loadUser(self).toDict() + for prop in newU: + setattr(self, prop, newU[prop]) + + def toDict(self): + data = {} + for prop in self.__props: + data[prop] = getattr(self, prop) + return data + + def __str__(self): + return '<User %s: %s %s <%s>>' % (self.userId, self.firstName, + self.lastName, self.username) + + +class Client(object): + + def __init__(self, webapolloinstance, **requestArgs): + self._wa = webapolloinstance + + self.__verify = requestArgs.get('verify', True) + self._requestArgs = requestArgs + + if 'verify' in self._requestArgs: + del self._requestArgs['verify'] + + def request(self, clientMethod, data, post_params={}, isJson=True): + url = self._wa.apollo_url + self.CLIENT_BASE + clientMethod + + headers = { + 'Content-Type': 'application/json' + } + + data.update({ + 'username': self._wa.username, + 'password': self._wa.password, + }) + + r = requests.post(url, data=json.dumps(data), headers=headers, + verify=self.__verify, params=post_params, **self._requestArgs) + + if r.status_code == 200: + if isJson: + d = r.json() + if 'username' in d: + del d['username'] + if 'password' in d: + del d['password'] + return d + else: + return r.text + + # @see self.body for HTTP response body + raise Exception("Unexpected response from apollo %s: %s" % + (r.status_code, r.text)) + + def get(self, clientMethod, get_params): + url = self._wa.apollo_url + self.CLIENT_BASE + clientMethod + headers = {} + + r = requests.get(url, headers=headers, verify=self.__verify, + params=get_params, **self._requestArgs) + if r.status_code == 200: + d = r.json() + if 'username' in d: + del d['username'] + if 'password' in d: + del d['password'] + return d + # @see self.body for HTTP response body + raise Exception("Unexpected response from apollo %s: %s" % + (r.status_code, r.text)) + + +class MetricsClient(Client): + CLIENT_BASE = '/metrics/' + + def getServerMetrics(self): + return self.get('metrics', {}) + + +class AnnotationsClient(Client): + CLIENT_BASE = '/annotationEditor/' + + def _update_data(self, data): + if not hasattr(self, '_extra_data'): raise Exception("Please call setSequence first") + data.update(self._extra_data) + return data + + def setSequence(self, sequence, organism): + self._extra_data = { + 'sequence': sequence, + 'organism': organism, + } + + def setDescription(self, featureDescriptions): + data = { + 'features': featureDescriptions, + } + data = self._update_data(data) + return self.request('setDescription', data) + + def setName(self, uniquename, name): + # TODO + data = { + 'features': [ + { + 'uniquename': uniquename, + 'name': name, + } + ], + } + data = self._update_data(data) + return self.request('setName', data) + + def setNames(self, features): + # TODO + data = { + 'features': features, + } + data = self._update_data(data) + return self.request('setName', data) + + def setStatus(self, statuses): + # TODO + data = { + 'features': statuses, + } + data = self._update_data(data) + return self.request('setStatus', data) + + def setSymbol(self, symbols): + data = { + 'features': symbols, + } + data.update(self._extra_data) + return self.request('setSymbol', data) + + def getComments(self, features): + data = { + 'features': features, + } + data = self._update_data(data) + return self.request('getComments', data) + + def addAttribute(self, features): + data = { + 'features': features, + } + data = self._update_data(data) + return self.request('addAttribute', data) + + def getFeatures(self): + data = self._update_data({}) + return self.request('getFeatures', data) + + def getSequence(self, uniquename): + data = { + 'features': [ + {'uniquename': uniquename} + ] + } + data = self._update_data(data) + return self.request('getSequence', data) + + def addFeature(self, feature, trustme=False): + if not trustme: + raise NotImplementedError("Waiting on better docs from project. If you know what you are doing, pass trustme=True to this function.") + + data = {} + data.update(feature) + data = self._update_data(data) + return self.request('addFeature', data) + + def addTranscript(self, transcript, trustme=False): + if not trustme: + raise NotImplementedError("Waiting on better docs from project. If you know what you are doing, pass trustme=True to this function.") + + data = {} + data.update(transcript) + data = self._update_data(data) + return self.request('addTranscript', data) + + # addExon, add/delete/updateComments, addTranscript skipped due to docs + + def duplicateTranscript(self, transcriptId): + data = { + 'features': [{'uniquename': transcriptId}] + } + + data = self._update_data(data) + return self.request('duplicateTranscript', data) + + def setTranslationStart(self, uniquename, start): + data = { + 'features': [{ + 'uniquename': uniquename, + 'location': { + 'fmin': start + } + }] + } + data = self._update_data(data) + return self.request('setTranslationStart', data) + + def setTranslationEnd(self, uniquename, end): + data = { + 'features': [{ + 'uniquename': uniquename, + 'location': { + 'fmax': end + } + }] + } + data = self._update_data(data) + return self.request('setTranslationEnd', data) + + def setLongestOrf(self, uniquename): + data = { + 'features': [{ + 'uniquename': uniquename, + }] + } + data = self._update_data(data) + return self.request('setLongestOrf', data) + + def setBoundaries(self, uniquename, start, end): + data = { + 'features': [{ + 'uniquename': uniquename, + 'location': { + 'fmin': start, + 'fmax': end, + } + }] + } + data = self._update_data(data) + return self.request('setBoundaries', data) + + def getSequenceAlterations(self): + data = { + } + data = self._update_data(data) + return self.request('getSequenceAlterations', data) + + def setReadthroughStopCodon(self, uniquename): + data = { + 'features': [{ + 'uniquename': uniquename, + }] + } + data = self._update_data(data) + return self.request('setReadthroughStopCodon', data) + + def deleteSequenceAlteration(self, uniquename): + data = { + 'features': [{ + 'uniquename': uniquename, + }] + } + data = self._update_data(data) + return self.request('deleteSequenceAlteration', data) + + def flipStrand(self, uniquenames): + data = { + 'features': [ + {'uniquename': x} for x in uniquenames + ] + } + data = self._update_data(data) + return self.request('flipStrand', data) + + def mergeExons(self, exonA, exonB): + data = { + 'features': [ + {'uniquename': exonA}, + {'uniquename': exonB}, + ] + } + data = self._update_data(data) + return self.request('mergeExons', data) + + # def splitExon(): pass + + def deleteFeatures(self, uniquenames): + assert isinstance(uniquenames, collections.Iterable) + data = { + 'features': [ + {'uniquename': x} for x in uniquenames + ] + } + data = self._update_data(data) + return self.request('deleteFeature', data) + + # def deleteExon(): pass + + # def makeIntron(self, uniquename, ): pass + + def getSequenceSearchTools(self): + return self.get('getSequenceSearchTools', {}) + + def getCannedComments(self): + return self.get('getCannedComments', {}) + + def searchSequence(self, searchTool, sequence, database): + data = { + 'key': searchTool, + 'residues': sequence, + 'database_id': database, + } + return self.request('searchSequences', data) + + def getGff3(self, uniquenames): + assert isinstance(uniquenames, collections.Iterable) + data = { + 'features': [ + {'uniquename': x} for x in uniquenames + ] + } + data = self._update_data(data) + return self.request('getGff3', data, isJson=False) + + +class GroupsClient(Client): + CLIENT_BASE = '/group/' + + def createGroup(self, name): + data = {'name': name} + return self.request('createGroup', data) + + def getOrganismPermissionsForGroup(self, group): + data = { + 'id': group.groupId, + 'name': group.name, + } + return self.request('getOrganismPermissionsForGroup', data) + + def loadGroups(self, group=None): + data ={} + if group is not None: + data['groupId'] = group.groupId + + return self.request('loadGroups', data) + + def deleteGroup(self, group): + data = { + 'id': group.groupId, + 'name': group.name, + } + return self.request('deleteGroup', data) + + def updateGroup(self, group, newName): + # TODO: Sure would be nice if modifying ``group.name`` would invoke + # this? + data = { + 'id': group.groupId, + 'name': newName, + } + return self.request('updateGroup', data) + + def updateOrganismPermission(self, group, organismName, + administrate=False, write=False, read=False, + export=False): + data = { + 'groupId': group.groupId, + 'name': organismName, + 'administrate': administrate, + 'write': write, + 'export': export, + 'read': read, + } + return self.request('updateOrganismPermission', data) + + def updateMembership(self, group, users): + data = { + 'groupId': group.groupId, + 'user': [user.email for user in users] + } + return self.request('updateMembership', data) + + +class IOClient(Client): + CLIENT_BASE = '/IOService/' + + def write(self, exportType='FASTA', seqType='peptide', + exportFormat='text', sequences=None, organism=None, + output='text', exportAllSequences=False, + exportGff3Fasta=False): + if exportType not in ('FASTA', 'GFF3'): + raise Exception("exportType must be one of FASTA, GFF3") + + if seqType not in ('peptide', 'cds', 'cdna', 'genomic'): + raise Exception("seqType must be one of peptide, cds, dna, genomic") + + if exportFormat not in ('gzip', 'text'): + raise Exception("exportFormat must be one of gzip, text") + + if output not in ('file', 'text'): + raise Exception("output must be one of file, text") + + data = { + 'type': exportType, + 'seqType': seqType, + 'format': exportFormat, + 'sequences': sequences, + 'organism': organism, + 'output': output, + 'exportAllSequences': exportAllSequences, + 'exportGff3Fasta': exportGff3Fasta, + } + + return self.request('write', data, isJson=output == 'file') + + def download(self, uuid, outputFormat='gzip'): + + if outputFormat.lower() not in ('gzip', 'text'): + raise Exception("outputFormat must be one of file, text") + + data = { + 'format': outputFormat, + 'uuid': uuid, + } + return self.request('write', data) + + +class OrganismsClient(Client): + CLIENT_BASE = '/organism/' + + def addOrganism(self, commonName, directory, blatdb=None, species=None, + genus=None, public=False): + data = { + 'commonName': commonName, + 'directory': directory, + 'publicMode': public, + } + + if blatdb is not None: + data['blatdb'] = blatdb + if genus is not None: + data['genus'] = genus + if species is not None: + data['species'] = species + + return self.request('addOrganism', data) + + def findAllOrganisms(self): + return self.request('findAllOrganisms', {}) + + def findOrganismByCn(self, cn): + orgs = self.findAllOrganisms() + orgs = [x for x in orgs if x['commonName'] == cn] + if len(orgs) == 0: + raise Exception("Unknown common name") + else: + return orgs[0] + + def deleteOrganism(self, organismId): + return self.request('deleteOrganism', {'id': organismId}) + + def deleteOrganismFeatures(self, organismId): + return self.request('deleteOrganismFeatures', {'id': organismId}) + + def getSequencesForOrganism(self, commonName): + return self.request('getSequencesForOrganism', {'organism': commonName}) + + def updateOrganismInfo(self, organismId, commonName, directory, blatdb=None, species=None, genus=None, public=False): + data = { + 'id': organismId, + 'name': commonName, + 'directory': directory, + 'publicMode': public, + } + + if blatdb is not None: + data['blatdb'] = blatdb + if genus is not None: + data['genus'] = genus + if species is not None: + data['species'] = species + + return self.request('updateOrganismInfo', data) + + +class UsersClient(Client): + CLIENT_BASE = '/user/' + + def getOrganismPermissionsForUser(self, user): + data = { + 'userId': user.userId, + } + return self.request('getOrganismPermissionsForUser', data) + + def updateOrganismPermission(self, user, organism, administrate=False, + write=False, export=False, read=False): + data = { + 'userId': user.userId, + 'organism': organism, + 'ADMINISTRATE': administrate, + 'WRITE': write, + 'EXPORT': export, + 'READ': read, + } + return self.request('updateOrganismPermission', data) + + def loadUser(self, user): + return self.loadUserById(user.userId) + + def loadUserById(self, userId): + res = self.request('loadUsers', {'userId': userId}) + if isinstance(res, list): + # We can only match one, right? + return UserObj(**res[0]) + else: + return res + + def loadUsers(self, email=None): + res = self.request('loadUsers', {}) + data = [UserObj(**x) for x in res] + if email is not None: + data = [x for x in data if x.username == email] + + return data + + def addUserToGroup(self, group, user): + data = {'group': group.name, 'userId': user.userId} + return self.request('addUserToGroup', data) + + def removeUserFromGroup(self, group, user): + data = {'group': group.name, 'userId': user.userId} + return self.request('removeUserFromGroup', data) + + def createUser(self, email, firstName, lastName, newPassword, role="user", groups=None): + data = { + 'firstName': firstName, + 'lastName': lastName, + 'email': email, + 'role': role, + 'groups': [] if groups is None else groups, + # 'availableGroups': [], + 'newPassword': newPassword, + # 'organismPermissions': [], + } + return self.request('createUser', data) + + def deleteUser(self, user): + return self.request('deleteUser', {'userId': user.userId}) + + def updateUser(self, user, email, firstName, lastName, newPassword): + data = { + 'userId': user.userId, + 'email': email, + 'firstName': firstName, + 'lastName': lastName, + 'newPassword': newPassword, + } + return self.request('updateUser', data) + + +class RemoteRecord(Client): + CLIENT_BASE = None + + def ParseRecord(self, cn): + org = self._wa.organisms.findOrganismByCn(cn) + self._wa.annotations.setSequence(org['commonName'], org['id']) + + data = StringIO.StringIO(self._wa.io.write( + exportType='GFF3', + seqType='genomic', + exportAllSequences=False, + exportGff3Fasta=True, + output="text", + exportFormat="text", + sequences=cn, + )) + data.seek(0) + + for record in GFF.parse(data): + yield WebApolloSeqRecord(record, self._wa) + + +class WebApolloSeqRecord(object): + def __init__(self, sr, wa): + self._sr = sr + self._wa = wa + + def __dir__(self): + return dir(self._sr) + + def __getattr__(self, key): + if key in ('_sr', '_wa'): + print self.__dict__ + return self.__dict__[key] + else: + if key == 'features': + return (WebApolloSeqFeature(x, self._wa) + for x in self._sr.__dict__[key]) + else: + return self._sr.__dict__[key] + + def __setattr__(self, key, value): + if key in ('_sd', '_wa'): + self.__dict__[key] = value + else: + self._sr.__dict__[key] = value + # Methods acting on the SeqRecord object + print key, value + + +class WebApolloSeqFeature(object): + def __init__(self, sf, wa): + self._sf = sf + self._wa = wa + + def __dir__(self): + return dir(self._sf) + + def __getattr__(self, key): + if key in ('_sf', '_wa'): + return self.__dict__[key] + else: + return self._sf.__dict__[key] + + def __setattr__(self, key, value): + if key in ('_sf', '_wa'): + self.__dict__[key] = value + else: + # Methods acting on the SeqFeature object + if key == 'location': + if value.strand != self._sf.location.strand: + self.wa.annotations.flipStrand( + self._sf.qualifiers['ID'][0] + ) + + self.wa.annotations.setBoundaries( + self._sf.qualifiers['ID'][0], + value.start, + value.end, + ) + + self._sf.__dict__[key] = value + else: + self._sf.__dict__[key] = value + print key, value + +def _tnType(feature): + if feature.type in ('gene', 'mRNA', 'exon', 'CDS'): + return feature.type + else: + return 'exon' + +def _yieldFeatData(features): + for f in features: + current = { + 'location': { + 'strand': f.strand, + 'fmin': int(f.location.start), + 'fmax': int(f.location.end), + }, + 'type': { + 'name': _tnType(f), + 'cv': { + 'name': 'sequence', + } + }, + } + if f.type in ('gene', 'mRNA'): + current['name'] = f.qualifiers.get('Name', [f.id])[0] + if hasattr(f, 'sub_features') and len(f.sub_features) > 0: + current['children'] = [x for x in _yieldFeatData(f.sub_features)] + + yield current + +def featuresToFeatureSchema(features): + compiled = [] + for feature in features: + if feature.type != 'gene': + log.warn("Not able to handle %s features just yet...", feature.type) + continue + + for x in _yieldFeatData([feature]): + compiled.append(x) + return compiled