Skip to content
Snippets Groups Projects
pybis.py 97 KiB
Newer Older
  • Learn to ignore specific revisions
  •             files = self.file_list()
            elif isinstance(files, str):
                files = [files]
    
    
            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 = os.path.join(self.openbis.hostname, self.permid, filename)
    
                queue.put([download_url, filename, 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(self.openbis.hostname, self.permid))
    
            return self.openbis.get_datasets(withChildren=self.permid)
    
            return self.openbis.get_datasets(withParents=self.permid)
    
    
    
        def file_list(self):
            files = []
            for file in self.get_file_list(recursive=True):
                if file['isDirectory']:
                    pass
                else:
                    files.append(file['pathInDataSet'])
            return files
    
    
        def get_files(self, start_folder='/'):
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            """ Returns a DataFrame of all files in this dataset
            """
    
            def createRelativePath(pathInDataSet):
                if self.shareId is None:
                    return ''
                else:
                    return os.path.join(self.shareId, self.location, pathInDataSet)
                
    
            files = self.get_file_list(start_folder=start_folder)
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            df = DataFrame(files)
            df['relativePath'] = df['pathInDataSet'].map(createRelativePath)
    
            df['crc32Checksum'] = df['crc32Checksum'].fillna(0.0).astype(int).map(signed_to_unsigned)
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            return df[['isDirectory', 'pathInDataSet', 'fileSize', 'crc32Checksum']]
    
        def get_file_list(self, recursive=True, start_folder="/"):
    
            """ Lists all files of a given dataset. You can specifiy a start_folder other than "/".
            By default, all directories and their containing files are listed recursively. You can
            turn off this option by setting recursive=False.
            """
    
            request = {
                "method" : "listFilesForDataSet",
                "params" : [ 
                    self.openbis.token,
                    self.permid, 
    
                    start_folder,
    
            resp = requests.post(
    
                self.data["dataStore"]["downloadUrl"] + '/datastore_server/rmi-dss-api-v1.json',
    
                json.dumps(request), 
                verify=self.openbis.verify_certificates
            )
    
                    raise ValueError('Error from openBIS: ' + data['error'] )
    
                elif 'result' in data:
                    return data['result']
    
                    raise ValueError('request to openBIS did not return either result nor error')
    
                raise ValueError('internal error while performing post request')
    
    class Vocabulary():
    
        def __init__(self, data):
            self.data = data
    
    
        @property
        def terms_kv(self):
             return [ 
                {voc["code"]:voc["label"] }
                for voc 
                in sorted(self.data['objects'], key=lambda v: v["ordinal"]) 
            ]
    
        @property
        def terms(self):
             return [ 
                voc["code"]
                for voc 
                in sorted(self.data['objects'], key=lambda v: v["ordinal"]) 
            ]
    
    
        def _repr_html_(self):
            html = """
                <table border="1" class="dataframe">
                <thead>
                    <tr style="text-align: right;">
                    <th>vocabulary term</th>
                    <th>label</th>
                    <th>description</th>
                    <th>vocabulary</th>
                    </tr>
                </thead>
                <tbody>
            """
    
            for voc in sorted(
                self.data['objects'], 
                key=lambda v: (v["permId"]["vocabularyCode"], v["ordinal"])
            ):
    
                html += "<tr> <td>{}</td> <td>{}</td> <td>{}</td> <td>{}</td> </tr>".format(
                    voc['code'],
                    voc['label'],
                    voc['description'],
                    voc['permId']['vocabularyCode']
                )
    
            html += """
                </tbody>
                </table>
            """
            return html
    
    
    
    class PropertyHolder():
    
        def __init__(self, openbis_obj, type):
            self.__dict__['_openbis'] = openbis_obj
            self.__dict__['_type'] = type
            self.__dict__['_property_names'] = []
            for prop in type.data['propertyAssignments']:
                self._property_names.append(prop['propertyType']['code'].lower())
    
    
        def _get_terms(self, vocabulary):
            return self._openbis.get_terms(vocabulary)
    
        def _all_props(self):
            props = {}
            for code in self._type.codes():
                props[code] = getattr(self, code)
            return props
    
        def __getattr__(self, name):
            if name.endswith('_'):
                name = name.rstrip('_')
                property_type = self._type.prop[name]['propertyType']
    
                if property_type['dataType'] == 'CONTROLLEDVOCABULARY':
    
                    return self._openbis.get_terms(name)
                else:
    
                    return { property_type["label"] : property_type["dataType"]}
    
    
        def __setattr__(self, name, value):
            if name not in self._property_names:
                raise KeyError("No such property: {}".format(name)) 
    
            property_type = self._type.prop[name]['propertyType']
    
            data_type = property_type['dataType']
    
            if data_type == 'CONTROLLEDVOCABULARY':
    
                voc = self._openbis.get_terms(name)
                if value not in voc.terms:
                    raise ValueError("Value must be one of these terms: " + ", ".join(voc.terms))
    
            elif data_type in ('INTEGER', 'BOOLEAN', 'VARCHAR'):
                if not check_datatype(data_type, value):
                    raise ValueError("Value must be of type {}".format(data_type))
            self.__dict__[name] = value
    
        def __dir__(self):
            return self._property_names
    
        def _repr_html_(self):
    
            def nvl(val, string=''):
                if val is None:
                    return string
                elif val == 'true':
                    return True
                elif val == 'false':
                    return False
                return val
    
            html = """
                <table border="1" class="dataframe">
                <thead>
                    <tr style="text-align: right;">
                    <th>property</th>
                    <th>value</th>
                    </tr>
                </thead>
                <tbody>
            """
    
            for prop in self._property_names:
    
                #value = ''
                #try:
                #    value = getattr(self, prop)
                #except Exception:
                #    pass
    
    
                html += "<tr> <td>{}</td> <td>{}</td> </tr>".format(
    
        """ General class for both samples and experiments that hold all common attributes, such as:
        - space
    
        - experiment (sample)
        - samples (experiment)
        - parents (sample, dataset)
        - children (sample, dataset)
    
        def __init__(self, openbis_obj, entity, type=None):
    
            self.__dict__['_openbis'] = openbis_obj
    
            self.__dict__['_entity'] = entity
    
            if type is not None:
                self.__dict__['_type_obj'] = type
    
            self.__dict__['_allowed_attrs'] = _definitions(entity)['attrs']
    
            self.__dict__['_identifier'] = None
            self.__dict__['_is_new'] = True
    
    
        def __call__(self, data):
            self.__dict__['_is_new'] = False
    
            for attr in self._allowed_attrs:
                if attr in ["code","permId","identifier","type",
                            "container","components","attachments"]:
                    self.__dict__['_'+attr] = data.get(attr, None)
    
    
                elif attr in ["space"]:
    
                    d =  data.get(attr, None)
                    if d is not None:
                        d = d['permId']
                    self.__dict__['_'+attr] = d
    
    
                elif attr in ["experiment", "project"]:
    
                    d =  data.get(attr, None)
                    if d is not None:
                        d = d['identifier']
                    self.__dict__['_'+attr] = d
    
    
                elif attr in ["parents","children","samples"]:
    
                    self.__dict__['_'+attr] = []
                    for item in data[attr]:
                        self.__dict__['_'+attr].append(item['identifier'])
    
    
                elif attr in ["tags"]:
    
                    self.__dict__['_'+attr] = []
                    for item in data[attr]:
                        self.__dict__['_'+attr].append({
                            "code": item['code'],
                            "@type": "as.dto.tag.id.TagCode"
                        })
    
                elif attr in ["attachments"]:
                    pass
    
                else:
                    self.__dict__['_'+attr] = data.get(attr, None)
    
            defs = _definitions(self._entity)
    
            attr2ids = _definitions('attr2ids')
            ids2type = _definitions('ids2type')
    
            request = {}
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            # look at all attributes available for that entity
    
            for attr in self._allowed_attrs:
                # these attributes cannot be changed (or not directly)
                if attr in ["code", "permId", "identifier", "type", "attachments"]:
                    continue
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                    if self._is_new:
                        # handle multivalue attributes (parents, children, tags etc.)
                        if attr in defs['multi']:
                            items = self.__dict__.get('_'+attr, [])
                            if items == None:
                                items = []
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                        else:
    
                            request[attr2ids[attr]] = self.__dict__.get('_'+attr, None)
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                        # handle multivalue attributes (parents, children, tags etc.)
                        # we only cover the Set mechanism, which means we always update all items in a
                        # list
                        if attr in defs['multi']:
                            items = self.__dict__.get('_'+attr, [])
                            if items == None:
                                items = []
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                                "actions": [
                                    {
                                        "items": items,
                                        "@type": "as.dto.common.update.ListUpdateActionSet",
                                    }
                                ],
                                "@type": "as.dto.common.update.IdListUpdateValue"
                            }
                        else:
                            # handle single attribut4es (space, experiment, project, container, etc.)
                            value =  self.__dict__.get('_'+attr, {})
                            if value is None:
                                pass
                            else:
                                isModified=False
                                if 'isModified' in value:
                                    isModified=True
                                    del value['isModified']
                                
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                                   "@type": "as.dto.common.update.FieldUpdateValue",
                                   "isModified": isModified,
                                   "value": value,
                                }
    
            if self.__dict__.get('_code', None) is None:
                request['autoGeneratedCode'] = True
            else:
                pass
                
            return request
    
        def __getattr__(self, name):
            """ handles all attribute requests dynamically. Values are returned in a sensible way,
                for example the identifiers of parents, children and components are returned
                as an array of values.
            """
                    
            int_name = '_'+name
            if int_name in self.__dict__: 
    
                if int_name == '_attachments':
                    return Attachments(self._attachments)
                elif int_name in ["_experiment"]:
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                    except Exception:
    
                        pass
                    return exp
                elif isinstance(self.__dict__[int_name], list):
                    values = []
                    for item in self.__dict__[int_name]:
                        values.append(
                            item.get("code",
                                item.get("identifier",
                                    item.get("permId", None)))
                        )
                    return values
                elif isinstance(self.__dict__[int_name], dict):
                    return self.__dict__[int_name].get(name,
                        self.__dict__[int_name].get("code",
                            self.__dict__[int_name].get("identifier",
                                self.__dict__[int_name].get("permId", None))))
                else:
                    return self.__dict__[int_name]
            else:
                return None
    
        def __setattr__(self, name, value):
            if name in ["parents", "children", "components"]:
                if not isinstance(value, list):
                    value = [value]
                objs = []
                for val in value:
                    # fetch objects in openBIS, make sure they actually exists
    
                    obj = getattr(self._openbis, 'get_'+self._entity.lower())(val)
    
                    objs.append(obj)
                self.__dict__['_'+name] = {
                    "@type": "as.dto.common.update.IdListUpdateValue",
                    "actions": [{
                        "@type": "as.dto.common.update.ListUpdateActionSet",
                        "items": [item._permId for item in objs]
                    }]
                }
            elif name in ["tags"]:
                tags = []
                for val in value:
                    tags.append({
                        "@type": "as.dto.tag.id.TagCode",
                        "code": val
                    })
    
                self.__dict__['_tags'] = tags
    
    
            elif name in ["experiment"]:
                # fetch object in openBIS, make sure it actually exists
                obj = getattr(self._openbis, "get_"+name)(value)
                self.__dict__['_'+name] = obj.data['identifier']
    
                    # mark attribute as modified, if it's an existing entity
                if self.__dict__['_is_new']:
                    pass
                else:
                    self.__dict__['_'+name]['isModified'] = True
    
            elif name in ["space"]:
    
                # fetch object in openBIS, make sure it actually exists
                obj = getattr(self._openbis, "get_"+name)(value)
                self.__dict__['_'+name] = obj['permId']
    
    Swen Vermeul's avatar
    Swen Vermeul committed
    
                    # mark attribute as modified, if it's an existing entity
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                    self.__dict__['_'+name]['isModified'] = True
    
            elif name in ["project"]:
                # fetch object in openBIS, make sure it actually exists
                obj = getattr(self._openbis, "get_"+name)(value)
                self.__dict__['_'+name] = obj['identifier']
    
                    # mark attribute as modified, if it's an existing entity
                if self.__dict__['_is_new']:
                    pass
                else:
                    self.__dict__['_'+name]['isModified'] = True
    
    
    
            elif name in ["identifier", "project"]:
                raise KeyError("you can not modify the {}".format(name))
            elif name == "code":
                if self.__dict__['_type_obj'].data['autoGeneratedCode']:
    
                    raise KeyError("for this {}Type you can not set a code".format(self.__dict__['_entity']))
    
                else:
                    self.__dict__['_code'] = value
            else:
                raise KeyError("no such attribute: {}".format(name))
    
        def get_type(self):
            return self._type_obj
    
        def get_parents(self):
    
            # e.g. self._openbis.get_samples(withChildren=self.identifier)
    
            return getattr(self._openbis, 'get_'+self._entity.lower()+'s')(withChildren=self.identifier)
    
            # e.g. self._openbis.get_samples(withParents=self.identifier)
    
            return getattr(self._openbis, 'get_'+self._entity.lower()+'s')(withParents=self.identifier)
    
        def set_tags(self, tags):
            tagIds = _tagIds_for_tags(tags, 'Set')
            self.openbis.update_sample(self.permId, tagIds=tagIds)
    
        def add_tags(self, tags):
            tagIds = _tagIds_for_tags(tags, 'Add')
            self.openbis.update_sample(self.permId, tagIds=tagIds)
    
        def del_tags(self, tags):
            tagIds = _tagIds_for_tags(tags, 'Remove')
            self.openbis.update_sample(self.permId, tagIds=tagIds)
    
        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 == 'attachments':
                    continue
                html += "<tr> <td>{}</td> <td>{}</td> </tr>".format(
                    attr, nvl(getattr(self, attr, ''),'') 
                )
    
            if 'attachments' in self._allowed_attrs and self._attachments is not None:
    
                html += "<tr><td>attachments</td><td>"
                html += "<br/>".join(att['fileName'] for att in self._attachments)
                html += "</td></tr>"
    
        """ A Sample is one of the most commonly used objects in openBIS.
    
        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, 'Sample', type)
    
            if kwargs is not None:
                for key in kwargs:
                    setattr(self, key, kwargs[key])
    
        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
    
    
            return ['props', 'get_parents()', 'get_children()', 'get_datasets()', 'get_experiment()', 'space', 'project', 'experiment', 'project','tags', 'attachments', 'data']
    
        @property
        def props(self):
            return self.__dict__['p']
    
        @property
        def type(self):
            return self.__dict__['type'].data['code']
    
        @type.setter
        def type(self, type_name):
                sample_type = self.openbis.get_sample_type(type_name)
                self.p.__dict__['_type'] = sample_type
                self.a.__dict__['_type'] = sample_type
    
        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)
    
    
        def _repr_html_(self):
            html = self.a._repr_html_()
            return html
    
        def set_properties(self, properties):
            self.openbis.update_sample(self.permId, properties=properties)
    
        def save(self):
    
            props = self.p._all_props()
            attrs = self.a._all_attrs()
            attrs["properties"] = props
    
            if self.identifier is None:
                # create a new sample
                attrs["@type"] = "as.dto.sample.create.SampleCreation"
                attrs["typeId"] = self.__dict__['type'].data['permId']
                request = {
                    "method": "createSamples",
                    "params": [ self.openbis.token,
                        [ attrs ]
                    ]
                }
                resp = self.openbis._post_request(self.openbis.as_v3, request)
                new_sample_data = self.openbis.get_sample(resp[0]['permId'], only_data=True)
                self._set_data(new_sample_data)
                return self
                
    
                attrs["@type"] = "as.dto.sample.update.SampleUpdate"
                attrs["sampleId"] = {
                    "permId": self.permId,
                    "@type": "as.dto.sample.id.SamplePermId"
                }
                request = {
                    "method": "updateSamples",
                    "params": [ self.openbis.token,
                        [ attrs ]
                    ]
                }
                resp = self.openbis._post_request(self.openbis.as_v3, request)
                print('Sample successfully updated')
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def delete(self, reason):
    
            self.openbis.delete_entity('sample', self.permId, reason)
    
            return self.openbis.get_datasets(sample=self.permId)
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def get_projects(self):
            return self.openbis.get_project(withSamples=[self.permId])
    
        def get_experiment(self):
            try: 
                return self.openbis.get_experiment(self._experiment['identifier'])
            except Exception:
                pass
    
    class Space(dict):
        """ managing openBIS spaces
        """
    
        def __init__(self, openbis_obj, *args, **kwargs):
            super(Space, self).__init__(*args, **kwargs)
            self.__dict__ = self
            self.openbis = openbis_obj
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def get_samples(self, *args, **kwargs):
    
            """ Lists all samples in a given space. A pandas DataFrame object is returned.
            """
    
    Swen Vermeul's avatar
    Swen Vermeul committed
    
            return self.openbis.get_samples(space=self.code, *args, **kwargs)
    
    
        @property
        def projects(self):
            return self.openbis.get_projects(space=self.code)
        
        def new_project(self, **kwargs):
            return self.openbis.new_project(space=self.code, **kwargs)
    
        @property
        def experiments(self):
            return self.openbis.get_experiments(space=self.code)
    
    
    
    class Things():
        """An object that contains a DataFrame object about an entity  available in openBIS.
           
        """
    
    
        def __init__(self, openbis_obj, entity, df, identifier_name='code'):
    
            self.openbis = openbis_obj
    
            self.entity = entity
    
            self.df = df
            self.identifier_name = identifier_name
    
        def _repr_html_(self):
            return self.df._repr_html_()
    
        def __getitem__(self, key):
            if self.df is not None and len(self.df) > 0:
                row = None
                if isinstance(key, int):
                    # get thing by rowid
                    row = self.df.loc[[key]]
    
                elif isinstance(key, list):
                    # treat it as a normal dataframe
                    return self.df[key]
    
                else:
                    # get thing by code
                    row = self.df[self.df[self.identifier_name]==key.upper()]
    
                if row is not None:
    
                    # invoke the openbis.get_entity() method
                    return getattr(self.openbis, 'get_'+self.entity)(row[self.identifier_name].values[0])
    
        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, 'Experiment', type)
    
    
            if data is not None:
                self._set_data(data)
    
            if kwargs is not None:
                for key in kwargs:
                    setattr(self, key, kwargs[key])
    
        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
    
        def __dir__(self):
            # the list of possible methods/attributes displayed
            # when invoking TAB-completition
            return [
    
                'props', 'space', 'project', 
    
                'project','tags', 'attachments', 'data',
    
                'get_datasets()', 'get_samples()'
    
            ]
    
        @property
        def props(self):
            return self.__dict__['p']
    
        @property
        def type(self):
            return self.__dict__['type'].data['code']
    
        @type.setter
        def type(self, type_name):
                experiment_type = self.openbis.get_experiment_type(type_name)
                self.p.__dict__['_type'] = experiment_type
                self.a.__dict__['_type'] = experiment_type
    
        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)
    
        def _repr_html_(self):
            html = self.a._repr_html_()
            return html
    
            self.openbis.update_experiment(self.permId, properties=properties)
    
        def save(self):
            props = self.p._all_props()
            attrs = self.a._all_attrs()
            attrs["properties"] = props
    
            if self.identifier is None:
                # create a new experiment
                attrs["@type"] = "as.dto.experiment.create.ExperimentCreation"
                attrs["typeId"] = self.__dict__['type'].data['permId']
                request = {
                    "method": "createExperiments",
                    "params": [ self.openbis.token,
                        [ attrs ]
                    ]
                }
                resp = self.openbis._post_request(self.openbis.as_v3, request)
                new_experiment_data = self.openbis.get_experiment(resp[0]['permId'], only_data=True)
                self._set_data(new_experiment_data)
                return self
                
            else:
                attrs["@type"] = "as.dto.experiment.update.ExperimentUpdate"
                attrs["experimentId"] = {
                    "permId": self.permId,
                    "@type": "as.dto.experiment.id.ExperimentPermId"
                }
                request = {
                    "method": "updateExperiments",
                    "params": [ self.openbis.token,
                        [ attrs ]
                    ]
                }
                resp = self.openbis._post_request(self.openbis.as_v3, request)
                print('Experiment successfully updated')
    
            self.openbis.delete_entity('experiment', self.permId, reason)
    
            return self.openbis.get_datasets(experiment=self.permId)
    
            return self.openbis.get_project(experiment=self.permId)
    
            return self.openbis.get_samples(experiment=self.permId)
    
        """ holds are properties, that are assigned to an entity, eg. sample or experiment
        """
    
    
        def __init__(self, openbis_obj, data):
            self.openbis = openbis_obj
            self.data = data
            self.prop = {}
    
            if self.data['propertyAssignments'] is None:
                self.data['propertyAssignments'] = []
    
            for pa in self.data['propertyAssignments']:
                self.prop[pa['propertyType']['code'].lower()] = pa
    
    
        def codes(self):
            codes = []
            for pa in self.data['propertyAssignments']:
                codes.append(pa['propertyType']['code'].lower())
            return codes
    
            """.format(
                self.data['@type'].split('.')[-1],
                self.data['code'], 
                self.data['description']
            )
            if 'autoGeneratedCode' in self.data:
                html += "<p>Code autogenerated: {}</p>".format(
                    self.data['autoGeneratedCode'])
    
            html += """
    
    <table border="1" class="dataframe">
      <thead>
        <tr style="text-align: right;">
          <th>property</th>
          <th>label</th>
          <th>description</th>
    
            for pa in self.data['propertyAssignments']:
                html += "<tr> <th>{}</th> <td>{}</td> <td>{}</td> <td>{}</td> <td>{}</td> </tr>".format(
                    pa['propertyType']['code'].lower(),    
                    pa['propertyType']['label'],    
                    pa['propertyType']['description'],    
    
                    pa['propertyType']['dataType'],    
    
    
    class Attachments:
    
        def __init__(self, attachments):
            self.atts = attachments
            atts = []
            for att in attachments:
                atts.append({
                    "fileName": att["fileName"],
                    "title": att["title"],
                    "description": att["description"],
                    "registrationDate": format_timestamp(att["registrationDate"]), 
                    "version": att["version"],
                })
            self.df = DataFrame(atts)[['fileName','title','description','registrationDate','version']]
    
        def _repr_html_(self):  
            return self.df._repr_html_() 
    
        def download(self):
            pass         
    
        def __getitem__(self, key):
            if self.df is not None  and len(self.df) > 0:
                row = None
                if isinstance(key, int):
                    row = self.df.loc[[key]]
    
                if row is not None:
                    self._download()
    
    
    
    class Project:
    
        def __init__(self, openbis_obj, data=None, **kwargs):
            self.__dict__['openbis'] = openbis_obj
            self.__dict__['type'] = type
            self.__dict__['a'] = AttrHolder(openbis_obj, 'Project')
    
            if data is not None:
                self.a(data)
                self.__dict__['data'] = data
    
            if kwargs is not None:
                for key in kwargs:
                    setattr(self, key, kwargs[key])
    
        def _repr_html_(self):
            html = self.a._repr_html_()
            return html