comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:6002cc0df04e
1 import requests
2 import json
3 import collections
4 from BCBio import GFF
5 import StringIO
6 import logging
7 logging.getLogger("requests").setLevel(logging.CRITICAL)
8 log = logging.getLogger()
9
10
11
12 class WebApolloInstance(object):
13
14 def __init__(self, url, username, password):
15 self.apollo_url = url
16 self.username = username
17 self.password = password
18
19 self.annotations = AnnotationsClient(self)
20 self.groups = GroupsClient(self)
21 self.io = IOClient(self)
22 self.organisms = OrganismsClient(self)
23 self.users = UsersClient(self)
24 self.metrics = MetricsClient(self)
25 self.bio = RemoteRecord(self)
26
27 def __str__(self):
28 return '<WebApolloInstance at %s>' % self.apollo_url
29
30
31 class GroupObj(object):
32 def __init__(self, **kwargs):
33 self.name = kwargs['name']
34
35 if 'id' in kwargs:
36 self.groupId = kwargs['id']
37
38
39 class UserObj(object):
40 ROLE_USER = 'USER'
41 ROLE_ADMIN = 'ADMIN'
42
43 def __init__(self, **kwargs):
44 # Generally expect 'userId', 'firstName', 'lastName', 'username' (email)
45 for attr in kwargs.keys():
46 setattr(self, attr, kwargs[attr])
47
48 if 'groups' in kwargs:
49 groups = []
50 for groupData in kwargs['groups']:
51 groups.append(GroupObj(**groupData))
52 self.groups = groups
53
54 self.__props = kwargs.keys()
55
56
57 def isAdmin(self):
58 if hasattr(self, 'role'):
59 return self.role == self.ROLE_ADMIN
60 return False
61
62 def refresh(self, wa):
63 # This method requires some sleeping usually.
64 newU = wa.users.loadUser(self).toDict()
65 for prop in newU:
66 setattr(self, prop, newU[prop])
67
68 def toDict(self):
69 data = {}
70 for prop in self.__props:
71 data[prop] = getattr(self, prop)
72 return data
73
74 def __str__(self):
75 return '<User %s: %s %s <%s>>' % (self.userId, self.firstName,
76 self.lastName, self.username)
77
78
79 class Client(object):
80
81 def __init__(self, webapolloinstance, **requestArgs):
82 self._wa = webapolloinstance
83
84 self.__verify = requestArgs.get('verify', True)
85 self._requestArgs = requestArgs
86
87 if 'verify' in self._requestArgs:
88 del self._requestArgs['verify']
89
90 def request(self, clientMethod, data, post_params={}, isJson=True):
91 url = self._wa.apollo_url + self.CLIENT_BASE + clientMethod
92
93 headers = {
94 'Content-Type': 'application/json'
95 }
96
97 data.update({
98 'username': self._wa.username,
99 'password': self._wa.password,
100 })
101
102 r = requests.post(url, data=json.dumps(data), headers=headers,
103 verify=self.__verify, params=post_params, **self._requestArgs)
104
105 if r.status_code == 200:
106 if isJson:
107 d = r.json()
108 if 'username' in d:
109 del d['username']
110 if 'password' in d:
111 del d['password']
112 return d
113 else:
114 return r.text
115
116 # @see self.body for HTTP response body
117 raise Exception("Unexpected response from apollo %s: %s" %
118 (r.status_code, r.text))
119
120 def get(self, clientMethod, get_params):
121 url = self._wa.apollo_url + self.CLIENT_BASE + clientMethod
122 headers = {}
123
124 r = requests.get(url, headers=headers, verify=self.__verify,
125 params=get_params, **self._requestArgs)
126 if r.status_code == 200:
127 d = r.json()
128 if 'username' in d:
129 del d['username']
130 if 'password' in d:
131 del d['password']
132 return d
133 # @see self.body for HTTP response body
134 raise Exception("Unexpected response from apollo %s: %s" %
135 (r.status_code, r.text))
136
137
138 class MetricsClient(Client):
139 CLIENT_BASE = '/metrics/'
140
141 def getServerMetrics(self):
142 return self.get('metrics', {})
143
144
145 class AnnotationsClient(Client):
146 CLIENT_BASE = '/annotationEditor/'
147
148 def _update_data(self, data):
149 if not hasattr(self, '_extra_data'): raise Exception("Please call setSequence first")
150 data.update(self._extra_data)
151 return data
152
153 def setSequence(self, sequence, organism):
154 self._extra_data = {
155 'sequence': sequence,
156 'organism': organism,
157 }
158
159 def setDescription(self, featureDescriptions):
160 data = {
161 'features': featureDescriptions,
162 }
163 data = self._update_data(data)
164 return self.request('setDescription', data)
165
166 def setName(self, uniquename, name):
167 # TODO
168 data = {
169 'features': [
170 {
171 'uniquename': uniquename,
172 'name': name,
173 }
174 ],
175 }
176 data = self._update_data(data)
177 return self.request('setName', data)
178
179 def setNames(self, features):
180 # TODO
181 data = {
182 'features': features,
183 }
184 data = self._update_data(data)
185 return self.request('setName', data)
186
187 def setStatus(self, statuses):
188 # TODO
189 data = {
190 'features': statuses,
191 }
192 data = self._update_data(data)
193 return self.request('setStatus', data)
194
195 def setSymbol(self, symbols):
196 data = {
197 'features': symbols,
198 }
199 data.update(self._extra_data)
200 return self.request('setSymbol', data)
201
202 def getComments(self, features):
203 data = {
204 'features': features,
205 }
206 data = self._update_data(data)
207 return self.request('getComments', data)
208
209 def addAttribute(self, features):
210 data = {
211 'features': features,
212 }
213 data = self._update_data(data)
214 return self.request('addAttribute', data)
215
216 def getFeatures(self):
217 data = self._update_data({})
218 return self.request('getFeatures', data)
219
220 def getSequence(self, uniquename):
221 data = {
222 'features': [
223 {'uniquename': uniquename}
224 ]
225 }
226 data = self._update_data(data)
227 return self.request('getSequence', data)
228
229 def addFeature(self, feature, trustme=False):
230 if not trustme:
231 raise NotImplementedError("Waiting on better docs from project. If you know what you are doing, pass trustme=True to this function.")
232
233 data = {}
234 data.update(feature)
235 data = self._update_data(data)
236 return self.request('addFeature', data)
237
238 def addTranscript(self, transcript, trustme=False):
239 if not trustme:
240 raise NotImplementedError("Waiting on better docs from project. If you know what you are doing, pass trustme=True to this function.")
241
242 data = {}
243 data.update(transcript)
244 data = self._update_data(data)
245 return self.request('addTranscript', data)
246
247 # addExon, add/delete/updateComments, addTranscript skipped due to docs
248
249 def duplicateTranscript(self, transcriptId):
250 data = {
251 'features': [{'uniquename': transcriptId}]
252 }
253
254 data = self._update_data(data)
255 return self.request('duplicateTranscript', data)
256
257 def setTranslationStart(self, uniquename, start):
258 data = {
259 'features': [{
260 'uniquename': uniquename,
261 'location': {
262 'fmin': start
263 }
264 }]
265 }
266 data = self._update_data(data)
267 return self.request('setTranslationStart', data)
268
269 def setTranslationEnd(self, uniquename, end):
270 data = {
271 'features': [{
272 'uniquename': uniquename,
273 'location': {
274 'fmax': end
275 }
276 }]
277 }
278 data = self._update_data(data)
279 return self.request('setTranslationEnd', data)
280
281 def setLongestOrf(self, uniquename):
282 data = {
283 'features': [{
284 'uniquename': uniquename,
285 }]
286 }
287 data = self._update_data(data)
288 return self.request('setLongestOrf', data)
289
290 def setBoundaries(self, uniquename, start, end):
291 data = {
292 'features': [{
293 'uniquename': uniquename,
294 'location': {
295 'fmin': start,
296 'fmax': end,
297 }
298 }]
299 }
300 data = self._update_data(data)
301 return self.request('setBoundaries', data)
302
303 def getSequenceAlterations(self):
304 data = {
305 }
306 data = self._update_data(data)
307 return self.request('getSequenceAlterations', data)
308
309 def setReadthroughStopCodon(self, uniquename):
310 data = {
311 'features': [{
312 'uniquename': uniquename,
313 }]
314 }
315 data = self._update_data(data)
316 return self.request('setReadthroughStopCodon', data)
317
318 def deleteSequenceAlteration(self, uniquename):
319 data = {
320 'features': [{
321 'uniquename': uniquename,
322 }]
323 }
324 data = self._update_data(data)
325 return self.request('deleteSequenceAlteration', data)
326
327 def flipStrand(self, uniquenames):
328 data = {
329 'features': [
330 {'uniquename': x} for x in uniquenames
331 ]
332 }
333 data = self._update_data(data)
334 return self.request('flipStrand', data)
335
336 def mergeExons(self, exonA, exonB):
337 data = {
338 'features': [
339 {'uniquename': exonA},
340 {'uniquename': exonB},
341 ]
342 }
343 data = self._update_data(data)
344 return self.request('mergeExons', data)
345
346 # def splitExon(): pass
347
348 def deleteFeatures(self, uniquenames):
349 assert isinstance(uniquenames, collections.Iterable)
350 data = {
351 'features': [
352 {'uniquename': x} for x in uniquenames
353 ]
354 }
355 data = self._update_data(data)
356 return self.request('deleteFeature', data)
357
358 # def deleteExon(): pass
359
360 # def makeIntron(self, uniquename, ): pass
361
362 def getSequenceSearchTools(self):
363 return self.get('getSequenceSearchTools', {})
364
365 def getCannedComments(self):
366 return self.get('getCannedComments', {})
367
368 def searchSequence(self, searchTool, sequence, database):
369 data = {
370 'key': searchTool,
371 'residues': sequence,
372 'database_id': database,
373 }
374 return self.request('searchSequences', data)
375
376 def getGff3(self, uniquenames):
377 assert isinstance(uniquenames, collections.Iterable)
378 data = {
379 'features': [
380 {'uniquename': x} for x in uniquenames
381 ]
382 }
383 data = self._update_data(data)
384 return self.request('getGff3', data, isJson=False)
385
386
387 class GroupsClient(Client):
388 CLIENT_BASE = '/group/'
389
390 def createGroup(self, name):
391 data = {'name': name}
392 return self.request('createGroup', data)
393
394 def getOrganismPermissionsForGroup(self, group):
395 data = {
396 'id': group.groupId,
397 'name': group.name,
398 }
399 return self.request('getOrganismPermissionsForGroup', data)
400
401 def loadGroups(self, group=None):
402 data ={}
403 if group is not None:
404 data['groupId'] = group.groupId
405
406 return self.request('loadGroups', data)
407
408 def deleteGroup(self, group):
409 data = {
410 'id': group.groupId,
411 'name': group.name,
412 }
413 return self.request('deleteGroup', data)
414
415 def updateGroup(self, group, newName):
416 # TODO: Sure would be nice if modifying ``group.name`` would invoke
417 # this?
418 data = {
419 'id': group.groupId,
420 'name': newName,
421 }
422 return self.request('updateGroup', data)
423
424 def updateOrganismPermission(self, group, organismName,
425 administrate=False, write=False, read=False,
426 export=False):
427 data = {
428 'groupId': group.groupId,
429 'name': organismName,
430 'administrate': administrate,
431 'write': write,
432 'export': export,
433 'read': read,
434 }
435 return self.request('updateOrganismPermission', data)
436
437 def updateMembership(self, group, users):
438 data = {
439 'groupId': group.groupId,
440 'user': [user.email for user in users]
441 }
442 return self.request('updateMembership', data)
443
444
445 class IOClient(Client):
446 CLIENT_BASE = '/IOService/'
447
448 def write(self, exportType='FASTA', seqType='peptide',
449 exportFormat='text', sequences=None, organism=None,
450 output='text', exportAllSequences=False,
451 exportGff3Fasta=False):
452 if exportType not in ('FASTA', 'GFF3'):
453 raise Exception("exportType must be one of FASTA, GFF3")
454
455 if seqType not in ('peptide', 'cds', 'cdna', 'genomic'):
456 raise Exception("seqType must be one of peptide, cds, dna, genomic")
457
458 if exportFormat not in ('gzip', 'text'):
459 raise Exception("exportFormat must be one of gzip, text")
460
461 if output not in ('file', 'text'):
462 raise Exception("output must be one of file, text")
463
464 data = {
465 'type': exportType,
466 'seqType': seqType,
467 'format': exportFormat,
468 'sequences': sequences,
469 'organism': organism,
470 'output': output,
471 'exportAllSequences': exportAllSequences,
472 'exportGff3Fasta': exportGff3Fasta,
473 }
474
475 return self.request('write', data, isJson=output == 'file')
476
477 def download(self, uuid, outputFormat='gzip'):
478
479 if outputFormat.lower() not in ('gzip', 'text'):
480 raise Exception("outputFormat must be one of file, text")
481
482 data = {
483 'format': outputFormat,
484 'uuid': uuid,
485 }
486 return self.request('write', data)
487
488
489 class OrganismsClient(Client):
490 CLIENT_BASE = '/organism/'
491
492 def addOrganism(self, commonName, directory, blatdb=None, species=None,
493 genus=None, public=False):
494 data = {
495 'commonName': commonName,
496 'directory': directory,
497 'publicMode': public,
498 }
499
500 if blatdb is not None:
501 data['blatdb'] = blatdb
502 if genus is not None:
503 data['genus'] = genus
504 if species is not None:
505 data['species'] = species
506
507 return self.request('addOrganism', data)
508
509 def findAllOrganisms(self):
510 return self.request('findAllOrganisms', {})
511
512 def findOrganismByCn(self, cn):
513 orgs = self.findAllOrganisms()
514 orgs = [x for x in orgs if x['commonName'] == cn]
515 if len(orgs) == 0:
516 raise Exception("Unknown common name")
517 else:
518 return orgs[0]
519
520 def deleteOrganism(self, organismId):
521 return self.request('deleteOrganism', {'id': organismId})
522
523 def deleteOrganismFeatures(self, organismId):
524 return self.request('deleteOrganismFeatures', {'id': organismId})
525
526 def getSequencesForOrganism(self, commonName):
527 return self.request('getSequencesForOrganism', {'organism': commonName})
528
529 def updateOrganismInfo(self, organismId, commonName, directory, blatdb=None, species=None, genus=None, public=False):
530 data = {
531 'id': organismId,
532 'name': commonName,
533 'directory': directory,
534 'publicMode': public,
535 }
536
537 if blatdb is not None:
538 data['blatdb'] = blatdb
539 if genus is not None:
540 data['genus'] = genus
541 if species is not None:
542 data['species'] = species
543
544 return self.request('updateOrganismInfo', data)
545
546
547 class UsersClient(Client):
548 CLIENT_BASE = '/user/'
549
550 def getOrganismPermissionsForUser(self, user):
551 data = {
552 'userId': user.userId,
553 }
554 return self.request('getOrganismPermissionsForUser', data)
555
556 def updateOrganismPermission(self, user, organism, administrate=False,
557 write=False, export=False, read=False):
558 data = {
559 'userId': user.userId,
560 'organism': organism,
561 'ADMINISTRATE': administrate,
562 'WRITE': write,
563 'EXPORT': export,
564 'READ': read,
565 }
566 return self.request('updateOrganismPermission', data)
567
568 def loadUser(self, user):
569 return self.loadUserById(user.userId)
570
571 def loadUserById(self, userId):
572 res = self.request('loadUsers', {'userId': userId})
573 if isinstance(res, list):
574 # We can only match one, right?
575 return UserObj(**res[0])
576 else:
577 return res
578
579 def loadUsers(self, email=None):
580 res = self.request('loadUsers', {})
581 data = [UserObj(**x) for x in res]
582 if email is not None:
583 data = [x for x in data if x.username == email]
584
585 return data
586
587 def addUserToGroup(self, group, user):
588 data = {'group': group.name, 'userId': user.userId}
589 return self.request('addUserToGroup', data)
590
591 def removeUserFromGroup(self, group, user):
592 data = {'group': group.name, 'userId': user.userId}
593 return self.request('removeUserFromGroup', data)
594
595 def createUser(self, email, firstName, lastName, newPassword, role="user", groups=None):
596 data = {
597 'firstName': firstName,
598 'lastName': lastName,
599 'email': email,
600 'role': role,
601 'groups': [] if groups is None else groups,
602 # 'availableGroups': [],
603 'newPassword': newPassword,
604 # 'organismPermissions': [],
605 }
606 return self.request('createUser', data)
607
608 def deleteUser(self, user):
609 return self.request('deleteUser', {'userId': user.userId})
610
611 def updateUser(self, user, email, firstName, lastName, newPassword):
612 data = {
613 'userId': user.userId,
614 'email': email,
615 'firstName': firstName,
616 'lastName': lastName,
617 'newPassword': newPassword,
618 }
619 return self.request('updateUser', data)
620
621
622 class RemoteRecord(Client):
623 CLIENT_BASE = None
624
625 def ParseRecord(self, cn):
626 org = self._wa.organisms.findOrganismByCn(cn)
627 self._wa.annotations.setSequence(org['commonName'], org['id'])
628
629 data = StringIO.StringIO(self._wa.io.write(
630 exportType='GFF3',
631 seqType='genomic',
632 exportAllSequences=False,
633 exportGff3Fasta=True,
634 output="text",
635 exportFormat="text",
636 sequences=cn,
637 ))
638 data.seek(0)
639
640 for record in GFF.parse(data):
641 yield WebApolloSeqRecord(record, self._wa)
642
643
644 class WebApolloSeqRecord(object):
645 def __init__(self, sr, wa):
646 self._sr = sr
647 self._wa = wa
648
649 def __dir__(self):
650 return dir(self._sr)
651
652 def __getattr__(self, key):
653 if key in ('_sr', '_wa'):
654 print self.__dict__
655 return self.__dict__[key]
656 else:
657 if key == 'features':
658 return (WebApolloSeqFeature(x, self._wa)
659 for x in self._sr.__dict__[key])
660 else:
661 return self._sr.__dict__[key]
662
663 def __setattr__(self, key, value):
664 if key in ('_sd', '_wa'):
665 self.__dict__[key] = value
666 else:
667 self._sr.__dict__[key] = value
668 # Methods acting on the SeqRecord object
669 print key, value
670
671
672 class WebApolloSeqFeature(object):
673 def __init__(self, sf, wa):
674 self._sf = sf
675 self._wa = wa
676
677 def __dir__(self):
678 return dir(self._sf)
679
680 def __getattr__(self, key):
681 if key in ('_sf', '_wa'):
682 return self.__dict__[key]
683 else:
684 return self._sf.__dict__[key]
685
686 def __setattr__(self, key, value):
687 if key in ('_sf', '_wa'):
688 self.__dict__[key] = value
689 else:
690 # Methods acting on the SeqFeature object
691 if key == 'location':
692 if value.strand != self._sf.location.strand:
693 self.wa.annotations.flipStrand(
694 self._sf.qualifiers['ID'][0]
695 )
696
697 self.wa.annotations.setBoundaries(
698 self._sf.qualifiers['ID'][0],
699 value.start,
700 value.end,
701 )
702
703 self._sf.__dict__[key] = value
704 else:
705 self._sf.__dict__[key] = value
706 print key, value
707
708 def _tnType(feature):
709 if feature.type in ('gene', 'mRNA', 'exon', 'CDS'):
710 return feature.type
711 else:
712 return 'exon'
713
714 def _yieldFeatData(features):
715 for f in features:
716 current = {
717 'location': {
718 'strand': f.strand,
719 'fmin': int(f.location.start),
720 'fmax': int(f.location.end),
721 },
722 'type': {
723 'name': _tnType(f),
724 'cv': {
725 'name': 'sequence',
726 }
727 },
728 }
729 if f.type in ('gene', 'mRNA'):
730 current['name'] = f.qualifiers.get('Name', [f.id])[0]
731 if hasattr(f, 'sub_features') and len(f.sub_features) > 0:
732 current['children'] = [x for x in _yieldFeatData(f.sub_features)]
733
734 yield current
735
736 def featuresToFeatureSchema(features):
737 compiled = []
738 for feature in features:
739 if feature.type != 'gene':
740 log.warn("Not able to handle %s features just yet...", feature.type)
741 continue
742
743 for x in _yieldFeatData([feature]):
744 compiled.append(x)
745 return compiled