diff --git a/src/dsp_tools/commands/project/models/project.py b/src/dsp_tools/commands/project/models/project.py index 525b2cd7f..589c02ea5 100644 --- a/src/dsp_tools/commands/project/models/project.py +++ b/src/dsp_tools/commands/project/models/project.py @@ -360,11 +360,11 @@ def create(self) -> Project: :return: JSON-object from DSP """ - jsonobj = self._toJsonObj_action_create() + jsonobj = self._toJsonObj_create() result = self._con.post(Project.ROUTE, jsonobj) return Project.fromJsonObj(self._con, result["project"]) - def _toJsonObj_action_create(self) -> dict[str, str]: + def _toJsonObj_create(self) -> dict[str, str]: tmp = {} if self._shortcode is None: raise BaseError("There must be a valid project shortcode!") @@ -415,11 +415,11 @@ def update(self) -> Project: Returns: JSON object returned as response from DSP reflecting the update """ - jsonobj = self._toJsonObj_action_update() + jsonobj = self._toJsonObj_update() result = self._con.put(Project.IRI + quote_plus(self.iri), jsonobj) return Project.fromJsonObj(self._con, result["project"]) - def _toJsonObj_action_update(self) -> dict[str, str]: + def _toJsonObj_update(self) -> dict[str, str]: tmp = {} if self._shortcode is not None and "shortcode" in self._changed: tmp["shortcode"] = self._shortcode diff --git a/src/dsp_tools/commands/project/models/propertyclass.py b/src/dsp_tools/commands/project/models/propertyclass.py index 7a8cb02bd..96c731be4 100644 --- a/src/dsp_tools/commands/project/models/propertyclass.py +++ b/src/dsp_tools/commands/project/models/propertyclass.py @@ -1,10 +1,12 @@ +from __future__ import annotations + from typing import Any, Optional, Sequence, Union from urllib.parse import quote_plus import regex from dsp_tools.commands.project.models.context import Context -from dsp_tools.commands.project.models.helpers import Actions, WithId +from dsp_tools.commands.project.models.helpers import WithId from dsp_tools.commands.project.models.listnode import ListNode from dsp_tools.commands.project.models.model import Model from dsp_tools.models.datetimestamp import DateTimeStamp @@ -37,7 +39,7 @@ def __init__( iri: Optional[str] = None, name: Optional[str] = None, ontology_id: Optional[str] = None, - superproperties: Optional[Sequence[Union["PropertyClass", str]]] = None, + superproperties: Optional[Sequence[Union[PropertyClass, str]]] = None, rdf_object: Optional[str] = None, rdf_subject: Optional[str] = None, gui_element: Optional[str] = None, @@ -62,34 +64,25 @@ def __init__( self._rdf_subject = rdf_subject self._gui_element = gui_element self._gui_attributes = gui_attributes - # - # process label - # - if label is not None: - if isinstance(label, str): - self._label = LangString(label) - elif isinstance(label, LangString): - self._label = label - else: - raise BaseError("Invalid LangString for label!") - else: - self._label = LangString({}) - # - # process comment - # - if comment is not None: - if isinstance(comment, str): - self._comment = LangString(comment) - elif isinstance(comment, LangString): - self._comment = comment - else: - raise BaseError("Invalid LangString for comment!") - else: - self._comment = LangString({}) + + self._label = PropertyClass._init_process_language_value(label, "label") + self._comment = PropertyClass._init_process_language_value(comment, "comment") self._editable = editable self._linkvalue = linkvalue + @staticmethod + def _init_process_language_value(prop_val: None | str | LangString, property: str) -> LangString: + if prop_val is not None: + if isinstance(prop_val, str): + return LangString(prop_val) + elif isinstance(prop_val, LangString): + return prop_val + else: + raise BaseError(f"Invalid LangString for {property}!") + else: + return LangString({}) + # # Here follows a list of getters/setters # @@ -186,33 +179,62 @@ def linkvalue(self) -> None: raise BaseError('"linkvalue" cannot be modified!') @classmethod - def fromJsonObj(cls, con: Connection, context: Context, json_obj: Any) -> "PropertyClass": + def fromJsonObj(cls, con: Connection, context: Context, json_obj: Any) -> PropertyClass: if isinstance(json_obj, list): json_obj = json_obj[0] - rdfs = context.prefix_from_iri("http://www.w3.org/2000/01/rdf-schema#") - knora_api = context.prefix_from_iri("http://api.knora.org/ontology/knora-api/v2#") - salsah_gui = context.prefix_from_iri("http://api.knora.org/ontology/salsah-gui/v2#") + rdfs_iri = context.prefix_from_iri("http://www.w3.org/2000/01/rdf-schema#") + knora_api_iri = context.prefix_from_iri("http://api.knora.org/ontology/knora-api/v2#") + salsah_gui_iri = context.prefix_from_iri("http://api.knora.org/ontology/salsah-gui/v2#") - if not json_obj.get(knora_api + ":isResourceProperty"): + if not json_obj.get(knora_api_iri + ":isResourceProperty"): raise BaseError("This is not a property!") if json_obj.get("@id") is None: raise BaseError('Property class has no "@id"!') + tmp_id = json_obj.get("@id").split(":") iri = context.iri_from_prefix(tmp_id[0]) + "#" + tmp_id[1] ontology_id = tmp_id[0] name = tmp_id[1] + + rdf_object = WithId(json_obj.get(knora_api_iri + ":objectType")).to_string() + rdf_subject = WithId(json_obj.get(knora_api_iri + ":subjectType")).to_string() + label = LangString.fromJsonLdObj(json_obj.get(rdfs_iri + ":label")) + comment = LangString.fromJsonLdObj(json_obj.get(rdfs_iri + ":comment")) + editable = json_obj.get(knora_api_iri + ":isEditable") + linkvalue = json_obj.get(knora_api_iri + ":isLinkProperty") + + gui_attributes, gui_element = cls._fromJsonObj_get_gui_info(json_obj, salsah_gui_iri) + superproperties = cls._fromJson_get_superproperties(json_obj, rdfs_iri) + + return cls( + con=con, + context=context, + iri=iri, + name=name, + ontology_id=ontology_id, + superproperties=superproperties, + rdf_object=rdf_object, + rdf_subject=rdf_subject, + gui_element=gui_element, + gui_attributes=gui_attributes, + label=label, + comment=comment, + editable=editable, + linkvalue=linkvalue, + ) + + @classmethod + def _fromJson_get_superproperties(cls, json_obj: dict[str, Any], rdfs: str) -> list[str] | None: superproperties_obj = json_obj.get(rdfs + ":subPropertyOf") - superproperties: list[Union[None, str]] if not isinstance(superproperties_obj, list): superproperties_obj = [superproperties_obj] # make a list out of it if superproperties_obj: - superproperties = [x["@id"] for x in superproperties_obj if x and x.get("@id")] + return [x["@id"] for x in superproperties_obj if x and x.get("@id")] else: - superproperties = None - rdf_object = WithId(json_obj.get(knora_api + ":objectType")).to_string() - rdf_subject = WithId(json_obj.get(knora_api + ":subjectType")).to_string() - label = LangString.fromJsonLdObj(json_obj.get(rdfs + ":label")) - comment = LangString.fromJsonLdObj(json_obj.get(rdfs + ":comment")) + return None + + @classmethod + def _fromJsonObj_get_gui_info(cls, json_obj: dict[str, Any], salsah_gui: str) -> tuple[dict[str, str], str]: gui_element = None if json_obj.get(salsah_gui + ":guiElement") is not None: gui_element = WithId(json_obj.get(salsah_gui + ":guiElement")).to_string() @@ -230,126 +252,89 @@ def fromJsonObj(cls, con: Connection, context: Context, json_obj: Any) -> "Prope gui_attributes[tmp[0]] = "" else: gui_attributes[tmp[0]] = tmp[1] + return gui_attributes, gui_element - editable = json_obj.get(knora_api + ":isEditable") - linkvalue = json_obj.get(knora_api + ":isLinkProperty") - return cls( - con=con, - context=context, - iri=iri, - name=name, - ontology_id=ontology_id, - superproperties=superproperties, - rdf_object=rdf_object, - rdf_subject=rdf_subject, - gui_element=gui_element, - gui_attributes=gui_attributes, - label=label, - comment=comment, - editable=editable, - linkvalue=linkvalue, - ) - - def toJsonObj(self, lastModificationDate: DateTimeStamp, action: Actions, what: Optional[str] = None) -> Any: - def resolve_propref(resref: str) -> dict[str, str]: - tmp = resref.split(":") - if len(tmp) > 1: - if tmp[0]: - # return {"@id": resref} # fully qualified name in the form "prefix:name" - return { - "@id": self._context.get_qualified_iri(resref) - } # fully qualified name in the form "prefix:name" - else: - return { - "@id": self._context.prefix_from_iri(self._ontology_id) + ":" + tmp[1] - } # ":name" in current ontology - else: - return {"@id": "knora-api:" + resref} # no ":", must be from knora-api! - - tmp = {} - exp = regex.compile("^http.*") # It is already a fully IRI + def _make_full_onto_iri(self) -> tuple[str, str]: + exp = regex.compile("^http.*") # It is already a full IRI if exp.match(self._ontology_id): propid = self._context.prefix_from_iri(self._ontology_id) + ":" + self._name ontid = self._ontology_id else: propid = self._ontology_id + ":" + self._name ontid = self._context.iri_from_prefix(self._ontology_id) - if action == Actions.Create: - if self._name is None: - raise BaseError("There must be a valid property class name!") - if self._ontology_id is None: - raise BaseError("There must be a valid ontology_id given!") - if self._superproperties is None: - superproperties = [{"@id": "knora-api:hasValue"}] + return ontid, propid + + def _resolve_propref(self, resref: str) -> dict[str, str]: + tmp = resref.split(":") + if len(tmp) > 1: + if tmp[0]: + # return {"@id": resref} # fully qualified name in the form "prefix:name" + return { + "@id": self._context.get_qualified_iri(resref) + } # fully qualified name in the form "prefix:name" else: - superproperties = list(map(resolve_propref, self._superproperties)) - - tmp = { - "@id": ontid, # self._ontology_id, - "@type": "owl:Ontology", - "knora-api:lastModificationDate": lastModificationDate.toJsonObj(), - "@graph": [ - { - "@id": propid, - "@type": "owl:ObjectProperty", - "rdfs:label": self._label.toJsonLdObj(), - "rdfs:subPropertyOf": superproperties, - } - ], - "@context": self._context.toJsonObj(), - } - if self._comment: - tmp["@graph"][0]["rdfs:comment"] = self._comment.toJsonLdObj() - if self._rdf_subject: - tmp["@graph"][0]["knora-api:subjectType"] = resolve_propref(self._rdf_subject) - if self._rdf_object: - tmp["@graph"][0]["knora-api:objectType"] = resolve_propref(self._rdf_object) - if self._gui_element: - tmp["@graph"][0]["salsah-gui:guiElement"] = {"@id": self._gui_element} - if self._gui_attributes: - ga = list(map(lambda x: x[0] + "=" + str(x[1]), self._gui_attributes.items())) - tmp["@graph"][0]["salsah-gui:guiAttribute"] = ga - elif action == Actions.Update: - tmp = { - "@id": ontid, # self._ontology_id, - "@type": "owl:Ontology", - "knora-api:lastModificationDate": lastModificationDate.toJsonObj(), - "@graph": [ - { - "@id": propid, - "@type": "owl:ObjectProperty", - } - ], - "@context": self._context.toJsonObj(), - } - if what == "label": - if not self._label.isEmpty() and "label" in self._changed: - tmp["@graph"][0]["rdfs:label"] = self._label.toJsonLdObj() - if what == "comment": - if not self._comment.isEmpty() and "comment" in self._changed: - tmp["@graph"][0]["rdfs:comment"] = self._comment.toJsonLdObj() - - return tmp + return { + "@id": self._context.prefix_from_iri(self._ontology_id) + ":" + tmp[1] + } # ":name" in current ontology + else: + return {"@id": "knora-api:" + resref} # no ":", must be from knora-api! - def create(self, last_modification_date: DateTimeStamp) -> tuple[DateTimeStamp, "PropertyClass"]: - jsonobj = self.toJsonObj(last_modification_date, Actions.Create) + def create(self, last_modification_date: DateTimeStamp) -> tuple[DateTimeStamp, PropertyClass]: + jsonobj = self._toJsonObj_create(last_modification_date) result = self._con.post(PropertyClass.ROUTE, jsonobj) last_modification_date = DateTimeStamp(result["knora-api:lastModificationDate"]) return last_modification_date, PropertyClass.fromJsonObj(self._con, self._context, result["@graph"]) - def update(self, last_modification_date: DateTimeStamp) -> tuple[DateTimeStamp, "PropertyClass"]: + def _toJsonObj_create(self, lastModificationDate: DateTimeStamp) -> dict[str, Any]: + ontid, propid = self._make_full_onto_iri() + if self._name is None: + raise BaseError("There must be a valid property class name!") + if self._ontology_id is None: + raise BaseError("There must be a valid ontology_id given!") + if self._superproperties is None: + superproperties = [{"@id": "knora-api:hasValue"}] + else: + superproperties = list(map(self._resolve_propref, self._superproperties)) + tmp = { + "@id": ontid, # self._ontology_id, + "@type": "owl:Ontology", + "knora-api:lastModificationDate": lastModificationDate.toJsonObj(), + "@graph": [ + { + "@id": propid, + "@type": "owl:ObjectProperty", + "rdfs:label": self._label.toJsonLdObj(), + "rdfs:subPropertyOf": superproperties, + } + ], + "@context": self._context.toJsonObj(), + } + if self._comment: + tmp["@graph"][0]["rdfs:comment"] = self._comment.toJsonLdObj() + if self._rdf_subject: + tmp["@graph"][0]["knora-api:subjectType"] = self._resolve_propref(self._rdf_subject) + if self._rdf_object: + tmp["@graph"][0]["knora-api:objectType"] = self._resolve_propref(self._rdf_object) + if self._gui_element: + tmp["@graph"][0]["salsah-gui:guiElement"] = {"@id": self._gui_element} + if self._gui_attributes: + ga = list(map(lambda x: x[0] + "=" + str(x[1]), self._gui_attributes.items())) + tmp["@graph"][0]["salsah-gui:guiAttribute"] = ga + return tmp + + def update(self, last_modification_date: DateTimeStamp) -> tuple[DateTimeStamp, PropertyClass]: # # Note: DSP is able to change only one thing per call, either label or comment! # result = None something_changed = False if "label" in self._changed: - jsonobj = self.toJsonObj(last_modification_date, Actions.Update, "label") + jsonobj = self._toJsonObj_update(last_modification_date, "label") result = self._con.put(PropertyClass.ROUTE, jsonobj) last_modification_date = DateTimeStamp(result["knora-api:lastModificationDate"]) something_changed = True if "comment" in self._changed: - jsonobj = self.toJsonObj(last_modification_date, Actions.Update, "comment") + jsonobj = self._toJsonObj_update(last_modification_date, "comment") result = self._con.put(PropertyClass.ROUTE, jsonobj) last_modification_date = DateTimeStamp(result["knora-api:lastModificationDate"]) something_changed = True @@ -358,6 +343,28 @@ def update(self, last_modification_date: DateTimeStamp) -> tuple[DateTimeStamp, else: return last_modification_date, self + def _toJsonObj_update(self, lastModificationDate: DateTimeStamp, what_changed: str) -> dict[str, Any]: + ontid, propid = self._make_full_onto_iri() + tmp = { + "@id": ontid, # self._ontology_id, + "@type": "owl:Ontology", + "knora-api:lastModificationDate": lastModificationDate.toJsonObj(), + "@graph": [ + { + "@id": propid, + "@type": "owl:ObjectProperty", + } + ], + "@context": self._context.toJsonObj(), + } + if what_changed == "label": + if not self._label.isEmpty() and "label" in self._changed: + tmp["@graph"][0]["rdfs:label"] = self._label.toJsonLdObj() + if what_changed == "comment": + if not self._comment.isEmpty() and "comment" in self._changed: + tmp["@graph"][0]["rdfs:comment"] = self._comment.toJsonLdObj() + return tmp + def delete(self, last_modification_date: DateTimeStamp) -> DateTimeStamp: result = self._con.delete( PropertyClass.ROUTE + "/" + quote_plus(self._iri) + "?lastModificationDate=" + str(last_modification_date) @@ -391,35 +398,39 @@ def createDefinitionFileObj(self, context: Context, shortname: str) -> dict[str, if self.gui_element: def_file_obj["gui_element"] = context.reduce_iri(self.gui_element, shortname) if self.gui_attributes: - gui_elements = {} - for attname, attvalue in self.gui_attributes.items(): - if attname == "size": - gui_elements[attname] = int(attvalue) - elif attname == "maxlength": - gui_elements[attname] = int(attvalue) - elif attname == "maxsize": - gui_elements[attname] = int(attvalue) - elif attname == "hlist": - iri = attvalue[1:-1] - rootnode = ListNode(con=self._con, iri=iri).read() - gui_elements[attname] = rootnode.name - elif attname == "numprops": - gui_elements[attname] = int(attvalue) - elif attname == "ncolors": - gui_elements[attname] = int(attvalue) - elif attname == "cols": - gui_elements[attname] = int(attvalue) - elif attname == "rows": - gui_elements[attname] = int(attvalue) - elif attname == "width": - gui_elements[attname] = str(attvalue) - elif attname == "wrap": - gui_elements[attname] = str(attvalue) - elif attname == "max": - gui_elements[attname] = float(attvalue) - elif attname == "min": - gui_elements[attname] = float(attvalue) - else: - gui_elements[attname] = str(attvalue) + gui_elements = self._createDefinitionFileObj_gui_attributes() def_file_obj["gui_attributes"] = gui_elements return def_file_obj + + def _createDefinitionFileObj_gui_attributes(self) -> dict[str, Any]: + gui_elements = {} + for attname, attvalue in self.gui_attributes.items(): + if attname == "size": + gui_elements[attname] = int(attvalue) + elif attname == "maxlength": + gui_elements[attname] = int(attvalue) + elif attname == "maxsize": + gui_elements[attname] = int(attvalue) + elif attname == "hlist": + iri = attvalue[1:-1] + rootnode = ListNode(con=self._con, iri=iri).read() + gui_elements[attname] = rootnode.name + elif attname == "numprops": + gui_elements[attname] = int(attvalue) + elif attname == "ncolors": + gui_elements[attname] = int(attvalue) + elif attname == "cols": + gui_elements[attname] = int(attvalue) + elif attname == "rows": + gui_elements[attname] = int(attvalue) + elif attname == "width": + gui_elements[attname] = str(attvalue) + elif attname == "wrap": + gui_elements[attname] = str(attvalue) + elif attname == "max": + gui_elements[attname] = float(attvalue) + elif attname == "min": + gui_elements[attname] = float(attvalue) + else: + gui_elements[attname] = str(attvalue) + return gui_elements