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: def __init__(self, openbis_obj, type=None): self.__dict__["_openbis"] = openbis_obj self.__dict__["_property_names"] = {} if type is None: return 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 def _all_props(self): props = {} if not getattr(self, "_type"): return props for code in self._type.codes(): props[code] = getattr(self, code) return props def all(self): """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]) else: 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'] """ return getattr(self, key) 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_": # 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']) else: syntax = {property_type["label"]: property_type["dataType"]} if property_type["dataType"] == "TIMESTAMP": syntax["syntax"] = "YYYY-MM-DD HH:MIN:SS" return syntax else: return def __setattr__(self, name, value): """This special method allows a PropertyHolder object to check the attributes that are assigned to that object """ if name not in self._property_names: raise KeyError( f"No such property: «{name}». Allowed properties are: {', '.join(self._property_names.keys())}" ) property_type = self._property_names[name] data_type = property_type["dataType"] if data_type == "CONTROLLEDVOCABULARY": terms = property_type["terms"] value = str(value).upper() 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"): if not check_datatype(data_type, value): raise ValueError(f"Value must be of type {data_type}") 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) 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> <th>description</th> <th>type</th> <th>mandatory</th> </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"), ] ) html += "</tr>" html += """ </tbody> </table> """ return html def __repr__(self): def nvl(val, string=""): if val is None: return string elif val == "true": return 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)