From 074408daecb0c253d516256fc9f69934f07b55b0 Mon Sep 17 00:00:00 2001
From: vermeul <swen@ethz.ch>
Date: Wed, 31 Jan 2018 11:29:24 +0100
Subject: [PATCH] get_group implemented, _repr_html_ for groups

---
 src/python/PyBis/pybis/pybis.py | 246 +++++++++++++++++++++++++-------
 1 file changed, 195 insertions(+), 51 deletions(-)

diff --git a/src/python/PyBis/pybis/pybis.py b/src/python/PyBis/pybis/pybis.py
index 31dd60f53e3..99f8ac05c27 100644
--- a/src/python/PyBis/pybis/pybis.py
+++ b/src/python/PyBis/pybis/pybis.py
@@ -161,10 +161,14 @@ def _definitions(entity):
     }
     return entities[entity]
 
-def get_search_criteria_for_entity(entity):
-    """ Creates a basic search object for a given entity. Returns a dictionary.
+
+def get_search_type_for_entity(entity):
+    """ Returns a dictionary containing the correct search criteria type
+    for a given entity.
+
     Example::
-        get_search_criteria_for_entity('space')
+        get_search_type_for_entity('space')
+        # returns:
         {'@type': 'as.dto.space.search.SpaceSearchCriteria'}
     """
     search_criteria = {
@@ -185,24 +189,33 @@ def get_search_criteria_for_entity(entity):
         "material_type": "as.dto.material.search.MaterialTypeSearchCriteria",
         "vocabulary_term": "as.dto.vocabulary.search.VocabularyTermSearchCriteria",
         "tag": "as.dto.tag.search.TagSearchCriteria",
-        "authorization_group": "as.dto.authorizationgroup.search.AuthorizationGroupSearchCriteria",
+        "authorizationGroup": "as.dto.authorizationgroup.search.AuthorizationGroupSearchCriteria",
         "role_assignment": "as.dto.roleassignment.search.RoleAssignmentSearchCriteria",
         "person": "as.dto.person.search.PersonSearchCriteria",
         "code": "as.dto.common.search.CodeSearchCriteria",
         "sample_type": "as.dto.sample.search.SampleTypeSearchCriteria",
         "global": "as.dto.global.GlobalSearchObject",
     }
-    if entity in search_criteria:
-        return {
-            "@type": search_criteria[entity]
-        }
-    else:
-        return {}
+    return {
+        "@type": search_criteria[entity]
+    }
+
+def get_attrs_for_entity(entity):
+    """ For a given entity this method returns an iterator for all searchable
+    attributes.
+    """
+    search_args = {
+        "person": ['firstName','lastName','email','userId']
+    }
+    for search_arg in search_args[entity]:
+        yield search_arg
+
 
 fetch_option = {
     "space": {"@type": "as.dto.space.fetchoptions.SpaceFetchOptions"},
     "project": {"@type": "as.dto.project.fetchoptions.ProjectFetchOptions"},
     "person": {"@type": "as.dto.person.fetchoptions.PersonFetchOptions"},
+    "users": {"@type": "as.dto.person.fetchoptions.PersonFetchOptions" },
     "experiment": {
         "@type": "as.dto.experiment.fetchoptions.ExperimentFetchOptions",
         "type": {"@type": "as.dto.experiment.fetchoptions.ExperimentTypeFetchOptions"}
@@ -272,6 +285,21 @@ def search_request_for_identifier(ident, entity):
         }
     return search_request
 
+def get_search_criteria(entity, **search_args):
+    search_criteria = get_search_type_for_entity(entity)
+
+    criteria = []
+    for attr in get_attrs_for_entity(entity):
+        if attr in search_args:
+            sub_crit = get_search_type_for_entity(attr)
+            sub_crit['fieldValue'] = get_field_value_search(attr, search_args[attr])
+            criteria.append(sub_crit)
+
+    search_criteria['criteria'] = criteria
+    search_criteria['operator'] = "AND"
+
+    return search_criteria
+
 
 def extract_code(obj):
     if not isinstance(obj, dict):
@@ -341,6 +369,11 @@ def extract_person(person):
     return person['userId']
 
 
+def extract_users(users):
+    if not isinstance(users, dict):
+        return str(users)
+
+
 def crc32(fileName):
     """since Python3 the zlib module returns unsigned integers (2.7: signed int)
     """
@@ -655,7 +688,7 @@ def _subcriteria_for_code(code, object_type):
             fieldtype = "as.dto.common.search.CodeSearchCriteria"
 
           
-        search_criteria = get_search_criteria_for_entity(object_type.lower())
+        search_criteria = get_search_type_for_entity(object_type.lower())
         search_criteria['criteria'] = [{
             "fieldName": fieldname,
             "fieldType": "ATTRIBUTE",
@@ -669,7 +702,7 @@ def _subcriteria_for_code(code, object_type):
         search_criteria["operator"] = "AND"
         return search_criteria
     else:
-        return get_search_criteria_for_entity(object_type.lower())
+        return get_search_type_for_entity(object_type.lower())
 
 
 class Openbis:
@@ -739,9 +772,11 @@ class Openbis:
             "get_spaces()",
             "get_tags()",
             "get_terms()",
+            "new_person(userId, space)",
             "get_persons()",
-            "get_person(userId=None)",
-            "new_person(userId, firstName, lastName, email)",
+            "get_person(userId)",
+            "get_groups()",
+            "get_group(code)",
             "new_group(code, description, userIds)",
             'new_space(name, description)',
             'new_project(space, code, description, attachments)',
@@ -832,7 +867,7 @@ class Openbis:
         data
         """
         if "id" not in request:
-            request["id"] = "1"
+            request["id"] = "2"
         if "jsonrpc" not in request:
             request["jsonrpc"] = "2.0"
         if request["params"][0] is None:
@@ -929,7 +964,14 @@ class Openbis:
     def new_person(self, userId, space=None):
         """ creates an openBIS person
         """
-        return Person(self, userId=userId, space=space) 
+        try:
+            person = self.get_person(userId=userId)
+        except Exception:
+            return Person(self, userId=userId, space=space) 
+
+        raise ValueError(
+            "There already exists a user with userId={}".format(userId)
+        )
 
 
     def new_group(self, code, description=None, users=None):
@@ -937,41 +979,96 @@ class Openbis:
         """
         return Group(self, code=code, description=description, users=users)
 
-    def get_groups(self, code=None):
-        """ Get openBIS AuthorizationGroups
+
+    def get_group(self, groupId, only_data=False):
+        """ Get an openBIS AuthorizationGroup. Returns a Group object.
         """
 
-        criterias = []
-        if code:
-            criterias.append(_subcriteria_for_code(code))
-        criteria = get_search_criteria_for_entity('authorizationGroup')
-        criteria['criteria'] = criterias
+        ids = [{
+            "@type": "as.dto.authorizationgroup.id.AuthorizationGroupPermId",
+            "permId": groupId
+        }]
+
+        fetchopts = {}
+        for option in ['roleAssignments', 'users', 'registrator']:
+            fetchopts[option] = fetch_option[option]
+
+        request = {
+            "method": "getAuthorizationGroups",
+            "params": [
+                self.token,
+                ids,
+                fetchopts
+            ]
+        }
+        resp = self._post_request(self.as_v3, request)
+        if len(resp) == 0:
+            raise ValueError("No group found!")
+
+        for permid in resp:
+            group = resp[permid]
+            parse_jackson(group)
+
+            if only_data:
+                return group
+            else:
+                return Group(self, data=group)
+        
+
+
+    def get_groups(self, **search_args):
+        """ Get openBIS AuthorizationGroups. Returns a «Things» object.
+
+        Usage::
+            groups = e.get.groups()
+            groups[0]             # select first group
+            groups['GROUP_NAME']  # select group with this code
+            for group in groups:
+                ...               # a Group object
+            groups.df             # get a DataFrame object of the group list
+            print(groups)         # print a nice ASCII table (eg. in IPython)
+            groups                # HTML table (in a Jupyter notebook)
+
+        """
+
+        criteria = []
+        for search_arg in ['code', 'description', 'registrator']:
+            if search_arg in search_args:
+                pass
+                #sub_crit = get_search_type_for_entity(search_arg)
+                #criteria.append(sub_crit)
+
+        search_criteria = get_search_type_for_entity('authorizationGroup')
+        search_criteria['criteria'] = criteria
                 
         fetchopts = fetch_option['authorizationGroup']
-        for option in ['roleAssignments']:
+        for option in ['roleAssignments', 'registrator', 'users']:
             fetchopts[option] = fetch_option[option]
         request = {
             "method": "searchAuthorizationGroups",
             "params": [
                 self.token,
-                criteria,
+                search_criteria,
                 fetchopts
             ],
         }
         resp = self._post_request(self.as_v3, request)
         if len(resp['objects']) == 0:
-            raise ValueError("No persons found!")
+            raise ValueError("No groups found!")
 
         objects = resp['objects']
         parse_jackson(objects)
 
-        persons = DataFrame(resp['objects'])
-        persons['permId'] = persons['permId'].map(extract_permid)
-        persons['registrationDate'] = persons['registrationDate'].map(format_timestamp)
-        persons['space'] = persons['space'].map(extract_nested_permid)
+        groups = DataFrame(resp['objects'])
+
+        groups['permId'] = groups['permId'].map(extract_permid)
+        groups['registrator'] = groups['registrator'].map(extract_person)
+        groups['users'] = groups['users'].map(extract_users)
+        groups['registrationDate'] = groups['registrationDate'].map(format_timestamp)
+        groups['modificationDate'] = groups['modificationDate'].map(format_timestamp)
         p = Things(
-            self, entity='person', 
-            df=persons[['permId', 'userId', 'firstName', 'lastName', 'email', 'space', 'registrationDate', 'active']],
+            self, entity='group', 
+            df=groups[['permId', 'code', 'description', 'users', 'registrator', 'registrationDate', 'modificationDate']],
             identifier_name='permId'
         )
         return p
@@ -981,21 +1078,7 @@ class Openbis:
         """ Get openBIS users
         """
 
-        criteria = []
-        for search_arg in ['userId','firstName','lastName','email']:
-            if search_arg in search_args:
-                sub_crit = get_search_criteria_for_entity(search_arg)
-                sub_crit['fieldValue'] = get_field_value_search(search_arg, search_args[search_arg])
-                #sub_crit['fieldValue'] = {
-                #    "value": search_args[search_arg],
-                #    "@type": "as.dto.common.search.StringEqualToValue"
-                #}
-                criteria.append(sub_crit)
-
-        search_criteria = get_search_criteria_for_entity('person')
-        search_criteria['criteria'] = criteria
-        search_criteria['operator'] = "AND"
-                
+        search_criteria = get_search_criteria('person', **search_args)
         fetchopts = {}
         for option in ['space']:
             fetchopts[option] = fetch_option[option]
@@ -1255,7 +1338,7 @@ class Openbis:
             for prop in properties:
                 sub_criteria.append(_subcriteria_for_properties(prop, properties[prop]))
 
-        search_criteria = get_search_criteria_for_entity('experiment')
+        search_criteria = get_search_type_for_entity('experiment')
         search_criteria['criteria'] = sub_criteria
         search_criteria['operator'] = 'AND'
 
@@ -1333,7 +1416,7 @@ class Openbis:
             for prop in properties:
                 sub_criteria.append(_subcriteria_for_properties(prop, properties[prop]))
 
-        search_criteria = get_search_criteria_for_entity('dataset')
+        search_criteria = get_search_type_for_entity('dataset')
         search_criteria['criteria'] = sub_criteria
         search_criteria['operator'] = 'AND'
 
@@ -3298,7 +3381,6 @@ class AttrHolder():
             else:
                 self.__dict__['_' + name]['isModified'] = True
 
-
         elif name in ["identifier"]:
             raise KeyError("you can not modify the {}".format(name))
         elif name == "code":
@@ -3313,7 +3395,7 @@ class AttrHolder():
             self.__dict__['_code'] = value
 
         elif name in [ "description", "userId" ]:
-            self.__dict__['-'+name] = value
+            self.__dict__['_'+name] = value
         else:
             raise KeyError("no such attribute: {}".format(name))
 
@@ -3713,6 +3795,7 @@ class Person(OpenBisObject):
                 request['params'][1][0]['homeSpaceId'] =  request['params'][1][0]['spaceId']
                 del(request['params'][1][0]['spaceId'])
 
+            return json.dumps(request)
             self.openbis._post_request(self.openbis.as_v3, request)
             print("Person successfully updated.")
             new_person_data = self.openbis.get_person(self.permId, only_data=True)
@@ -3741,6 +3824,67 @@ class Group(OpenBisObject):
             'get_roles()', 'add_roles()', 'del_roles()'
         ]
 
+    def _repr_html_(self):
+        def nvl(val, string=''):
+            if val is None:
+                return string
+            return val
+
+        html = """
+            <table border="1" class="dataframe">
+            <thead>
+                <tr style="text-align: right;">
+                <th>attribute</th>
+                <th>value</th>
+                </tr>
+            </thead>
+            <tbody>
+        """
+
+        for attr in self._allowed_attrs:
+            if attr in ['users','roleAssignments']:
+                continue
+            html += "<tr> <td>{}</td> <td>{}</td> </tr>".format(
+                attr, nvl(getattr(self, attr, ''), '')
+            )
+
+        if getattr(self, '_users') is not None:
+            html += "<tr><td>Users</td><td>"
+            html += ", ".join(att['userId'] for att in self._users)
+            html += "</td></tr>"
+
+        html += """
+            </tbody>
+            </table>
+        """
+
+        if getattr(self, '_roleAssignments') is not None:
+            html += """
+                <br/>
+                <b>Role Assignments</b>
+                <table border="1" class="dataframe">
+                <thead>
+                    <tr style="text-align: right;">
+                    <th>Role</th>
+                    <th>Role Level</th>
+                    <th>Space</th>
+                    </tr>
+                </thead>
+                <tbody>
+            """
+            for roleAssignment in self._roleAssignments:
+                html += "<tr><td>{}</td><td>{}</td><td>{}</td></tr>".format(
+                    roleAssignment.get('role'),
+                    roleAssignment.get('roleLevel'),
+                    roleAssignment.get('space').get('code'),
+                )
+            html += """
+                </tbody>
+                </table>
+            """
+        return html
+
+
     def save(self):
         if self.is_new:
             request = self._new_attrs()
-- 
GitLab