diff 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
line wrap: on
line diff
--- a/webapollo.py	Sat Mar 04 18:00:52 2017 -0500
+++ b/webapollo.py	Tue Jun 27 04:05:17 2017 -0400
@@ -1,23 +1,29 @@
-import requests
-import json
-import os
+from __future__ import print_function
+import argparse
 import collections
-try:
-    import StringIO as io
-except:
-    import io
+import json
 import logging
+import os
+import requests
 import time
-import argparse
+from future import standard_library
+from builtins import next
+from builtins import str
+from builtins import object
 from abc import abstractmethod
 from BCBio import GFF
 from Bio import SeqIO
+standard_library.install_aliases()
+try:
+    import StringIO as io
+except BaseException:
+    import io
 logging.getLogger("requests").setLevel(logging.CRITICAL)
 log = logging.getLogger()
 
 
 #############################################
-###### BEGIN IMPORT OF CACHING LIBRARY ######
+#      BEGIN IMPORT OF CACHING LIBRARY      #
 #############################################
 # This code is licensed under the MIT       #
 # License and is a copy of code publicly    #
@@ -25,6 +31,7 @@
 # e27332bc82f4e327aedaec17c9b656ae719322ed  #
 # of https://github.com/tkem/cachetools/    #
 #############################################
+
 class DefaultMapping(collections.MutableMapping):
 
     __slots__ = ()
@@ -65,6 +72,7 @@
             self[key] = value = default
         return value
 
+
 DefaultMapping.register(dict)
 
 
@@ -381,21 +389,24 @@
 
 
 #############################################
-######  END IMPORT OF CACHING LIBRARY  ######
+#       END IMPORT OF CACHING LIBRARY       #
 #############################################
 
+
 cache = TTLCache(
-    100, # Up to 100 items
-    5 * 60 # 5 minute cache life
+    100,  # Up to 100 items
+    5 * 60  # 5 minute cache life
 )
 userCache = TTLCache(
-    2, # Up to 2 items
-    60 # 1 minute cache life
+    2,  # Up to 2 items
+    60  # 1 minute cache life
 )
 
+
 class UnknownUserException(Exception):
     pass
 
+
 def WAAuth(parser):
     parser.add_argument('apollo', help='Complete Apollo URL')
     parser.add_argument('username', help='WA Username')
@@ -468,8 +479,6 @@
         self.apollo_url = url
         self.username = username
         self.password = password
-        # TODO: Remove after apollo 2.0.6.
-        self.clientToken = time.time()
 
         self.annotations = AnnotationsClient(self)
         self.groups = GroupsClient(self)
@@ -478,6 +487,10 @@
         self.users = UsersClient(self)
         self.metrics = MetricsClient(self)
         self.bio = RemoteRecord(self)
+        self.status = StatusClient(self)
+        self.canned_comments = CannedCommentsClient(self)
+        self.canned_keys = CannedKeysClient(self)
+        self.canned_values = CannedValuesClient(self)
 
     def __str__(self):
         return '<WebApolloInstance at %s>' % self.apollo_url
@@ -539,6 +552,12 @@
             data[prop] = getattr(self, prop)
         return data
 
+    def orgPerms(self):
+        for orgPer in self.organismPermissions:
+            if len(orgPer['permissions']) > 2:
+                orgPer['permissions'] = json.loads(orgPer['permissions'])
+                yield orgPer
+
     def __str__(self):
         return '<User %s: %s %s <%s>>' % (self.userId, self.firstName,
                                           self.lastName, self.username)
@@ -565,7 +584,6 @@
         data.update({
             'username': self._wa.username,
             'password': self._wa.password,
-            'clientToken': self._wa.clientToken,
         })
 
         r = requests.post(url, data=json.dumps(data), headers=headers,
@@ -677,22 +695,49 @@
         data = self._update_data(data)
         return self.request('getComments', data)
 
-    def addComments(self, feature_id, comment):
-        #TODO: This is probably not great and will delete comments, if I had to guess...
+    def addComments(self, feature_id, comments):
+        # TODO: This is probably not great and will delete comments, if I had to guess...
         data = {
             'features': [
                 {
                     'uniquename': feature_id,
-                    'comments': [comment]
+                    'comments': comments
                 }
             ],
         }
         data = self._update_data(data)
-        return self.request('getComments', data)
+        return self.request('addComments', data)
+
+    def addAttributes(self, feature_id, attributes):
+        nrps = []
+        for (key, values) in attributes.items():
+            for value in values:
+                nrps.append({
+                    'tag': key,
+                    'value': value
+                })
 
-    def addAttribute(self, features):
         data = {
-            'features': features,
+            'features': [
+                {
+                    'uniquename': feature_id,
+                    'non_reserved_properties': nrps
+                }
+            ]
+        }
+        data = self._update_data(data)
+        return self.request('addAttribute', data)
+
+    def deleteAttribute(self, feature_id, key, value):
+        data = {
+            'features': [
+                {
+                    'uniquename': feature_id,
+                    'non_reserved_properties': [
+                        {'tag': key, 'value': value}
+                    ]
+                }
+            ]
         }
         data = self._update_data(data)
         return self.request('addAttribute', data)
@@ -991,6 +1036,198 @@
         return self.request('write', data)
 
 
+class StatusClient(Client):
+    CLIENT_BASE = '/availableStatus/'
+
+    def addStatus(self, value):
+        data = {
+            'value': value
+        }
+
+        return self.request('createStatus', data)
+
+    def findAllStatuses(self):
+        return self.request('showStatus', {})
+
+    def findStatusByValue(self, value):
+        statuses = self.findAllStatuses()
+        statuses = [x for x in statuses if x['value'] == value]
+        if len(statuses) == 0:
+            raise Exception("Unknown status value")
+        else:
+            return statuses[0]
+
+    def findStatusById(self, id_number):
+        statuses = self.findAllStatuses()
+        statuses = [x for x in statuses if str(x['id']) == str(id_number)]
+        if len(statuses) == 0:
+            raise Exception("Unknown ID")
+        else:
+            return statuses[0]
+
+    def updateStatus(self, id_number, new_value):
+        data = {
+            'id': id_number,
+            'new_value': new_value
+        }
+
+        return self.request('updateStatus', data)
+
+    def deleteStatus(self, id_number):
+        data = {
+            'id': id_number
+        }
+
+        return self.request('deleteStatus', data)
+
+
+class CannedCommentsClient(Client):
+    CLIENT_BASE = '/cannedComment/'
+
+    def addComment(self, comment, metadata=""):
+        data = {
+            'comment': comment,
+            'metadata': metadata
+        }
+
+        return self.request('createComment', data)
+
+    def findAllComments(self):
+        return self.request('showComment', {})
+
+    def findCommentByValue(self, value):
+        comments = self.findAllComments()
+        comments = [x for x in comments if x['comment'] == value]
+        if len(comments) == 0:
+            raise Exception("Unknown comment")
+        else:
+            return comments[0]
+
+    def findCommentById(self, id_number):
+        comments = self.findAllComments()
+        comments = [x for x in comments if str(x['id']) == str(id_number)]
+        if len(comments) == 0:
+            raise Exception("Unknown ID")
+        else:
+            return comments[0]
+
+    def updateComment(self, id_number, new_value, metadata=None):
+        data = {
+            'id': id_number,
+            'new_comment': new_value
+        }
+
+        if metadata is not None:
+            data['metadata'] = metadata
+
+        return self.request('updateComment', data)
+
+    def deleteComment(self, id_number):
+        data = {
+            'id': id_number
+        }
+
+        return self.request('deleteComment', data)
+
+
+class CannedKeysClient(Client):
+    CLIENT_BASE = '/cannedKey/'
+
+    def addKey(self, key, metadata=""):
+        data = {
+            'key': key,
+            'metadata': metadata
+        }
+
+        return self.request('createKey', data)
+
+    def findAllKeys(self):
+        return self.request('showKey', {})
+
+    def findKeyByValue(self, value):
+        keys = self.findAllKeys()
+        keys = [x for x in keys if x['label'] == value]
+        if len(keys) == 0:
+            raise Exception("Unknown key")
+        else:
+            return keys[0]
+
+    def findKeyById(self, id_number):
+        keys = self.findAllKeys()
+        keys = [x for x in keys if str(x['id']) == str(id_number)]
+        if len(keys) == 0:
+            raise Exception("Unknown ID")
+        else:
+            return keys[0]
+
+    def updateKey(self, id_number, new_key, metadata=None):
+        data = {
+            'id': id_number,
+            'new_key': new_key
+        }
+
+        if metadata is not None:
+            data['metadata'] = metadata
+
+        return self.request('updateKey', data)
+
+    def deleteKey(self, id_number):
+        data = {
+            'id': id_number
+        }
+
+        return self.request('deleteKey', data)
+
+
+class CannedValuesClient(Client):
+    CLIENT_BASE = '/cannedValue/'
+
+    def addValue(self, value, metadata=""):
+        data = {
+            'value': value,
+            'metadata': metadata
+        }
+
+        return self.request('createValue', data)
+
+    def findAllValues(self):
+        return self.request('showValue', {})
+
+    def findValueByValue(self, value):
+        values = self.findAllValues()
+        values = [x for x in values if x['label'] == value]
+        if len(values) == 0:
+            raise Exception("Unknown value")
+        else:
+            return values[0]
+
+    def findValueById(self, id_number):
+        values = self.findAllValues()
+        values = [x for x in values if str(x['id']) == str(id_number)]
+        if len(values) == 0:
+            raise Exception("Unknown ID")
+        else:
+            return values[0]
+
+    def updateValue(self, id_number, new_value, metadata=None):
+        data = {
+            'id': id_number,
+            'new_value': new_value
+        }
+
+        if metadata is not None:
+            data['metadata'] = metadata
+
+        return self.request('updateValue', data)
+
+    def deleteValue(self, id_number):
+        data = {
+            'id': id_number
+        }
+
+        return self.request('deleteValue', data)
+
+
 class OrganismsClient(Client):
     CLIENT_BASE = '/organism/'
 
@@ -1222,7 +1459,7 @@
 
 
 def _tnType(feature):
-    if feature.type in ('gene', 'mRNA', 'exon', 'CDS'):
+    if feature.type in ('gene', 'mRNA', 'exon', 'CDS', 'terminator', 'tRNA'):
         return feature.type
     else:
         return 'exon'
@@ -1365,8 +1602,55 @@
     # Return org list
     return orgs
 
-## This is all for implementing the command line interface for testing.
+
+def galaxy_list_users(trans, *args, **kwargs):
+    email = trans.get_user().email
+    wa = WebApolloInstance(
+        os.environ['GALAXY_WEBAPOLLO_URL'],
+        os.environ['GALAXY_WEBAPOLLO_USER'],
+        os.environ['GALAXY_WEBAPOLLO_PASSWORD']
+    )
+    # Assert that the email exists in apollo
+    try:
+        gx_user = wa.requireUser(email)
+    except UnknownUserException:
+        return []
 
+    # Key for cached data
+    cacheKey = 'users-' + email
+    # We don't want to trust "if key in cache" because between asking and fetch
+    # it might through key error.
+    if cacheKey not in cache:
+        # However if it ISN'T there, we know we're safe to fetch + put in
+        # there.
+        data = _galaxy_list_users(wa, gx_user, *args, **kwargs)
+        cache[cacheKey] = data
+        return data
+    try:
+        # The cache key may or may not be in the cache at this point, it
+        # /likely/ is. However we take no chances that it wasn't evicted between
+        # when we checked above and now, so we reference the object from the
+        # cache in preparation to return.
+        data = cache[cacheKey]
+        return data
+    except KeyError:
+        # If access fails due to eviction, we will fail over and can ensure that
+        # data is inserted.
+        data = _galaxy_list_users(wa, gx_user, *args, **kwargs)
+        cache[cacheKey] = data
+        return data
+
+
+def _galaxy_list_users(wa, gx_user, *args, **kwargs):
+    # Fetch the users.
+    user_data = []
+    for user in wa.users.loadUsers():
+        # Reformat
+        user_data.append((user.username, user.username, False))
+    return user_data
+
+
+# This is all for implementing the command line interface for testing.
 class obj(object):
     pass
 
@@ -1381,16 +1665,46 @@
         o.email = self.un
         return o
 
+
+def retry(closure, sleep=1, limit=5):
+    """
+    Apollo has the bad habit of returning 500 errors if you call APIs
+    too quickly, largely because of the unholy things that happen in
+    grails.
+
+    To deal with the fact that we cannot send an addComments call too
+    quickly after a createFeature call, we have this function that will
+    keep calling a closure until it works.
+    """
+    count = 0
+    while True:
+        count += 1
+
+        if count >= limit:
+            return False
+        try:
+            # Try calling it
+            closure()
+            # If successful, exit
+            return True
+        except Exception as e:
+            log.info(str(e)[0:100])
+            time.sleep(sleep)
+
+
 if __name__ == '__main__':
     parser = argparse.ArgumentParser(description='Test access to apollo server')
     parser.add_argument('email', help='Email of user to test')
-    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.')
+    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.')
     args = parser.parse_args()
 
     trans = fakeTrans(args.email)
     if args.action == 'org':
         for f in galaxy_list_orgs(trans):
             print(f)
-    else:
+    elif args.action == 'group':
         for f in galaxy_list_groups(trans):
             print(f)
+    else:
+        for f in galaxy_list_users(trans):
+            print(f)