Skip to content
Snippets Groups Projects
property.py 7.86 KiB
Newer Older
  • Learn to ignore specific revisions
  • #   Copyright ETH 2018 - 2023 Zürich, Scientific IT Services
    # 
    #   Licensed under the Apache License, Version 2.0 (the "License");
    #   you may not use this file except in compliance with the License.
    #   You may obtain a copy of the License at
    # 
    #        http://www.apache.org/licenses/LICENSE-2.0
    #   
    #   Unless required by applicable law or agreed to in writing, software
    #   distributed under the License is distributed on an "AS IS" BASIS,
    #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    #   See the License for the specific language governing permissions and
    #   limitations under the License.
    #
    
    from tabulate import tabulate
    
    from texttable import Texttable
    
    from pybis.utils import (
        check_datatype,
        split_identifier,
        format_timestamp,
        is_identifier,
        is_permid,
        nvl,
    )
    
    class PropertyHolder:
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def __init__(self, openbis_obj, type=None):
    
            self.__dict__["_openbis"] = openbis_obj
            self.__dict__["_property_names"] = {}
    
            self.__dict__["_type"] = type
            if (
                "propertyAssignments" in type.data
                and type.data["propertyAssignments"] is not None
            ):
                for prop in type.data["propertyAssignments"]:
                    property_name = prop["propertyType"]["code"].lower()
                    self._property_names[property_name] = prop["propertyType"]
                    self._property_names[property_name]["mandatory"] = prop["mandatory"]
                    self._property_names[property_name]["showInEditView"] = prop[
                        "showInEditView"
                    ]
                    if prop["propertyType"]["dataType"] == "CONTROLLEDVOCABULARY":
                        pt = self._openbis.get_property_type(prop["propertyType"]["code"])
    
                        # get the vocabulary of a property type.
                        # In some cases, the «code» of an assigned property is not identical to the «vocabulary» attribute
                        voc = self._openbis.get_vocabulary(pt.vocabulary)
                        terms = voc.get_terms()
    
                        self._property_names[property_name]["terms"] = terms
    
    Swen Vermeul's avatar
    Swen Vermeul committed
    
        def _all_props(self):
            props = {}
    
            if not getattr(self, "_type"):
    
                return props
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            for code in self._type.codes():
                props[code] = getattr(self, code)
            return props
    
    
            """Returns the properties as an array"""
    
            props = {}
            for code in self._type.codes():
                props[code] = getattr(self, code)
            return props
    
        def all_nonempty(self):
            props = {}
            for code in self._type.codes():
                value = getattr(self, code)
                if value is not None:
                    props[code] = value
            return props
    
    
        def __call__(self, *args):
            if len(args) == 0:
                return self.all()
            elif len(args) == 1:
                return getattr(self, args[0])
            elif len(args) == 2:
                return setattr(self, args[0], args[1])
            else:
    
                raise ValueError("called properties with more than 2 arguments")
    
    
        def get(self, *args):
            if len(args) == 0:
                return self.all()
            elif len(args) == 1 and not isinstance(args[0], list):
                return getattr(self, args[0])
    
                if isinstance(args[0], list):
                    args = args[0]
                return {arg: getattr(self, arg, None) for arg in args}
    
        def set(self, *args):
            if len(args) == 2:
                setattr(self, args[0], args[1])
            elif len(args) == 1 and isinstance(args[0], dict):
                for key in args[0]:
                    setattr(self, key, args[0][key])
    
        def __getitem__(self, key):
            """For properties that contain either a dot or a dash or any other non-valid method character,
            a user can use a key-lookup instead, e.g. sample.props['my-weird.property-name']
            """
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def __getattr__(self, name):
    
            """attribute syntax can be found out by
            adding an underscore at the end of the property name
            """
            if name == "_ipython_canary_method_should_not_exist_":
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                # make Jupyter use the _repr_html_ method
                return
    
            if name.endswith("_"):
                name = name.rstrip("_")
    
                if name in self._property_names:
                    property_type = self._property_names[name]
    
                    if property_type["dataType"] == "CONTROLLEDVOCABULARY":
                        return property_type["terms"]
                        # return self._get_terms(property_type['code'])
    
                        syntax = {property_type["label"]: property_type["dataType"]}
    
                            syntax["syntax"] = "YYYY-MM-DD HH:MIN:SS"
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                else:
                    return
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def __setattr__(self, name, value):
    
            """This special method allows a PropertyHolder object
            to check the attributes that are assigned to that object
            """
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            if name not in self._property_names:
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                raise KeyError(
    
                    f"No such property: «{name}». Allowed properties are: {', '.join(self._property_names.keys())}"
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                )
            property_type = self._property_names[name]
    
            data_type = property_type["dataType"]
            if data_type == "CONTROLLEDVOCABULARY":
                terms = property_type["terms"]
    
                if value not in terms.df["code"].values:
                    raise ValueError(
    
                        f"Value for attribute «{name}» must be one of these terms: {', '.join(terms.df['code'].values)}"
    
                    )
            elif data_type in ("INTEGER", "BOOLEAN", "VARCHAR"):
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                if not check_datatype(data_type, value):
    
                    raise ValueError(f"Value must be of type {data_type}")
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            self.__dict__[name] = value
    
    
        def __setitem__(self, key, value):
            """For properties that contain either a dot or a dash or any other non-valid method character,
            a user can use a key instead, e.g. sample.props['my-weird.property-name']
            """
            return setattr(self, key, value)
    
    
    Swen Vermeul's avatar
    Swen Vermeul committed
        def __dir__(self):
            return self._property_names
    
        def _repr_html_(self):
    
            def nvl(val, string=""):
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                if val is None:
                    return string
    
                elif val == "true":
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                    return True
    
                elif val == "false":
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                    return False
                return val
    
    Swen Vermeul's avatar
    Swen Vermeul committed
            html = """
                <table border="1" class="dataframe">
                <thead>
                    <tr style="text-align: right;">
                    <th>property</th>
                    <th>value</th>
    
                    <th>description</th>
                    <th>type</th>
                    <th>mandatory</th>
    
    Swen Vermeul's avatar
    Swen Vermeul committed
                    </tr>
                </thead>
                <tbody>
            """
    
    
            for prop_name, prop in self._property_names.items():
    
                html += "<tr>"
                html += "".join(
                    f"<td>{item}</td>"
                    for item in [
                        prop_name,
                        nvl(getattr(self, prop_name, ""), ""),
                        prop.get("description"),
                        prop.get("dataType"),
                        prop.get("mandatory"),
                    ]
    
    Swen Vermeul's avatar
    Swen Vermeul committed
    
            html += """
                </tbody>
                </table>
            """
            return html
    
    
        def __repr__(self):
    
            def nvl(val, string=""):
    
                if val is None:
                    return string
    
                elif val == "true":
    
                elif val == "false":
    
                    return False
                return str(val)
    
    
            headers = ["property", "value", "mandatory"]
    
    
            lines = []
            for prop_name in self._property_names:
    
                lines.append([prop_name, nvl(getattr(self, prop_name, ""))])
    
            return tabulate(lines, headers=headers)