Skip to content
Snippets Groups Projects
pybis.py 155 KiB
Newer Older
  • Learn to ignore specific revisions
  •                 raise ValueError(resp['error']['message'])
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                elif 'result' in resp:
                    return resp['result']
    
                else:
                    raise ValueError('request did not return either result nor error')
            else:
                raise ValueError('general error while performing post request')
    
    
        def logout(self):
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            """ Log out of openBIS. After logout, the session token is no longer valid.
    
    
            logout_request = {
    
                "method": "logout",
                "params": [self.token],
    
            resp = self._post_request(self.as_v3, logout_request)
    
            return resp
    
        def login(self, username=None, password=None, save_token=False):
    
            Expects a username and a password and updates the token (session-ID).
            The token is then used for every request.
    
            Clients may want to store the credentials object in a credentials store after successful login.
    
            Throw a ValueError with the error message if login failed.
            """
    
            if password is None:
                import getpass
                password = getpass.getpass()
    
    
            login_request = {
    
                "method": "login",
                "params": [username, password],
    
            result = self._post_request(self.as_v3, login_request)
    
            if result is None:
                raise ValueError("login to openBIS failed")
            else:
                self.token = result
    
                # update the OPENBIS_TOKEN environment variable, if OPENBIS_URL is identical to self.url
                if os.environ.get('OPENBIS_URL') == self.url:
                    os.environ['OPENBIS_TOKEN'] = self.token
    
        def _password(self, password=None, pstore={} ):
            """An elegant way to store passwords which are used later
            without giving the user an easy possibility to retrieve it.
            """
            import inspect
            allowed_methods = ['mount']
    
            if password is not None:
                pstore['password'] = password
            else:
                if inspect.stack()[1][3] in allowed_methods:
                    return pstore.get('password')
                else:
                    raise Exception("This method can only be called from these internal methods: {}".format(allowed_methods))
    
        def unmount(self, mountpoint=None):
            """Unmount a given mountpoint or unmount the stored mountpoint.
            If the umount command does not work, try the pkill command.
            If still not successful, throw an error message.
    
            if mountpoint is None and not getattr(self, 'mountpoint', None):
                raise ValueError("please provide a mountpoint to unmount")
    
            if mountpoint is None:
                mountpoint = self.mountpoint
    
            full_mountpoint_path = os.path.abspath(os.path.expanduser(mountpoint))
    
            if not os.path.exists(full_mountpoint_path):
                return
    
            # mountpoint is not a mountpoint path
            if not os.path.ismount(full_mountpoint_path):
                return
    
            status = subprocess.call('umount {}'.format(full_mountpoint_path), shell=True)
            if status == 1:
                status = subprocess.call(
                    'pkill -9 sshfs && umount "{}"'.format(full_mountpoint_path),
                    shell = True
                )
    
            if status == 1:
                raise OSError("could not unmount mountpoint: {} Please try to unmount manually".format(full_mountpoint_path))
            else:
    
               if VERBOSE: print("Successfully unmounted {}".format(full_mountpoint_path))
    
        def is_mounted(self, mountpoint=None):
    
            if mountpoint is None:
                mountpoint = getattr(self, 'mountpoint', None)
    
            if mountpoint is None:
                return False
    
            return os.path.ismount(mountpoint)
    
    
        def get_mountpoint(self, search_mountpoint=False):
    
            """Returns the path to the active mountpoint.
            Returns None if no mountpoint is found or if the mountpoint is not mounted anymore.
    
    Swen Vermeul's avatar
    Swen Vermeul committed
    
            search_mountpoint=True:  Tries to figure out an existing mountpoint for a given hostname 
                                     (experimental, does not work under Windows yet)
    
            """
    
            mountpoint = getattr(self, 'mountpoint', None)
            if mountpoint:
                if self.is_mounted(mountpoint):
                    return mountpoint
                else:
                    return None
    
            else:
                if not search_mountpoint: return None
    
    
            # try to find out the mountpoint
            import subprocess
            p1 = subprocess.Popen(["mount", "-d"], stdout=subprocess.PIPE)
            p2 = subprocess.Popen(["grep", "--fixed-strings", self.hostname], stdin=p1.stdout, stdout=subprocess.PIPE)
            p1.stdout.close()  # Allow p1 to receive a SIGPIPE if p2 exits.
            output = p2.communicate()[0]
            output = output.decode()
            # output will either be '' (=not mounted) or a string like this:
            # {username}@{hostname}:{path} on {mountpoint} (osxfuse, nodev, nosuid, synchronous, mounted by vermeul)
            try:
                mountpoint = output.split()[2]
                self.mountpoint = mountpoint
                return mountpoint
            except Exception:
                return None
    
    
        def mount(self,
            username=None, password=None,
            hostname=None, mountpoint=None,
            volname=None, path='/', port=2222,
            kex_algorithms ='+diffie-hellman-group1-sha1'
        ):
    
            """Mounts openBIS dataStore without being root, using sshfs and fuse. Both
            SSHFS and FUSE must be installed on the system (see below)
    
            Params:
            username -- default: the currently used username
            password -- default: the currently used password
            hostname -- default: the current hostname
            mountpoint -- default: ~/hostname
    
    
            FUSE / SSHFS Installation (requires root privileges):
    
    
            Mac OS X
            ========
            Follow the installation instructions on
            https://osxfuse.github.io
    
            Unix Cent OS 7
            ==============
            $ sudo yum install epel-release
            $ sudo yum --enablerepo=epel -y install fuse-sshfs
            $ user="$(whoami)"
            $ usermod -a -G fuse "$user"
    
            if self.is_mounted():
                if VERBOSE: print("openBIS dataStore is already mounted on {}".format(self.mountpoint))
                return
    
    
            def check_sshfs_is_installed():
                import subprocess
                import errno
                try:
                    subprocess.call('sshfs --help', shell=True)
                except OSError as e:
                    if e.errno == errno.ENOENT:
                        raise ValueError('Your system seems not to have SSHFS installed. For Mac OS X, see installation instructions on https://osxfuse.github.io For Unix: $ sudo yum install epel-release && sudo yum --enablerepo=epel -y install fuse-sshfs && user="$(whoami)" && usermod -a -G fuse "$user"')
    
            check_sshfs_is_installed()
    
    
            if username is None: username = self._get_username()
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            if not username: raise ValueError("no token available - please provide a username")
    
            if password is None: password = self._password()
            if not password: raise ValueError("please provide a password")
    
            if hostname is None: hostname = self.hostname
            if not hostname: raise ValueError("please provide a hostname")
    
            if mountpoint is None: mountpoint = os.path.join('~', self.hostname)
    
    
            # check if mountpoint exists, otherwise create it
            full_mountpoint_path = os.path.abspath(os.path.expanduser(mountpoint))
            if not os.path.exists(full_mountpoint_path):
                os.makedirs(full_mountpoint_path)
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            print("full_mountpoint_path: ", full_mountpoint_path)
    
    
            from sys import platform
            supported_platforms = ['darwin', 'linux']
            if platform not in supported_platforms:
                raise ValueError("This method is not yet supported on {} plattform".format(platform))
    
    
            os_options = {
    
                "darwin": "-oauto_cache,reconnect,defer_permissions,noappledouble,negative_vncache,volname={} -oStrictHostKeyChecking=no ".format(hostname),
                "linux": "-oauto_cache,reconnect -oStrictHostKeyChecking=no",
    
            import subprocess
            args = {
                "username": username,
                "password": password,
    
                "port": port,
                "path": path,
                "mountpoint": mountpoint,
                "volname": volname,
    
                "os_options": os_options[platform],
                "kex_algorithms": kex_algorithms,
    
            cmd = 'echo "{password}" | sshfs'\
    
                  ' {username}@{hostname}:{path} {mountpoint}' \
    
                  ' -o port={port} -o ssh_command="ssh -oKexAlgorithms={kex_algorithms}" -o password_stdin'\
                  ' {os_options}'.format(**args)
    
            status = subprocess.call(cmd, shell=True)
    
            if status == 0:
                if VERBOSE: print("Mounted successfully to {}".format(full_mountpoint_path))
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                self.mountpoint = full_mountpoint_path
                return self.mountpoint
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                raise OSError("mount failed, exit status: ", status)
    
    
        def get_server_information(self):
            """ Returns a dict containing the following server information:
                api-version, archiving-configured, authentication-service, enabled-technologies, project-samples-enabled
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            """
            if self.server_information is not None:
                return self.server_information
    
    
            request = {
                "method": "getServerInformation",
                "params": [self.token],
            }
            resp = self._post_request(self.as_v3, request)
            if resp is not None:
                # result is a dict of strings - use more useful types
                keys_boolean = ['archiving-configured', 'project-samples-enabled']
                keys_csv = ['enabled-technologies']
                for key in keys_boolean:
                    if key in resp:
                        resp[key] = resp[key] == 'true'
                for key in keys_csv:
                    if key in resp:
                        resp[key] = list(map(lambda item: item.strip(), resp[key].split(',')))
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                self.server_information = ServerInformation(resp)
                return self.server_information
    
            else:
                raise ValueError("Could not get the server information")
    
    
    
            # Request just 1 permId
            request = {
                "method": "createPermIdStrings",
                "params": [self.token, 1],
            }
            resp = self._post_request(self.as_v3, request)
            if resp is not None:
                return resp[0]
            else:
                raise ValueError("Could not create permId")
    
        def get_datastores(self):
    
            """ Get a list of all available datastores. Usually there is only one, but in some cases
    
            there might be multiple servers. If you upload a file, you need to specifiy the datastore you want
    
            the file uploaded to.
            """
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            if hasattr(self, 'datastores'):
    
                return self.datastores
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                "method": "searchDataStores",
                "params": [
                    self.token,
                    {
                        "@type": "as.dto.datastore.search.DataStoreSearchCriteria"
                    },
                    {
                        "@type": "as.dto.datastore.fetchoptions.DataStoreFetchOptions"
                    }
                ]
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            resp = self._post_request(self.as_v3, request)
            attrs=['code','downloadUrl','remoteUrl']
            if len(resp['objects']) == 0:
    
                raise ValueError("No datastore found!")
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            else:
                objects = resp['objects']
                parse_jackson(objects)
                datastores = DataFrame(objects)
    
                self.datastores = datastores[attrs]
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                return datastores[attrs]
    
        def gen_code(self, entity, prefix=""):
            """ Get the next sequence number for a Sample, Experiment, DataSet and Material. Other entities are currently not supported.
            Usage::
                gen_code('SAMPLE', 'SAM-')
                gen_code('EXPERIMENT', 'EXP-')
                gen_code('DATASET', '')
                gen_code('MATERIAL', 'MAT-')
            """
    
            entity = entity.upper()
            entity2enum = {
                "DATASET" : "DATA_SET",
                "OBJECT"  : "SAMPLE",
                "SAMPLE"  : "SAMPLE",
                "EXPERIMENT" : "EXPERIMENT",
                "COLLECTION" : "EXPERIMENT",
                "MATERIAL" : "MATERIAL",
            }
    
            if entity not in entity2enum:
                raise ValueError("no such entity: {}. Allowed entities are: DATA_SET, SAMPLE, EXPERIMENT, MATERIAL")
    
            request = {
                "method": "generateCode",
                "params": [
                    self.token,
                    prefix,
                    entity2enum[entity]
                ]
            }
            try:
                return self._post_request(self.as_v1, request)
            except Exception as e:
                raise ValueError("Could not generate a code for {}: {}".format(entity, e))
    
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def gen_permId(self, count=1):
            """ Generate a permId (or many permIds) for a dataSet
            """
    
            request = {
                "method": "createPermIdStrings",
                "params": [
                    self.token,
                    count
                ]
            }
            try:
                return self._post_request(self.as_v3, request)
            except Exception as e:
                raise ValueError("Could not generate a code for {}: {}".format(entity, e))
                
    
    
        def new_person(self, userId, space=None):
            """ creates an openBIS person
            """
    
            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, userIds=None):
    
            """ creates an openBIS person
            """
    
            return Group(self, code=code, description=description, userIds=userIds)
    
        def get_group(self, code, only_data=False):
    
            """ Get an openBIS AuthorizationGroup. Returns a Group object.
    
            ids = [{
                "@type": "as.dto.authorizationgroup.id.AuthorizationGroupPermId",
    
                "permId": code
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            fetchopts = {
                "@type": "as.dto.authorizationgroup.fetchoptions.AuthorizationGroupFetchOptions"
            }
    
            for option in ['roleAssignments', 'users', 'registrator']:
                fetchopts[option] = fetch_option[option]
    
    
            fetchopts['users']['space'] = fetch_option['space']
    
    
            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)
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def get_role_assignments(self, start_with=None, count=None, **search_args):
    
            """ Get the assigned roles for a given group, person or space
            """
    
            entity = 'roleAssignment'
            search_criteria = get_type_for_entity(entity, 'search')
    
            allowed_search_attrs = ['role', 'roleLevel', 'user', 'group', 'person', 'space']
    
    
            sub_crit = []
            for attr in search_args:
                if attr in allowed_search_attrs:
                    if attr == 'space':
                        sub_crit.append(
                            _subcriteria_for_code(search_args[attr], 'space')
                        )
    
                    elif attr in ['user','person']:
    
                        userId = ''
                        if isinstance(search_args[attr], str):
                            userId = search_args[attr]
                        else:
                            userId = search_args[attr].userId
    
                        sub_crit.append(
                            _subcriteria_for_userId(userId)    
                        )
                    elif attr == 'group':
    
                        groupId = ''
                        if isinstance(search_args[attr], str):
                            groupId = search_args[attr]
                        else:
                            groupId = search_args[attr].code
    
                        sub_crit.append(
    
                            _subcriteria_for_permid(groupId, 'authorizationGroup')
    
                        )
                    elif attr == 'role':
                        # TODO
                        raise ValueError("not yet implemented")
                    elif attr == 'roleLevel':
                        # TODO
                        raise ValueError("not yet implemented")
                    else:
                        pass
                else:
    
                    raise ValueError("unknown search argument {}".format(attr))
    
    
            search_criteria['criteria'] = sub_crit
    
    
            method_name = get_method_for_entity(entity, 'search')
            fetchopts = fetch_option[entity]
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            fetchopts['from'] = start_with
            fetchopts['count'] = count
            for option in ['space', 'project', 'user', 'authorizationGroup','registrator']:
    
                fetchopts[option] = fetch_option[option]
    
            request = {
    
                "params": [
                    self.token,
                    search_criteria,
                    fetchopts
                ]
            }
    
    
            attrs=['techId', 'role', 'roleLevel', 'user', 'group', 'space', 'project']
    
            resp = self._post_request(self.as_v3, request)
            if len(resp['objects']) == 0:
    
            else: 
                objects = resp['objects']
                parse_jackson(objects)
                roles = DataFrame(objects)
                roles['techId'] = roles['id'].map(extract_id)
                roles['user'] = roles['user'].map(extract_userId)
                roles['group'] = roles['authorizationGroup'].map(extract_code)
                roles['space'] = roles['space'].map(extract_code)
                roles['project'] = roles['project'].map(extract_code)
    
            return Things(
                openbis_obj = self,
                entity='role_assignment',
    
                identifier_name='techId',
                start_with = start_with,
                count = count,
    
                totalCount = resp.get('totalCount'),
    
        def get_role_assignment(self, techId, only_data=False):
    
            """ Fetches one assigned role by its techId.
            """
    
    
            fetchopts = fetch_option['roleAssignment']
            for option in ['space', 'project', 'user', 'authorizationGroup','registrator']:
    
                fetchopts[option] = fetch_option[option]
    
            request = {
                "method": "getRoleAssignments",
                "params": [
                    self.token,
                    [{
    
                        "techId": str(techId),
    
                        "@type": "as.dto.roleassignment.id.RoleAssignmentTechId"
                    }],
                    fetchopts
                ]
            }
    
            resp = self._post_request(self.as_v3, request)
            if len(resp) == 0:
                raise ValueError("No assigned role found for techId={}".format(techId))
    
            for id in resp:
                data = resp[id]
                parse_jackson(data)
    
                if only_data:
                    return data
                else:
                    return RoleAssignment(self, data=data)
    
    
        def assign_role(self, role, **args):
            """ general method to assign a role to either
                - a person
                - a group
            The scope is either
                - the whole instance
                - a space
                - a project
            """
    
            role = role.upper()
            defs = get_definition_for_entity('roleAssignment')
            if role not in defs['role']:
                raise ValueError("Role should be one of these: {}".format(defs['role']))
    
            userId = None
            groupId = None
            spaceId = None
            projectId = None
    
            for arg in args:
                if arg in ['person', 'group', 'space', 'project']:
                    permId = args[arg] if isinstance(args[arg],str) else args[arg].permId
                    if arg == 'person':
                        userId = {
                            "permId": permId,
                            "@type": "as.dto.person.id.PersonPermId"
                        }
                    elif arg == 'group':
                        groupId = {
                            "permId": permId,
                            "@type": "as.dto.authorizationgroup.id.AuthorizationGroupPermId"
                        }
                    elif arg == 'space':
                        spaceId = {
                            "permId": permId,
                            "@type": "as.dto.space.id.SpacePermId"
                        }
                    elif arg == 'project':
                        projectId = {
                            "permId": permId,
                            "@type": "as.dto.project.id.ProjectPermId"
                        }
    
            request = {
                "method": "createRoleAssignments",
                "params": [
    
                    [ {
                        "role": role,
                        "userId": userId,
                        "authorizationGroupId": groupId,
                        "spaceId": spaceId,
                        "projectId": projectId,
                        "@type": "as.dto.roleassignment.create.RoleAssignmentCreation",
                    } ]
                ]
            }
    
            resp = self._post_request(self.as_v3, request)
            return
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def get_groups(self, start_with=None, count=None, **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']:
                # unfortunately, there aren't many search possibilities yet...
    
                if search_arg in search_args:
    
                    if search_arg == 'code':
                        criteria.append(_criteria_for_code(search_args[search_arg]))
    
    
            search_criteria = get_search_type_for_entity('authorizationGroup')
            search_criteria['criteria'] = criteria
    
            search_criteria['operator'] = 'AND'
    
                    
            fetchopts = fetch_option['authorizationGroup']
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            fetchopts['from'] = start_with
            fetchopts['count'] = count
    
            for option in ['roleAssignments', 'registrator', 'users']:
    
                fetchopts[option] = fetch_option[option]
            request = {
                "method": "searchAuthorizationGroups",
                "params": [
                    self.token,
    
                    fetchopts
                ],
            }
            resp = self._post_request(self.as_v3, request)
    
    
            attrs = ['permId', 'code', 'description', 'users', 'registrator', 'registrationDate', 'modificationDate']
    
            if len(resp['objects']) == 0:
    
                groups = DataFrame(columns=attrs)
            else:
                objects = resp['objects']
                parse_jackson(objects)
                groups = DataFrame(objects)
    
                groups['permId'] = groups['permId'].map(extract_permid)
                groups['registrator'] = groups['registrator'].map(extract_person)
                groups['users'] = groups['users'].map(extract_userId)
                groups['registrationDate'] = groups['registrationDate'].map(format_timestamp)
                groups['modificationDate'] = groups['modificationDate'].map(format_timestamp)
    
            return Things(
                openbis_obj = self,
                entity='group',
                df=groups[attrs],
                identifier_name='permId',
                start_with = start_with,
                count = count,
    
                totalCount = resp.get('totalCount'),
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def get_persons(self, start_with=None, count=None, **search_args):
    
            search_criteria = get_search_criteria('person', **search_args)
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            fetchopts = fetch_option['person']
            fetchopts['from'] = start_with
            fetchopts['count'] = count
    
            for option in ['space']:
                fetchopts[option] = fetch_option[option]
            request = {
                "method": "searchPersons",
                "params": [
                    self.token,
                    search_criteria,
                    fetchopts
                ],
            }
            resp = self._post_request(self.as_v3, request)
    
    
            attrs = ['permId', 'userId', 'firstName', 'lastName', 'email', 'space', 'registrationDate', 'active']
    
            if len(resp['objects']) == 0:
    
                persons = DataFrame(columns=attrs)
            else:
                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)
    
                openbis_obj = self,
                entity='person',
                df=persons[attrs],
                identifier_name='permId',
                start_with = start_with,
                count = count,
    
                totalCount = resp.get('totalCount'),
    
        def get_person(self, userId, only_data=False):
            """ Get a person (user)
            """
             
            ids = [{
                "@type": "as.dto.person.id.PersonPermId",
                "permId": userId
            }]
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            fetchopts = {
                "@type": "as.dto.person.fetchoptions.PersonFetchOptions"
            }
    
                fetchopts[option] = fetch_option[option]
    
            request = {
                "method": "getPersons",
                "params": [
                    self.token,
                    ids,
                    fetchopts,
                ],
            }
            
            resp = self._post_request(self.as_v3, request)
            if len(resp) == 0:
                raise ValueError("No person found!")
    
    
            for permid in resp:
                person = resp[permid]
                parse_jackson(person)
    
                if only_data:
                    return person
                else:
                    return Person(self, data=person)
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def get_spaces(self, code=None, start_with=None, count=None):
    
            """ Get a list of all available spaces (DataFrame object). To create a sample or a
            dataset, you need to specify in which space it should live.
            """
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            method = get_method_for_entity('space', 'search')
    
            search_criteria = _subcriteria_for_code(code, 'space')
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            fetchopts = fetch_option['space']
            fetchopts['from'] = start_with
            fetchopts['count'] = count
    
            request = {
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                "method": method,
                "params": [
                    self.token,
                    search_criteria,
                    fetchopts,
                ],
    
            }
            resp = self._post_request(self.as_v3, request)
    
    
            attrs = ['code', 'description', 'registrationDate', 'modificationDate']
            if len(resp['objects']) == 0:
                spaces = DataFrame(columns=attrs)
            else:
    
                spaces = DataFrame(resp['objects'])
    
                spaces['registrationDate'] = spaces['registrationDate'].map(format_timestamp)
                spaces['modificationDate'] = spaces['modificationDate'].map(format_timestamp)
    
            return Things(
                openbis_obj = self,
                entity = 'space',
                df = spaces[attrs],
                start_with = start_with,
                count = count,
    
                totalCount = resp.get('totalCount'),
    
        def get_space(self, code, only_data=False):
            """ Returns a Space object for a given identifier.
    
            space = not only_data and self._object_cache(entity='space',code=code)
            if space:
                return space
    
    
            fetchopts = {"@type": "as.dto.space.fetchoptions.SpaceFetchOptions"}
    
            for option in ['registrator']:
                fetchopts[option] = fetch_option[option]
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            method = get_method_for_entity('space', 'get')
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                "method": method,
    
                        "@type": "as.dto.space.id.SpacePermId"
                    }],
                    fetchopts
    
            resp = self._post_request(self.as_v3, request)
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            if len(resp) == 0:
    
                raise ValueError("No such space: %s" % code)
    
            for permid in resp:
                if only_data:
                    return resp[permid]
                else:
    
                    space = Space(self, data=resp[permid])
                    if self.use_cache:
                        self._object_cache(entity='space',code=code,value=space)
                    return space
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def get_samples(
    
            self, identifier=None, code=None, permId=None,
    
            space=None, project=None, experiment=None, collection=None, type=None,
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            start_with=None, count=None,
    
            withParents=None, withChildren=None, tags=None, attrs=None, props=None,
            **properties
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        ):
    
            """Returns a DataFrame of all samples for a given space/project/experiment (or any combination)
    
            type         -- sampleType code or object
            space        -- space code or object
            project      -- project code or object
            experiment   -- experiment code or object
            collection   -- same as above
    
            tags         -- only return samples with the specified tags
    
    
            start_with   -- default=None
            count        -- number of samples that should be fetched. default=None.
    
    
            Include in result list
            ----------------------
    
            withParents  -- the list of parent's permIds in a column 'parents'
            withChildren -- the list of children's permIds in a column 'children'
            attrs        -- list of all desired attributes. Examples:
                            space, project, experiment: just return their identifier
                            space.code, project.code, experiment.code
                            registrator.email, registrator.firstName
                            type.generatedCodePrefix
            props        -- list of all desired properties. Returns an empty string if
                            a) property is not present
                            b) property is not defined for this sampleType
    
            if collection is not None:
                experiment = collection
    
    
                crit = _subcriteria_for(identifier, 'sample')
                sub_criteria += crit['criteria']
    
                sub_criteria.append(_subcriteria_for(space, 'space'))
    
                sub_criteria.append(_subcriteria_for(project, 'project'))
    
                sub_criteria.append(_subcriteria_for(experiment, 'experiment'))
    
            if withParents:
                sub_criteria.append(_subcriteria_for(withParents, 'sample', 'Parents'))
            if withChildren:
                sub_criteria.append(_subcriteria_for(withChildren, 'sample', 'Children'))
    
    
            if properties is not None:
                for prop in properties:
    
                    sub_criteria.append(_subcriteria_for_properties(prop, properties[prop], entity='sample'))
    
                sub_criteria.append(_subcriteria_for_code(type, 'sampleType'))
    
            if tags:
                sub_criteria.append(_subcriteria_for_tags(tags))
    
            if code:
                sub_criteria.append(_criteria_for_code(code))
    
                sub_criteria.append(_common_search("as.dto.common.search.PermIdSearchCriteria", permId))
    
            criteria = {
                "criteria": sub_criteria,
                "@type": "as.dto.sample.search.SampleSearchCriteria",
                "operator": "AND"
            }
    
            attrs_fetchoptions = self._get_fetchopts_for_attrs(attrs)
    
            # build the various fetch options
    
            fetchopts = fetch_option['sample']
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            fetchopts['from'] = start_with
            fetchopts['count'] = count
    
            options = ['tags', 'properties', 'attachments', 'space', 'experiment', 'registrator', 'modifier', 'dataSets']
            if self.get_server_information().project_samples_enabled:
                options.append('project')
            for option in options:
                fetchopts[option] = fetch_option[option]
    
            for key in ['parents','children','container','components']:
                fetchopts[key] = {"@type": "as.dto.sample.fetchoptions.SampleFetchOptions"}
    
            if props is not None:
                fetchopts['properties'] = fetch_option['properties']
    
            request = {
                "method": "searchSamples",
    
                "params": [
                    self.token,
                    criteria,
                    fetchopts,
                ],
    
            resp = self._post_request(self.as_v3, request)
    
            samples = []
            parse_jackson(resp)
            for obj in resp['objects']:
                sample = Sample(
                    openbis_obj = self,
                    type = self.get_sample_type(obj['type']['code']),
                    data = obj
    
                samples.append(sample)
    
            return self._sample_list_for_response(
                response=resp['objects'],
                attrs=attrs,
                props=props,
                start_with=start_with,
                count=count,
                totalCount=resp['totalCount'],
                objects=samples
            )
    
        get_objects = get_samples # Alias
    
    
        def _get_fetchopts_for_attrs(self, attrs=None):
            if attrs is None:
    
    
            fetchopts = []
            for attr in attrs:
                if attr.startswith('space'): fetchopts.append('space')
                if attr.startswith('project'): fetchopts.append('project')
                if attr.startswith('experiment'): fetchopts.append('experiment')
    
                if attr.startswith('sample'): fetchopts.append('sample')
                if attr.startswith('registrator'): fetchopts.append('registrator')
                if attr.startswith('modifier'): fetchopts.append('modifier')
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def get_experiments(
    
            self, code=None, permId=None, type=None, space=None, project=None,
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            start_with=None, count=None,
    
            tags=None, is_finished=None, attrs=None, props=None, **properties
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        ):
    
            """Returns a DataFrame of all samples for a given space/project (or any combination)