Skip to content
Snippets Groups Projects
pybis.py 99 KiB
Newer Older
  • Learn to ignore specific revisions
  •         request = {
            "method": "getExperiments",
                "params": [ 
    
                ],
            } 
            resp = self._post_request(self.as_v3, request)
            if len(resp) == 0:
                raise ValueError("No such experiment: %s" % expId)
    
            return Experiment(self, 
                self.get_experiment_type(resp[expId]["type"]["code"]), 
                resp[expId]
            )
    
        def new_experiment(self, type, **kwargs):
            """ Creates a new experiment of a given experiment type.
            """
            return Experiment(self, self.get_experiment_type(type), None, **kwargs)
    
    
    
        def update_experiment(self, experimentId, properties=None, tagIds=None, attachments=None):
            params = {
                "experimentId": {
                    "permId": experimentId,
                    "@type": "as.dto.experiment.id.ExperimentPermId"
                },
                "@type": "as.dto.experiment.update.ExperimentUpdate"
            }
            if properties is not None:
                params["properties"]= properties
            if tagIds is not None:
                params["tagIds"] = tagIds
            if attachments is not None:
                params["attachments"] = attachments
    
            request = {
                "method": "updateExperiments",
                "params": [
                    self.token,
                    [ params ]
                ]
            }
            self._post_request(self.as_v3, request)
    
    
    
        def create_sample(self, space_ident, code, type, 
            project_ident=None, experiment_ident=None, properties=None, attachments=None, tags=None):
    
            tagIds = _create_tagIds(tags)
            typeId = _create_typeId(type)
            projectId = _create_projectId(project_ident)
            experimentId = _create_experimentId(experiment_ident)
    
            if properties is None:
                properties = {}
            
            request = {
                "method": "createSamples",
                "params": [
                    self.token,
                    [
                        {
                            "properties": properties,
                            "code": code,
                            "typeId" : typeId,
                            "projectId": projectId,
                            "experimentId": experimentId,
                            "tagIds": tagIds,
                            "attachments": attachments,
    
                            "@type": "as.dto.sample.create.SampleCreation",
    
                        }
                    ]
                ],
            }
            resp = self._post_request(self.as_v3, request)
            return self.get_sample(resp[0]['permId'])
    
    
    
        def update_sample(self, sampleId, space=None, project=None, experiment=None,
            parents=None, children=None, components=None, properties=None, tagIds=None, attachments=None):
    
            params = {
                "sampleId": {
                    "permId": sampleId,
                    "@type": "as.dto.sample.id.SamplePermId"
                },
                "@type": "as.dto.sample.update.SampleUpdate"
            }
    
            if space is not None:
                params['spaceId'] = space
            if project is not None:
                params['projectId'] = project
    
            if properties is not None:
                params["properties"]= properties
            if tagIds is not None:
                params["tagIds"] = tagIds
            if attachments is not None:
                params["attachments"] = attachments
    
            request = {
                "method": "updateSamples",
                "params": [
                    self.token,
                    [ params ]
                ]
            }
            self._post_request(self.as_v3, request)
    
    
    
        def delete_entity(self, entity, permid, reason):
    
            """Deletes Spaces, Projects, Experiments, Samples and DataSets
            """
    
    
            entity_type = "as.dto.{}.id.{}PermId".format(entity.lower(), entity.capitalize())
    
                "method": "delete" + entity.capitalize()  + 's',
    
                "params": [
                    self.token,
                    [
                        {
                            "permId": permid,
                            "@type": entity_type
                        }
                    ],
                    {
                        "reason": reason,
    
                        "@type": "as.dto.{}.delete.{}DeletionOptions".format(entity.lower(), entity.capitalize())
    
                    }
                ]
            }
            self._post_request(self.as_v3, request)
    
    
        def get_deletions(self):
            request = {
                "method": "searchDeletions",
                "params": [
                    self.token,
                    {},
                    {
                        "deletedObjects": {
                            "@type": "as.dto.deletion.fetchoptions.DeletedObjectFetchOptions"
                        }
                    }
                ]
            }
            resp = self._post_request(self.as_v3, request)
            objects = resp['objects']
            parse_jackson(objects)
    
            new_objs = [] 
            for value in objects:
                del_objs = extract_deletion(value)
                if len(del_objs) > 0:
                    new_objs.append(*del_objs)
    
            return DataFrame(new_objs)
    
        def new_project(self, space, code, description=None, **kwargs):
            return Project(self, None, space=space, code=code, description=description, **kwargs)
    
        def _gen_fetchoptions(self, options):
            fo = {}
            for option in options:
                fo[option] = fetch_option[option]
            return fo
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            options = ['space', 'registrator', 'modifier', 'attachments']
    
            if is_identifier(projectId):
                request = self._create_get_request(
                    'getProjects', 'project', projectId, options
                )
                resp = self._post_request(self.as_v3, request)
                return Project(self, resp[projectId])
    
            else:
                search_criteria = _gen_search_criteria({
                    'project': 'Project',
                    'operator': 'AND',
                    'code': projectId
                })
                fo = self._gen_fetchoptions(options)
                request = {
                    "method": "searchProjects",
                    "params": [self.token, search_criteria, fo]
                }
                resp = self._post_request(self.as_v3, request)
                return Project(self, resp['objects'][0])
                
    
        def get_projects(self, space=None):
            """ Get a list of all available projects (DataFrame object).
            """
    
            sub_criteria = []
            if space:
    
                sub_criteria.append(_subcriteria_for_code(space, 'space'))
    
    
            criteria = {
                "criteria": sub_criteria,
                "@type": "as.dto.project.search.ProjectSearchCriteria",
                "operator": "AND"
            }
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            fetchopts = { "@type": "as.dto.project.fetchoptions.ProjectFetchOptions" }
            for option in ['registrator', 'modifier', 'leader' ]:
                fetchopts[option] = fetch_option[option]
    
    
            request = {
                "method": "searchProjects",
                "params": [ self.token, 
                    criteria,
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                    fetchopts,
    
                ],
            }
    
            resp = self._post_request(self.as_v3, request)
            if resp is not None:
                objects = resp['objects']
    
    
                projects = DataFrame(objects)
                if len(projects) is 0:
                    raise ValueError("No projects found!")
    
                projects['registrationDate']= projects['registrationDate'].map(format_timestamp)
                projects['modificationDate']= projects['modificationDate'].map(format_timestamp)
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                projects['leader'] = projects['leader'].map(extract_person)
    
                projects['registrator'] = projects['registrator'].map(extract_person)
                projects['modifier'] = projects['modifier'].map(extract_person)
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                projects['permId'] = projects['permId'].map(extract_permid)
    
                projects['identifier'] = projects['identifier'].map(extract_identifier)
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                pros=projects[['identifier', 'permId', 'leader', 'registrator', 'registrationDate', 
                                'modifier', 'modificationDate']]
    
                return Things(self, 'project', pros, 'identifier')
    
            else:
                raise ValueError("No projects found!")
    
    
        def _create_get_request(self, method_name, entity, permids, options):
    
    
            if not isinstance(permids, list):
                permids = [permids]
    
    
            type = "as.dto.{}.id.{}".format(entity.lower(), entity.capitalize())
    
            search_params = []
            for permid in permids:
                # decide if we got a permId or an identifier
                match = re.match('/', permid)
                if match:
                    search_params.append(
                        { "identifier" : permid, "@type" : type + 'Identifier' }
                    )
                else: 
                    search_params.append(
                        { "permId" : permid, "@type": type + 'PermId' }
                    )
    
            fo = {}
            for option in options:
                fo[option] = fetch_option[option]
    
            request = {
                "method": method_name,
                "params": [
                    self.token,
                    search_params,
                    fo
                ],
            }
            return request
    
    
    
        def get_terms(self, vocabulary=None):
    
            """ Returns information about vocabulary, including its controlled vocabulary
    
            search_request = {}
            if vocabulary is not None:
    
                search_request = _gen_search_criteria( { 
    
                    "vocabulary": "VocabularyTerm", 
                    "criteria" : [{
                        "vocabulary": "Vocabulary",
                        "code": vocabulary
                    }]
                })
    
            fetch_options = {
                "vocabulary" : { "@type" : "as.dto.vocabulary.fetchoptions.VocabularyFetchOptions" },
                "@type": "as.dto.vocabulary.fetchoptions.VocabularyTermFetchOptions"
            }
    
            request = {
                "method": "searchVocabularyTerms",
    
                "params": [ self.token, search_request, fetch_options ]
    
            }
            resp = self._post_request(self.as_v3, request)
    
    
        def get_tags(self):
            """ Returns a DataFrame of all 
            """
            request = {
                "method": "searchTags",
                "params": [ self.token, {}, {} ]
            }
            resp = self._post_request(self.as_v3, request)
    
            objects = DataFrame(resp['objects'])
            objects['registrationDate'] = objects['registrationDate'].map(format_timestamp)
            return objects[['code', 'registrationDate']]
    
    
    
        def get_sample_types(self, type=None):
            """ Returns a list of all available sample types
            """
            return self._get_types_of(
                "searchSampleTypes",
                "Sample",
                type, 
                ["generatedCodePrefix"]
            )
    
        def get_sample_type(self, type):
            try:
                return self._get_types_of(
                    "searchSampleTypes", 
                    "Sample",
                    type,
                    ["generatedCodePrefix"]
                )
            except Exception:
                raise ValueError("no such sample type: {}".format(type))
    
    
        def get_experiment_types(self, type=None):
            """ Returns a list of all available experiment types
            """
            return self._get_types_of(
                "searchExperimentTypes", 
                "Experiment", 
                type
            )
    
        def get_experiment_type(self, type):
            try:    
                return self._get_types_of(
                    "searchExperimentTypes", 
                    "Experiment", 
                    type
                )
            except Exception:
                raise ValueError("No such experiment type: {}".format(type))
    
    
        def get_material_types(self, type=None):
            """ Returns a list of all available material types
            """
            return self._get_types_of("searchMaterialTypes", "Material", type)
    
        def get_material_type(self, type):
            try:
                return self._get_types_of("searchMaterialTypes", "Material", type)
            except Exception:
                raise ValueError("No such material type: {}".format(type))
    
    
        def get_dataset_types(self, type=None):
            """ Returns a list (DataFrame object) of all currently available dataset types
            """
    
            return self._get_types_of("searchDataSetTypes", "DataSet", type, ['kind'] )
    
    
        def get_dataset_type(self, type):
            try:
                return self._get_types_of("searchDataSetTypes", "DataSet", type, ['kind'])
            except Exception:
                raise ValueError("No such dataSet type: {}".format(type))
    
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def _get_types_of(self, method_name, entity, type_name=None, additional_attributes=None):
    
            """ Returns a list of all available types of an entity.
            If the name of the entity-type is given, it returns a PropertyAssignments object
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            if additional_attributes is None:
                additional_attributes = []
    
            attributes = ['code', 'description'] + additional_attributes + ['modificationDate']
    
            if type_name is not None:
                search_request = _gen_search_criteria({
    
                    entity.lower(): entity + "Type",
    
                    "code": type_name
    
                    "@type": "as.dto.{}.fetchoptions.{}TypeFetchOptions".format(
    
                        entity.lower(), entity
    
                fetch_options['propertyAssignments'] = fetch_option['propertyAssignments']
    
            request = {
                "method": method_name,
    
                "params": [ self.token, search_request, fetch_options ],
    
            }
            resp = self._post_request(self.as_v3, request)
    
            if type_name is not None and len(resp['objects']) == 1:
    
                return PropertyAssignments(self, resp['objects'][0])
    
            if len(resp['objects']) >= 1:
                types = DataFrame(resp['objects'])
    
                types['modificationDate'] = types['modificationDate'].map(format_timestamp)
    
                return Things(self, entity.lower()+'_type', types[attributes])
    
                return types[attributes]
    
                raise ValueError("Nothing found!")
    
            """ checks whether a session is still active. Returns true or false.
            """
    
            This method is useful to check if a token is still valid or if it has timed out,
            requiring the user to login again.
    
            :return: Return True if the token is valid, False if it is not valid.
            """
    
            if token is None:
                token = self.token
            
            if token is None:
    
            request = {
                "method": "isSessionActive",
    
            resp = self._post_request(self.as_v1, request)
    
            return resp
    
        def get_dataset(self, permid):
    
            """fetch a dataset and some metadata attached to it:
            - properties
            - sample
            - parents
            - children
            - containers
            - dataStore
            - physicalData
            - linkedData
            :return: a DataSet object
            """
    
            criteria = [{
                "permId": permid,
                "@type": "as.dto.dataset.id.DataSetPermId"
            }]
    
            fetchopts = {
                "parents":      { "@type": "as.dto.dataset.fetchoptions.DataSetFetchOptions" },
                "children":     { "@type": "as.dto.dataset.fetchoptions.DataSetFetchOptions" },
                "containers":   { "@type": "as.dto.dataset.fetchoptions.DataSetFetchOptions" },
    
                "type":         { "@type": "as.dto.dataset.fetchoptions.DataSetTypeFetchOptions" },
    
            }
    
            for option in ['tags', 'properties', 'dataStore', 'physicalData', 'linkedData', 
                           'experiment', 'sample']:
                fetchopts[option] = fetch_option[option]
    
            request = {
    
                "method": "getDataSets",
    
            resp = self._post_request(self.as_v3, request)
    
            if resp is None or len(resp) == 0:
                raise ValueError('no such dataset found: '+permid)
    
            if resp is not None:
                for permid in resp:
    
                    #return resp[permid]
                    return DataSet(self, self.get_dataset_type(resp[permid]["type"]["code"]), resp[permid])
    
        def get_sample(self, sample_ident, only_data=False, withAttachments=False):
    
            """Retrieve metadata for the sample.
            Get metadata for the sample and any directly connected parents of the sample to allow access
            to the same information visible in the ELN UI. The metadata will be on the file system.
            :param sample_identifiers: A list of sample identifiers to retrieve.
            """
    
            fetchopts = { "type": { "@type": "as.dto.sample.fetchoptions.SampleTypeFetchOptions" } }
    
    
            search_request = search_request_for_identifier(sample_ident, 'sample')
    
    
            for option in ['tags', 'properties', 'attachments', 'space', 'experiment', 'registrator', 'dataSets']:
                fetchopts[option] = fetch_option[option]
    
            if withAttachments:
                fetchopts['attachments'] = fetch_option['attachmentsWithContent']
    
    
            #fetchopts["parents"]  = { "@type": "as.dto.sample.fetchoptions.SampleFetchOptions" }
            #fetchopts["children"] = { "@type": "as.dto.sample.fetchoptions.SampleFetchOptions" }
    
    
            sample_request = {
                "method": "getSamples",
                "params": [
                    self.token,
    
            resp = self._post_request(self.as_v3, sample_request)
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            if resp is None or len(resp) == 0:
                raise ValueError('no such sample found: '+sample_ident)
            else:
    
                for sample_ident in resp:
    
                    if only_data:
                        return resp[sample_ident]
                    else:
                        return Sample(self, self.get_sample_type(resp[sample_ident]["type"]["code"]), resp[sample_ident])
    
        def new_space(self, name, description=None):
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            """ Creates a new space in the openBIS instance.
    
            request = {
                "method": "createSpaces",
                "params": [
                    self.token,
                    [ {
                        "code": name,
                        "description": description,
                        "@type": "as.dto.space.create.SpaceCreation"
                    } ]
                ],
            }
    
            resp = self._post_request(self.as_v3, request)
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            return self.get_space(name)
    
        def new_analysis(self, name, description=None, sample=None, dss_code=None, result_files=None,
    
            """ An analysis contains the Jupyter notebook file(s) and some result files.
                Technically this method involves uploading files to the session workspace
                and activating the dropbox aka dataset ingestion service "jupyter-uploader-api"
    
            """
    
            if dss_code is None:
                dss_code = self.get_datastores()['code'][0]
    
            # if a sample identifier was given, use it as a string.
            # if a sample object was given, take its identifier
    
                if (is_identifier(sample)):
                    sampleId = { 
                        "identifier": sample,
                        "@type": "as.dto.sample.id.SampleIdentifier"
                    }
                else:
                    sampleId = { 
                        "permId": sample,
                        "@type": "as.dto.sample.id.SamplePermId"
                    }
    
                sampleId = { 
                    "identifier": sample.identifier,
                    "@type": "as.dto.sample.id.SampleIdentifier"
                }
    
            parentIds = []
            if parents is not None:
                if not isinstance(parents, list):
                    parants = [parents]
                for parent in parents:
                    parentIds.append(parent.permId)
    
            datastore_url = self._get_dss_url(dss_code)
    
            # upload the files
    
            data_sets = []
            if notebook_files is not None:
                notebooks_folder = os.path.join(folder, 'notebook_files')
                self.upload_files(
                    datastore_url = datastore_url,
                    files=notebook_files,
                    folder= notebooks_folder, 
                    wait_until_finished=True
                )
                data_sets.append({
                    "dataSetType" : "JUPYTER_NOTEBOOk",
                    "sessionWorkspaceFolder": notebooks_folder,
                    "fileNames" : notebook_files,
                    "properties" : {}
                })
            if result_files is not None:
                results_folder = os.path.join(folder, 'result_files')
                self.upload_files(
                    datastore_url = datastore_url,
                    files=result_files,
                    folder=results_folder,
                    wait_until_finished=True
                )
                data_sets.append({
                    "dataSetType" : "JUPYTER_RESULT",
                    "sessionWorkspaceFolder" : results_folder,
                    "fileNames" : result_files,
                    "properties" : {}
                })
    
    
            # register the files in openBIS
    
            request = {
              "method": "createReportFromAggregationService",
              "params": [
                self.token,
                dss_code,
    
                    "sample" : { "identifier" : sampleId['identifier'] },
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                    "containers" : [ {
                        "dataSetType" : "JUPYTER_CONTAINER",
                        "properties" : {
                            "NAME" : name,
                            "DESCRIPTION" : description
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                    } ],
    
            
            resp = self._post_request(self.reg_v1, request)
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            try:
                if resp['rows'][0][0]['value'] == 'OK':
                    return resp['rows'][0][1]['value']
            except:
                return resp
    
        def new_sample(self, type, **kwargs):
            """ Creates a new sample of a given sample type.
    
            return Sample(self, self.get_sample_type(type), None, **kwargs)
    
        def new_dataset(self, type, **kwargs):
            """ Creates a new dataset of a given sample type.
            """
            return DataSet(self, self.get_dataset_type(type.upper()), None, **kwargs)
    
    
    
        def _get_dss_url(self, dss_code=None):
            """ internal method to get the downloadURL of a datastore.
            """
    
            dss = self.get_datastores()
            if dss_code is None:
                return dss['downloadUrl'][0]
    
                return dss[dss['code'] == dss_code]['downloadUrl'][0]
    
        def upload_files(self, datastore_url=None, files=None, folder=None, wait_until_finished=False):
    
                datastore_url = self._get_dss_url()
    
            if files is None:
                raise ValueError("Please provide a filename.")
    
    
            if folder is None:
                # create a unique foldername
                folder = time.strftime('%Y-%m-%d_%H-%M-%S')
    
    
            if isinstance(files, str):
                files = [files]
    
            self.files = files
            self.startByte = 0
            self.endByte   = 0
        
            # define a queue to handle the upload threads
            queue = DataSetUploadQueue()
    
            real_files = []
            for filename in files:
                if os.path.isdir(filename):
                    real_files.extend([os.path.join(dp, f) for dp, dn, fn in os.walk(os.path.expanduser(filename)) for f in fn])
                else:
                    real_files.append(os.path.join(filename))
    
            # compose the upload-URL and put URL and filename in the upload queue 
            for filename in real_files:
                file_in_wsp = os.path.join(folder, filename)
    
                    datastore_url + '/session_workspace_file_upload'
    
                    + '?filename=' + os.path.join(folder,filename)
                    + '&id=1'
                    + '&startByte=0&endByte=0'
                    + '&sessionID=' + self.token
                )
                queue.put([upload_url, filename, self.verify_certificates])
    
            # wait until all files have uploaded
            if wait_until_finished:
                queue.join()
    
            # return files with full path in session workspace
    
    class DataSetUploadQueue():
    
       
        def __init__(self, workers=20):
            # maximum files to be uploaded at once
            self.upload_queue = Queue()
    
            # define number of threads and start them
            for t in range(workers):
                t = Thread(target=self.upload_file)
                t.daemon = True
                t.start()
    
    
        def put(self, things):
            """ expects a list [url, filename] which is put into the upload queue
            """
            self.upload_queue.put(things)
    
    
        def join(self):
            """ needs to be called if you want to wait for all uploads to be finished
            """
            self.upload_queue.join()
    
    
        def upload_file(self):
            while True:
                # get the next item in the queue
                upload_url, filename, verify_certificates = self.upload_queue.get()
    
    
                filesize = os.path.getsize(filename)
    
    
                # upload the file to our DSS session workspace
                with open(filename, 'rb') as f:
                    resp = requests.post(upload_url, data=f, verify=verify_certificates)
                    resp.raise_for_status()
    
                    data = resp.json()
                    assert filesize == int(data['size'])
    
    
                # Tell the queue that we are done
                self.upload_queue.task_done()
    
    class DataSetDownloadQueue():
    
        
        def __init__(self, workers=20):
            # maximum files to be downloaded at once
            self.download_queue = Queue()
    
            # define number of threads
            for t in range(workers):
                t = Thread(target=self.download_file)
                t.daemon = True
                t.start()
    
    
        def put(self, things):
            """ expects a list [url, filename] which is put into the download queue
            """
            self.download_queue.put(things)
    
    
        def join(self):
            """ needs to be called if you want to wait for all downloads to be finished
            """
            self.download_queue.join()
    
    
        def download_file(self):
            while True:
    
                url, filename, file_size, verify_certificates = self.download_queue.get()
    
                # create the necessary directory structure if they don't exist yet
                os.makedirs(os.path.dirname(filename), exist_ok=True)
    
                # request the file in streaming mode
    
                r = requests.get(url, stream=True, verify=verify_certificates)
    
                with open(filename, 'wb') as f:
                    for chunk in r.iter_content(chunk_size=1024): 
                        if chunk: # filter out keep-alive new chunks
                            f.write(chunk)
    
    
                assert os.path.getsize(filename) == int(file_size)
    
    class OpenBisObject():
    
        def __init__(self, openbis_obj, type, data=None, **kwargs):
            self.__dict__['openbis'] = openbis_obj
            self.__dict__['type'] = type
            self.__dict__['p'] = PropertyHolder(openbis_obj, type)
            self.__dict__['a'] = AttrHolder(openbis_obj, 'DataSet', type)
    
            # existing OpenBIS object
            if data is not None:
                self._set_data(data)
    
            if kwargs is not None:
                for key in kwargs:
                    setattr(self, key, kwargs[key])
    
        def __eq__(self, other):
            return str(self) == str(other)
    
        def __ne__(self, other):
            return str(self) != str(other)
    
        def _set_data(self, data):
                # assign the attribute data to self.a by calling it 
                # (invoking the AttrHolder.__call__ function)
                self.a(data)
                self.__dict__['data'] = data
    
                # put the properties in the self.p namespace (without checking them)
                for key, value in data['properties'].items():
                    self.p.__dict__[key.lower()] = value
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        @property
        def space(self):
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                return self.openbis.get_space(self._space['permId'])
    
            except Exception:
                pass
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        @property
        def project(self):
    
            try: 
                return self.openbis.get_project(self._project['identifier'])
            except Exception:
                pass
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        @property
        def experiment(self):
    
            try: 
                return self.openbis.get_experiment(self._experiment['identifier'])
            except Exception:
                pass
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        @property
        def sample(self):
    
            try:
                return self.openbis.get_sample(self._sample['permId']['permId'])
            except Exception:
                pass
    
        def __getattr__(self, name):
            return getattr(self.__dict__['a'], name)
    
        def __setattr__(self, name, value):
            if name in ['set_properties', 'set_tags', 'add_tags']:
                raise ValueError("These are methods which should not be overwritten")
    
            setattr(self.__dict__['a'], name, value)
    
            """Print all the assigned attributes (identifier, tags, etc.) in a nicely formatted table. See
            AttributeHolder class.
    
            return self.a._repr_html_()
    
        def __repr__(self):
            """same thing as _repr_html_() but for IPython
            """
            return self.a.__repr__()
    
    
    
    class DataSet(OpenBisObject):
        """ DataSet are openBIS objects that contain the actual files.
        """
    
        def __init__(self, openbis_obj, type, data=None, **kwargs):
            super(DataSet, self).__init__(openbis_obj, type, data, **kwargs)
    
            # existing DataSet
            if data is not None:
                if data['physicalData'] is None:
                    self.__dict__['shareId'] = None
                    self.__dict__['location'] = None
                else:
                    self.__dict__['shareId'] = data['physicalData']['shareId']
                    self.__dict__['location'] = data['physicalData']['location']
    
        def __str__(self):
            return self.data['code']
    
        def __dir__(self):
    
            return [
                'props', 'get_parents()', 'get_children()',
                'sample', 'experiment', 
                'tags', 'set_tags()', 'add_tags()', 'del_tags()',
                'add_attachment()', 'get_attachments()', 'download_attachments()',
                'data'
            ]
    
    
        @property
        def type(self):
            return self.__dict__['type']
    
        @type.setter
        def type(self, type_name):
                dataset_type = self.openbis.get_dataset_type(type_name.upper())
                self.p.__dict__['_type'] = dataset_type
                self.a.__dict__['_type'] = dataset_type
    
    
        def set_properties(self, properties):
            self.openbis.update_dataset(self.permId, properties=properties)
    
        def download(self, files=None, destination=None, wait_until_finished=True, workers=10):
    
            """ download the actual files and put them by default in the following folder:
    
            __current_dir__/destination/dataset_permId/
    
            If no files are specified, all files of a given dataset are downloaded.
    
            If no destination is specified, the hostname is chosen instead.
    
            Files are usually downloaded in parallel, using 10 workers by default. If you want to wait until
            all the files are downloaded, set the wait_until_finished option to True.
    
            if files == None:
                files = self.file_list()
            elif isinstance(files, str):
                files = [files]
    
    
            if destination is None:
                destination = self.openbis.hostname
    
    
            base_url = self.data['dataStore']['downloadUrl'] + '/datastore_server/' + self.permId + '/'
    
            queue = DataSetDownloadQueue(workers=workers)
    
            # get file list and start download
    
                file_info = self.get_file_list(start_folder=filename)
                file_size = file_info[0]['fileSize']
    
                download_url = base_url + filename + '?sessionID=' + self.openbis.token 
    
                filename_dest = os.path.join(destination, self.permId, filename)
                queue.put([download_url, filename_dest, file_size, self.openbis.verify_certificates])
    
    
            # wait until all files have downloaded
            if wait_until_finished:
                queue.join()
    
    
            print("Files downloaded to: %s" % os.path.join(destination, self.permId))
    
            return self.openbis.get_datasets(withChildren=self.permId)
    
            return self.openbis.get_datasets(withParents=self.permId)
    
            """returns the list of files including their directories as an array of strings. Just folders are not
            listed.
            """
    
            files = []
            for file in self.get_file_list(recursive=True):
                if file['isDirectory']: