示例#1
0
class PropertyClass:
    _context: Context
    _id: str
    _name: str
    _ontology_id: str
    _superproperties: List[str]
    _object: str
    _subject: str
    _gui_element: str
    _gui_attributes: Dict[str, str]
    _label: LangString
    _comment: LangString
    _editable: bool
    _linkvalue: bool
    _changed: Set[str]

    def __init__(self,
                 con: Connection,
                 context: Context,
                 id: Optional[str] = None,
                 name: Optional[str] = None,
                 ontology_id: Optional[str] = None,
                 superproperties: Optional[List[Union['PropertyClass',
                                                      str]]] = None,
                 object: Optional[str] = None,
                 subject: Optional[str] = None,
                 gui_element: Optional[str] = None,
                 gui_attributes: Optional[Dict[str, str]] = None,
                 label: Optional[Union[LangString, str]] = None,
                 comment: Optional[Union[LangString, str]] = None,
                 editable: Optional[bool] = None,
                 linkvalue: Optional[bool] = None):
        if not isinstance(con, Connection):
            raise BaseError(
                '"con"-parameter must be an instance of Connection')
        if not isinstance(context, Context):
            raise BaseError(
                '"context"-parameter must be an instance of Context')
        self.con = con
        self._context = context
        self._id = id
        self._name = name
        self._ontology_id = ontology_id
        if isinstance(superproperties, PropertyClass):
            self._superproperties = list(map(lambda a: a.id, superproperties))
        else:
            self._superproperties = superproperties
        self._object = object
        self._subject = 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 = None
        #
        # 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 = None

        self._editable = editable
        self._linkvalue = linkvalue
        self._changed = set()

    #
    # Here follows a list of getters/setters
    #
    @property
    def name(self) -> Optional[str]:
        return self._name

    @name.setter
    def name(self, value: str) -> None:
        raise BaseError('"name" cannot be modified!')

    @property
    def id(self) -> Optional[str]:
        return self._id

    @id.setter
    def id(self, value: str) -> None:
        raise BaseError('"id" cannot be modified!')

    @property
    def ontology_id(self) -> Optional[str]:
        return self._ontology_id

    @ontology_id.setter
    def ontology_id(self, value: str) -> None:
        raise BaseError('"ontology_id" cannot be modified!')

    @property
    def superproperties(self) -> Optional[str]:
        return self._superproperties

    @superproperties.setter
    def superproperties(self, value: str) -> None:
        raise BaseError('"superproperties" cannot be modified!')

    @property
    def object(self) -> Optional[str]:
        return self._object

    @object.setter
    def object(self, value: Any):
        raise BaseError('"object" cannot be modified!')

    @property
    def subject(self) -> Optional[str]:
        return self._subject

    @subject.setter
    def subject(self, value: Any):
        raise BaseError('"subject" cannot be modified!')

    @property
    def gui_element(self) -> Optional[str]:
        return self._gui_element

    @gui_element.setter
    def gui_element(self, value: str) -> None:
        self._gui_element = value
        self._changed.append('gui_element')

    @property
    def gui_attributes(self) -> Optional[Dict[str, str]]:
        return self._gui_attributes

    @gui_attributes.setter
    def gui_attributes(self, value: List[Dict[str, str]]) -> None:
        self._gui_attributes = value
        self._changed.append('gui_attributes')

    def addGuiAttribute(self, key: str, value: str) -> None:
        self._gui_attributes[key] = value
        self._changed.append('gui_attributes')

    def rmGuiAttribute(self, key: str) -> None:
        if self._gui_attributes.get(key) is not None:
            del self._gui_attributes[key]
            self._changed.append('gui_attributes')

    @property
    def label(self) -> Optional[LangString]:
        return self._label

    @label.setter
    def label(self, value: Optional[Union[LangString, str]]) -> None:
        if value is None:
            self._label.empty()  # clear all labels
        else:
            if isinstance(value, LangString):
                self._label = value
            elif isinstance(value, str):
                self._label = LangString(value)
            else:
                raise BaseError('Not a valid LangString')
        self._changed.add('label')

    def addLabel(self, lang: Union[Languages, str], value: str) -> None:
        self._label[lang] = value
        self._changed.add('label')

    def rmLabel(self, lang: Union[Languages, str]) -> None:
        del self._label[lang]
        self._changed.add('label')

    @property
    def comment(self) -> Optional[LangString]:
        return self._comment

    @comment.setter
    def comment(self, value: Optional[LangString]) -> None:
        if value is None:
            self._comment.empty()  # clear all comments!
        else:
            if isinstance(value, LangString):
                self._comment = value
            elif isinstance(value, str):
                self._comment = LangString(value)
            else:
                raise BaseError('Not a valid LangString')
        self._changed.add('comment')

    def addComment(self, lang: Union[Languages, str], value: str) -> None:
        self._comment[lang] = value
        self._changed.add('comment')

    def rmComment(self, lang: Union[Languages, str]) -> None:
        del self._comment[lang]
        self._changed.add('comment')

    @property
    def editable(self) -> bool:
        return self._editable

    @editable.setter
    def editable(self, value: bool) -> None:
        raise BaseError('"editable" cannot be modified!')

    @property
    def linkvalue(self) -> bool:
        return self._linkvalue

    @linkvalue.setter
    def linkvalue(self) -> None:
        raise BaseError('"linkvalue" cannot be modified!')

    @classmethod
    def fromJsonObj(cls, con: Connection, context: Context,
                    json_obj: Any) -> Any:
        if isinstance(json_obj, list):
            json_obj = json_obj[
                0]  # TODO: Is it possible to have more than one element in the list??
        if not isinstance(con, Connection):
            raise BaseError(
                '"con"-parameter must be an instance of Connection')
        if not isinstance(context, Context):
            raise BaseError(
                '"context"-parameter must be an instance of Context')
        rdf = context.prefixFromIri(
            "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
        rdfs = context.prefixFromIri("http://www.w3.org/2000/01/rdf-schema#")
        owl = context.prefixFromIri("http://www.w3.org/2002/07/owl#")
        xsd = context.prefixFromIri("http://www.w3.org/2001/XMLSchema#")
        knora_api = context.prefixFromIri(
            "http://api.knora.org/ontology/knora-api/v2#")
        salsah_gui = context.prefixFromIri(
            "http://api.knora.org/ontology/salsah-gui/v2#")

        if not (json_obj.get(knora_api + ':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(':')
        id = context.iriFromPrefix(tmp_id[0]) + '#' + tmp_id[1]
        ontology_id = tmp_id[0]
        name = tmp_id[1]
        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 is not None:
            superprops: List[Any] = list(
                filter(lambda a: a.get('@id') is not None,
                       superproperties_obj))
            superproperties = list(map(lambda a: a['@id'], superprops))
        else:
            superproperties = None
        object = WithId(json_obj.get(knora_api + ':objectType')).str()
        subject = WithId(json_obj.get(knora_api + ':subjectType')).str()
        label = LangString.fromJsonLdObj(json_obj.get(rdfs + ':label'))
        comment = LangString.fromJsonLdObj(json_obj.get(rdfs + ':comment'))
        gui_element = None
        if json_obj.get(salsah_gui + ':guiElement') is not None:
            gui_element = WithId(json_obj.get(salsah_gui +
                                              ':guiElement')).str()
        gui_attributes_list = json_obj.get(salsah_gui + ':guiAttribute')
        gui_attributes: Union[None, Dict[str, str]] = None
        if gui_attributes_list is not None:
            gui_attributes = {}
            if not isinstance(gui_attributes_list, list):
                gui_attributes_list = [gui_attributes_list]
            for ga in gui_attributes_list:
                tmp = ga.split('=')
                if len(tmp) == 1:
                    gui_attributes[tmp[0]] = ''
                else:
                    gui_attributes[tmp[0]] = tmp[1]

        editable = json_obj.get(knora_api + ':isEditable')
        linkvalue = json_obj.get(knora_api + ':isLinkProperty')
        return cls(con=con,
                   context=context,
                   id=id,
                   name=name,
                   ontology_id=ontology_id,
                   superproperties=superproperties,
                   object=object,
                   subject=subject,
                   gui_element=gui_element,
                   gui_attributes=gui_attributes,
                   label=label,
                   comment=comment,
                   editable=editable,
                   linkvalue=linkvalue)

    def toJsonObj(self,
                  lastModificationDate: LastModificationDate,
                  action: Actions,
                  what: Optional[str] = None) -> Any:
        def resolve_propref(resref: str):
            tmp = resref.split(':')
            if len(tmp) > 1:
                if tmp[0]:
                    return {
                        "@id": resref
                    }  # fully qualified name in the form "prefix:name"
                else:
                    return {
                        "@id":
                        self._context.prefixFromIri(self._ontology_id) + ':' +
                        tmp[1]
                    }  # ":name" in current ontology
            else:
                return {
                    "@id": "knora-api:" + resref
                }  # no ":", must be from knora-api!

        tmp = {}
        exp = re.compile('^http.*')  # It is already a fully IRI
        if exp.match(self._ontology_id):
            propid = self._context.prefixFromIri(
                self._ontology_id) + ":" + self._name
            ontid = self._ontology_id
        else:
            propid = self._ontology_id + ":" + self._name
            ontid = self._context.iriFromPrefix(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"}]
            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 is not None:
                if not self._comment.isEmpty():
                    tmp['@graph'][0][
                        "rdfs:comment"] = self._comment.toJsonLdObj()
            if self._subject is not None:
                tmp['@graph'][0]["knora-api:subjectType"] = resolve_propref(
                    self._subject)
            if self._object is not None:
                tmp['@graph'][0]["knora-api:objectType"] = resolve_propref(
                    self._object)
            if self._gui_element is not None:
                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

    def create(
        self, last_modification_date: LastModificationDate
    ) -> Tuple[LastModificationDate, 'PropertyClass']:
        jsonobj = self.toJsonObj(last_modification_date, Actions.Create)
        jsondata = json.dumps(jsonobj, cls=SetEncoder, indent=2)
        result = self.con.post('/v2/ontologies/properties', jsondata)
        last_modification_date = LastModificationDate(
            result['knora-api:lastModificationDate'])
        return last_modification_date, PropertyClass.fromJsonObj(
            self.con, self._context, result['@graph'])

    def update(
        self, last_modification_date: LastModificationDate
    ) -> Tuple[LastModificationDate, 'ResourceClass']:
        #
        # Note: Knora 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')
            jsondata = json.dumps(jsonobj, cls=SetEncoder, indent=4)
            result = self.con.put('/v2/ontologies/properties', jsondata)
            last_modification_date = LastModificationDate(
                result['knora-api:lastModificationDate'])
            something_changed = True
        if 'comment' in self._changed:
            jsonobj = self.toJsonObj(last_modification_date, Actions.Update,
                                     'comment')
            jsondata = json.dumps(jsonobj, cls=SetEncoder, indent=4)
            result = self.con.put('/v2/ontologies/properties', jsondata)
            last_modification_date = LastModificationDate(
                result['knora-api:lastModificationDate'])
            something_changed = True
        if something_changed:
            return last_modification_date, PropertyClass.fromJsonObj(
                self.con, self._context, result['@graph'])
        else:
            return last_modification_date, self

    def delete(
            self, last_modification_date: LastModificationDate
    ) -> LastModificationDate:
        result = self.con.delete('/v2/ontologies/properties/' +
                                 quote_plus(self._id) +
                                 '?lastModificationDate=' +
                                 str(last_modification_date))
        return LastModificationDate(result['knora-api:lastModificationDate'])

    def print(self, offset: int = 0):
        blank = ' '
        print(f'{blank:>{offset}}Property Class Info')
        print(f'{blank:>{offset+2}}Name:            {self._name}')
        print(f'{blank:>{offset+2}}Ontology prefix: {self._ontology_id}')
        print(f'{blank:>{offset+2}}Superproperties:')
        if self._superproperties is not None:
            for sc in self._superproperties:
                print(f'{blank:>{offset + 4}}{sc}')
        if self._label is not None:
            print(f'{blank:>{offset + 2}}Labels:')
            self._label.print(offset + 4)
        if self._subject is not None:
            print(f'{blank:>{offset + 4}}Subject: {self._subject}')
        if self._object is not None:
            print(f'{blank:>{offset + 4}}Object: {self._object}')
        if self._gui_element is not None:
            print(f'{blank:>{offset + 4}}Guielement: {self._gui_element}')
        if self._gui_attributes is not None:
            print(f'{blank:>{offset + 4}}Gui Attributes:')
            if self._gui_attributes is not None:
                pprint(self._gui_attributes)
                for (attname, attvalue) in self._gui_attributes.items():
                    print(
                        f'{blank:>{offset + 6}}Attribute: {attname} Value: {attvalue}'
                    )
        if self._comment is not None:
            print(f'{blank:>{offset + 2}}Comments:')
            self._comment.print(offset + 4)
        if self._editable is not None:
            print(f'{blank:>{offset + 4}}Editable: {self._editable}')
        if self._linkvalue is not None:
            print(f'{blank:>{offset + 4}}Editable: {self._linkvalue}')
class Project:
    """
    This class represents a project in Knora.

    Attributes
    ----------

    con : Connection
        A Connection instance to a Knora server

    id : str
        IRI of the project [readonly, cannot be modified after creation of instance]

    shortcode : str
        Knora project shortcode [readonly, cannot be modified after creation of instance]

    shortname : str
        Knora project shortname [read/write]

    longname : str
        Knora project longname [read/write]

    description : LangString
        Knora project description in a given language (Languages.EN, Languages.DE, Languages.FR, Languages.IT).
        A desciption can be add/replaced or removed with the methods ``addDescription``and ``rmDescription``.

    keywords : Set[str]
        Set of keywords describing the project. Keywords can be added/removed by the methods ``addKeyword``
        and ``rmKeyword``

    ontologies : Set[str]
        Set if IRI's of te ontologies attached to the project [readonly]

    selfjoin : bool
        Boolean if the project allows selfjoin


    Methods
    -------

    create : Knora project information object
        Creates a new project and returns the information from the project as it is in Knora

    read : Knora project information object
        Read project data from an existing project

    update : Knora project information object
        Updates the changed attributes and returns the updated information from the project as it is in Knora

    delete : Knora result code
        Deletes a project and returns the result code

    getAllprojects [static]: List of all projects
        Returns a list of all projects available

    print : None
        Prints the project information to stdout

    """
    _id: str
    _shortcode: str
    _shortname: str
    _longname: str
    _description: LangString
    _keywords: Set[str]
    _ontologies: Set[str]
    _selfjoin: bool
    _status: bool
    _logo: Optional[str]
    changed: Set[str]

    SYSTEM_PROJECT: str = "http://www.knora.org/ontology/knora-admin#SystemProject"

    def __init__(self,
                 con: Connection,
                 id: Optional[str] = None,
                 shortcode: Optional[str] = None,
                 shortname: Optional[str] = None,
                 longname: Optional[str] = None,
                 description: LangStringParam = None,
                 keywords: Optional[Set[str]] = None,
                 ontologies: Optional[Set[str]] = None,
                 selfjoin: Optional[bool] = None,
                 status: Optional[bool] = None,
                 logo: Optional[str] = None):
        """
        Constructor for Project

        :param con: Connection instance
        :param id: IRI of the project [required for CREATE, READ]
        :param shortcode: Shortcode of the project. String inf the form 'XXXX' where each X is a hexadezimal sign 0-1,A,B,C,D,E,F. [required for CREATE]
        :param shortname: Shortname of the project [required for CREATE]
        :param longname: Longname of the project [required for CREATE]
        :param description: LangString instance containing the description [required for CREATE]
        :param keywords: Set of keywords [required for CREATE]
        :param ontologies: Set of ontologies that belong to this project [optional]
        :param selfjoin: Allow selfjoin [required for CREATE]
        :param status: Status of project (active if True) [required for CREATE]
        :param logo: Path to logo image file [optional] NOT YET USED
        """

        if not isinstance(con, Connection):
            raise BaseError(
                '"con"-parameter must be an instance of Connection')
        self.con = con
        self._id = id
        self._shortcode = shortcode
        self._shortname = shortname
        self._longname = longname
        self._description = LangString(description)
        self._keywords = keywords
        if not isinstance(ontologies, set) and ontologies is not None:
            raise BaseError('Ontologies must be a set of strings or None!')
        self._ontologies = ontologies
        self._selfjoin = selfjoin
        self._status = status
        self._logo = logo
        self.changed = set()

    def __str__(self):
        tmpstr = self._id + "\n  " + self._shortcode + "\n  " + self._shortname
        return tmpstr

    #
    # Here follows a list of getters/setters
    #
    @property
    def id(self) -> Optional[str]:
        return self._id

    @id.setter
    def id(self, value: str) -> None:
        raise BaseError('Project id cannot be modified!')

    @property
    def shortcode(self) -> Optional[str]:
        return self._shortcode

    @shortcode.setter
    def shortcode(self, value: str) -> None:
        raise BaseError('Shortcode id cannot be modified!')

    @property
    def shortname(self) -> Optional[str]:
        return self._shortname

    @shortname.setter
    def shortname(self, value: str) -> None:
        self._shortname = str(value)
        self.changed.add('shortname')

    @property
    def longname(self) -> Optional[str]:
        return self._longname

    @longname.setter
    def longname(self, value: str) -> None:
        self._longname = str(value)
        self.changed.add('longname')

    @property
    def description(self) -> Optional[LangString]:
        return self._description

    @description.setter
    def description(self, value: Optional[LangString]) -> None:
        self._description = LangString(value)
        self.changed.add('description')

    def addDescription(self, lang: Union[Languages, str], value: str) -> None:
        """
        Add/replace a project description with the given language (executed at next update)

        :param lang: The language the description is in, either a string "EN", "DE", "FR", "IT" or a Language instance
        :param value: The text of the description
        :return: None
        """

        self._description[lang] = value
        self.changed.add('description')

    def rmDescription(self, lang: Union[Languages, str]) -> None:
        """
        Remove a description from a project (executed at next update)

        :param lang: The language the description to be removed is in, either a string "EN", "DE", "FR", "IT" or a Language instance
        :return: None
        """

        del self._description[lang]
        self.changed.add('description')

    @property
    def keywords(self) -> Set[str]:
        return self._keywords

    @keywords.setter
    def keywords(self, value: Union[List[str], Set[str]]):
        if isinstance(value, set):
            self._keywords = value
            self.changed.add('keywords')
        elif isinstance(value, list):
            self._keywords = set(value)
            self.changed.add('keywords')
        else:
            raise BaseError('Must be a set of strings!')

    def addKeyword(self, value: str):
        """
        Add a new keyword to the set of keywords. (executed at next update)
        May raise a BaseError

        :param value: keyword
        :return: None
        """

        self._keywords.add(value)
        self.changed.add('keywords')

    def rmKeyword(self, value: str):
        """
        Remove a keyword from the list of keywords (executed at next update)
        May raise a BaseError

        :param value: keyword
        :return: None
        """
        try:
            self._keywords.remove(value)
        except KeyError as ke:
            raise BaseError('Keyword "' + value + '" is not in keyword set')
        self.changed.add('keywords')

    @property
    def ontologies(self) -> Set[str]:
        return self._ontologies

    @ontologies.setter
    def ontologies(self, value: Set[str]) -> None:
        raise BaseError('Cannot add a ontology!')

    @property
    def selfjoin(self) -> Optional[bool]:
        return self._selfjoin

    @selfjoin.setter
    def selfjoin(self, value: bool) -> None:
        if self._selfjoin != value:
            self.changed.add('selfjoin')
        self._selfjoin = value

    @property
    def status(self) -> bool:
        return self._status

    @status.setter
    def status(self, value: bool) -> None:
        self._status = value
        self.changed.add('status')

    @property
    def logo(self) -> str:
        return self._logo

    @logo.setter
    def logo(self, value: str) -> None:
        self._logo = value
        self.changed.add('logo')

    @classmethod
    def fromJsonObj(cls, con: Connection, json_obj: Any) -> Any:
        """
        Internal method! Should not be used directly!

        This method is used to create a Project instance from the JSON data returned by Knora

        :param con: Connection instance
        :param json_obj: JSON data returned by Knora as python3 object
        :return: Project instance
        """

        id = json_obj.get('id')
        if id is None:
            raise BaseError('Project id is missing')
        shortcode = json_obj.get('shortcode')
        if shortcode is None:
            raise BaseError("Shortcode is missing")
        shortname = json_obj.get('shortname')
        if shortname is None:
            raise BaseError("Shortname is missing")
        longname = json_obj.get('longname')
        if longname is None:
            raise BaseError("Longname is missing")
        description = LangString.fromJsonObj(json_obj.get('description'))
        keywords = set(json_obj.get('keywords'))
        if keywords is None:
            raise BaseError("Keywords are missing")
        ontologies = set(json_obj.get('ontologies'))
        if ontologies is None:
            raise BaseError("Keywords are missing")
        selfjoin = json_obj.get('selfjoin')
        if selfjoin is None:
            raise BaseError("Selfjoin is missing")
        status = json_obj.get('status')
        if status is None:
            raise BaseError("Status is missing")
        logo = json_obj.get('logo')
        return cls(con=con,
                   id=id,
                   shortcode=shortcode,
                   shortname=shortname,
                   longname=longname,
                   description=description,
                   keywords=keywords,
                   ontologies=ontologies,
                   selfjoin=selfjoin,
                   status=status,
                   logo=logo)

    def toJsonObj(self, action: Actions) -> Any:
        """
        Internal method! Should not be used directly!

        Creates a JSON-object from the Project instance that can be used to call Knora

        :param action: Action the object is used for (Action.CREATE or Action.UPDATE)
        :return: JSON-object
        """

        tmp = {}
        if action == Actions.Create:
            if self._shortcode is None:
                raise BaseError("There must be a valid project shortcode!")
            tmp['shortcode'] = self._shortcode
            if self._shortname is None:
                raise BaseError("There must be a valid project shortname!")
            tmp['shortname'] = self._shortname
            if self._longname is None:
                raise BaseError("There must be a valid project longname!")
            tmp['longname'] = self._longname
            if self._description.isEmpty():
                raise BaseError("There must be a valid project description!")
            tmp['description'] = self._description.toJsonObj()
            if len(self._keywords) > 0:
                tmp['keywords'] = self._keywords
            if self._selfjoin is None:
                raise BaseError("selfjoin must be defined (True or False!")
            tmp['selfjoin'] = self._selfjoin
            if self._status is None:
                raise BaseError("status must be defined (True or False!")
            tmp['status'] = self._status
        elif action == Actions.Update:
            if self._shortcode is not None and 'shortcode' in self.changed:
                tmp['shortcode'] = self._shortcode
            if self._shortname is not None and 'shortname' in self.changed:
                tmp['shortname'] = self._shortname
            if self._longname is not None and 'longname' in self.changed:
                tmp['longname'] = self._longname
            if not self._description.isEmpty(
            ) and 'description' in self.changed:
                tmp['description'] = self._description.toJsonObj()
            if len(self._keywords) > 0 and 'keywords' in self.changed:
                tmp['keywords'] = self._keywords
            if self._selfjoin is not None and 'selfjoin' in self.changed:
                tmp['selfjoin'] = self._selfjoin
            if self._status is not None and 'status' in self.changed:
                tmp['status'] = self._status
        return tmp

    def create(self) -> 'Project':
        """
        Create a new project in Knora

        :return: JSON-object from Knora
        """

        jsonobj = self.toJsonObj(Actions.Create)
        jsondata = json.dumps(jsonobj, cls=SetEncoder)
        result = self.con.post('/admin/projects', jsondata)
        return Project.fromJsonObj(self.con, result['project'])

    def read(self) -> 'Project':
        """
        Read a project from Knora

        :return: JSON-object from Knora
        """
        if self._id is not None:
            result = self.con.get('/admin/projects/iri/' +
                                  quote_plus(self._id))
        elif self._shortcode is not None:
            result = self.con.get('/admin/projects/shortcode/' +
                                  quote_plus(self._shortcode))
        return Project.fromJsonObj(self.con, result['project'])

    def update(self) -> Union['Project', None]:
        """
        Udate the project info in Knora with the modified data in this project instance

        :return: JSON-object from Knora refecting the update
        """

        jsonobj = self.toJsonObj(Actions.Update)
        if jsonobj:
            jsondata = json.dumps(jsonobj, cls=SetEncoder)
            result = self.con.put('/admin/projects/iri/' + quote_plus(self.id),
                                  jsondata)
            return Project.fromJsonObj(self.con, result['project'])
        else:
            return None

    def delete(self) -> 'Project':
        """
        Delete the given Knora project

        :return: Knora response
        """

        result = self.con.delete('/admin/projects/iri/' + quote_plus(self._id))
        return Project.fromJsonObj(self.con, result['project'])

    @staticmethod
    def getAllProjects(con: Connection) -> List['Project']:
        """
        Get all existing projects in Knora

        :param con: Connection instance
        :return:
        """
        result = con.get('/admin/projects')
        if 'projects' not in result:
            raise BaseError("Request got no projects!")
        return list(
            map(lambda a: Project.fromJsonObj(con, a), result['projects']))

    def print(self):
        """
        print info to stdout

        :return: None
        """

        print('Project Info:')
        print('  Id:         {}'.format(self._id))
        print('  Shortcode:  {}'.format(self._shortcode))
        print('  Shortname:  {}'.format(self._shortname))
        print('  Longname:   {}'.format(self._longname))
        if self._description is not None:
            print('  Description:')
            for descr in self._description.items():
                print('    {}: {}'.format(descr[0], descr[1]))
        else:
            print('  Description: None')
        if self._keywords is not None:
            print('  Keywords:   {}'.format(' '.join(self._keywords)))
        else:
            print('  Keywords:   None')
        if self._ontologies is not None:
            print('  Ontologies: {}'.format(' '.join(self._ontologies)))
        else:
            print('  Ontologies: None')
        print('  Selfjoin:   {}'.format(self._selfjoin))
        print('  Status:     {}'.format(self._status))
示例#3
0
class ResourceClass:
    con: Connection
    _context: Context
    _id: str
    _name: str
    _ontology_id: str
    _superclasses: List[str]
    _label: LangString
    _comment: LangString
    _permissions: str
    _has_properties: Dict[str, HasProperty]
    _changed: Set[str]

    def __init__(self,
                 con: Connection,
                 context: Context,
                 id: Optional[str] = None,
                 name: Optional[str] = None,
                 ontology_id: Optional[str] = None,
                 superclasses: Optional[List[Union['ResourceClass',
                                                   str]]] = None,
                 label: Optional[Union[LangString, str]] = None,
                 comment: Optional[Union[LangString, str]] = None,
                 permissions: Optional[str] = None,
                 has_properties: Optional[Dict[str, HasProperty]] = None):
        if not isinstance(con, Connection):
            raise BaseError(
                '"con"-parameter must be an instance of Connection')
        if not isinstance(context, Context):
            raise BaseError(
                '"context"-parameter must be an instance of Context')
        self.con = con
        self._context = context
        self._id = id
        self._name = name
        self._ontology_id = ontology_id
        if isinstance(superclasses, ResourceClass):
            self._superclasses = list(map(lambda a: a.id, superclasses))
        else:
            self._superclasses = superclasses
        #
        # 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 = None
        #
        # 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 = None
        self._permissions = permissions
        self._has_properties = has_properties
        self._changed = set()

    #
    # Here follows a list of getters/setters
    #
    @property
    def name(self) -> Optional[str]:
        return self._name

    @name.setter
    def name(self, value: str) -> None:
        raise BaseError('"name" cannot be modified!')

    @property
    def id(self) -> Optional[str]:
        return self._id

    @id.setter
    def id(self, value: str) -> None:
        raise BaseError('"id" cannot be modified!')

    @property
    def ontology_id(self) -> Optional[str]:
        return self._ontology_id

    @ontology_id.setter
    def ontology_id(self, value: str) -> None:
        raise BaseError('"ontology_id" cannot be modified!')

    @property
    def superclasses(self) -> Optional[List[str]]:
        return self._superclasses

    @superclasses.setter
    def superclasses(self, value: List[str]) -> None:
        raise BaseError('"superclasses" cannot be modified!')

    @property
    def label(self) -> Optional[LangString]:
        return self._label

    @label.setter
    def label(self, value: Optional[Union[LangString, str]]) -> None:
        if value is None:
            self._label.empty()  # clear all labels
        else:
            if isinstance(value, LangString):
                self._label = value
            elif isinstance(value, str):
                self._label = LangString(value)
            else:
                raise BaseError('Not a valid LangString')
        self._changed.add('label')

    def addLabel(self, lang: Union[Languages, str], value: str) -> None:
        self._label[lang] = value
        self._changed.add('label')

    def rmLabel(self, lang: Union[Languages, str]) -> None:
        del self._label[lang]
        self._changed.add('label')

    @property
    def comment(self) -> Optional[LangString]:
        return self._comment

    @comment.setter
    def comment(self, value: Optional[LangString]) -> None:
        if value is None:
            self._comment.empty()  # clear all comments!
        else:
            if isinstance(value, LangString):
                self._comment = value
            elif isinstance(value, str):
                self._comment = LangString(value)
            else:
                raise BaseError('Not a valid LangString')
        self._changed.add('comment')

    def addComment(self, lang: Union[Languages, str], value: str) -> None:
        self._comment[lang] = value
        self._changed.add('comment')

    def rmComment(self, lang: Union[Languages, str]) -> None:
        del self._comment[lang]
        self._changed.add('comment')

    @property
    def permissions(self) -> Optional[str]:
        return self._permissions

    @permissions.setter
    def permissions(self, value: str) -> None:
        raise BaseError('"permissions" cannot be modified!')

    @property
    def has_property(self) -> Dict[str, HasProperty]:
        return self._has_properties

    @has_property.setter
    def has_property(self, value: str) -> None:
        raise BaseError('"has_property" cannot be modified!')

    def getProperty(self, property_id) -> Optional[HasProperty]:
        if self._has_properties is None:
            return None
        else:
            return self._has_properties.get(
                self._context.getPrefixIri(property_id))

    def addProperty(
        self, property_id: str, cardinality: Cardinality,
        last_modification_date: LastModificationDate
    ) -> Optional[LastModificationDate]:
        if self._has_properties.get(property_id) is None:
            latest_modification_date, resclass = HasProperty(
                con=self.con,
                context=self._context,
                ontology_id=self._ontology_id,
                property_id=property_id,
                resclass_id=self.id,
                cardinality=cardinality).create(last_modification_date)
            hp = resclass.getProperty(property_id)
            hp.ontology_id = self._context.iriFromPrefix(self._ontology_id)
            hp.resclass_id = self.id
            self._has_properties[hp.property_id] = hp
            return latest_modification_date
        else:
            raise BaseError("Property already has cardinality in this class!")

    def updateProperty(
        self, property_id: str, cardinality: Cardinality,
        last_modification_date: LastModificationDate
    ) -> Optional[LastModificationDate]:
        property_id = self._context.getPrefixIri(property_id)
        if self._has_properties.get(property_id) is not None:
            has_properties = self._has_properties[property_id]
            onto_id = has_properties.ontology_id  # save for later user
            rescl_id = has_properties.resclass_id  # save for later user
            has_properties.cardinality = cardinality
            latest_modification_date, resclass = has_properties.update(
                last_modification_date)
            hp = resclass.getProperty(property_id)
            hp.ontology_id = self._context.iriFromPrefix(
                onto_id)  # restore value
            hp.resclass_id = rescl_id  # restore value
            self._has_properties[hp.property_id] = hp
            return latest_modification_date
        else:
            return last_modification_date

    @classmethod
    def fromJsonObj(cls, con: Connection, context: Context,
                    json_obj: Any) -> Any:
        if isinstance(json_obj, list):
            json_obj = json_obj[
                0]  # TODO: Is it possible to have more than one element in the list??
        if not isinstance(con, Connection):
            raise BaseError(
                '"con"-parameter must be an instance of Connection')
        if not isinstance(context, Context):
            raise BaseError(
                '"context"-parameter must be an instance of Context')
        rdf = context.prefixFromIri(
            "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
        rdfs = context.prefixFromIri("http://www.w3.org/2000/01/rdf-schema#")
        owl = context.prefixFromIri("http://www.w3.org/2002/07/owl#")
        xsd = context.prefixFromIri("http://www.w3.org/2001/XMLSchema#")
        knora_api = context.prefixFromIri(
            "http://api.knora.org/ontology/knora-api/v2#")
        salsah_gui = context.prefixFromIri(
            "http://api.knora.org/ontology/salsah-gui/v2#")

        if not (json_obj.get(knora_api + ':isResourceClass')
                or json_obj.get(knora_api + ':isStandoffClass')):
            raise BaseError("This is not a resource!")

        if json_obj.get('@id') is None:
            raise BaseError('Resource class has no "@id"!')
        tmp_id = json_obj.get('@id').split(':')
        id = context.iriFromPrefix(tmp_id[0]) + '#' + tmp_id[1]
        ontology_id = tmp_id[0]
        name = tmp_id[1]
        superclasses_obj = json_obj.get(rdfs + ':subClassOf')
        if superclasses_obj is not None:
            supercls: List[Any] = list(
                filter(lambda a: a.get('@id') is not None, superclasses_obj))
            superclasses: List[str] = list(map(lambda a: a['@id'], supercls))
            has_props: List[Any] = list(
                filter(lambda a: a.get('@type') == (owl + ':Restriction'),
                       superclasses_obj))
            has_properties: Dict[HasProperty] = dict(
                map(lambda a: HasProperty.fromJsonObj(con, context, a),
                    has_props))
        else:
            superclasses = None
            has_properties = None

        label = LangString.fromJsonLdObj(json_obj.get(rdfs + ':label'))
        comment = LangString.fromJsonLdObj(json_obj.get(rdfs + ':comment'))
        return cls(con=con,
                   context=context,
                   name=name,
                   id=id,
                   ontology_id=ontology_id,
                   superclasses=superclasses,
                   label=label,
                   comment=comment,
                   has_properties=has_properties)

    def toJsonObj(self,
                  lastModificationDate: LastModificationDate,
                  action: Actions,
                  what: Optional[str] = None) -> Any:
        def resolve_resref(resref: str):
            tmp = resref.split(':')
            if len(tmp) > 1:
                if tmp[0]:
                    return {
                        "@id": resref
                    }  # fully qualified name in the form "prefix:name"
                else:
                    return {
                        "@id":
                        self._context.prefixFromIri(self._ontology_id) + ':' +
                        tmp[1]
                    }  # ":name" in current ontology
            else:
                return {
                    "@id": "knora-api:" + resref
                }  # no ":", must be from knora-api!

        tmp = {}
        exp = re.compile('^http.*')  # It is already a fully IRI
        if exp.match(self._ontology_id):
            resid = self._context.prefixFromIri(
                self._ontology_id) + ":" + self._name
            ontid = self._ontology_id
        else:
            resid = self._ontology_id + ":" + self._name
            ontid = self._context.iriFromPrefix(self._ontology_id)
        if action == Actions.Create:
            if self._name is None:
                raise BaseError("There must be a valid resource class name!")
            if self._ontology_id is None:
                raise BaseError("There must be a valid ontology_id given!")
            if self._superclasses is None:
                superclasses = [{"@id": "knora-api:Resource"}]
            else:
                superclasses = list(map(resolve_resref, self._superclasses))
            if self._comment is None or self._comment.isEmpty():
                self._comment = LangString("no comment vailable")
            tmp = {
                "@id":
                ontid,  # self._ontology_id,
                "@type":
                "owl:Ontology",
                "knora-api:lastModificationDate":
                lastModificationDate.toJsonObj(),
                "@graph": [{
                    "@id": resid,
                    "@type": "owl:Class",
                    "rdfs:label": self._label.toJsonLdObj(),
                    "rdfs:comment": self._comment.toJsonLdObj(),
                    "rdfs:subClassOf": superclasses
                }],
                "@context":
                self._context.toJsonObj(),
            }
        elif action == Actions.Update:
            tmp = {
                "@id": ontid,  # self._ontology_id,
                "@type": "owl:Ontology",
                "knora-api:lastModificationDate":
                lastModificationDate.toJsonObj(),
                "@graph": [{
                    "@id": resid,
                    "@type": "owl:Class",
                }],
                "@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

    def create(
        self, last_modification_date: LastModificationDate
    ) -> Tuple[LastModificationDate, 'ResourceClass']:
        jsonobj = self.toJsonObj(last_modification_date, Actions.Create)
        jsondata = json.dumps(jsonobj)
        result = self.con.post('/v2/ontologies/classes', jsondata)
        last_modification_date = LastModificationDate(
            result['knora-api:lastModificationDate'])
        return last_modification_date, ResourceClass.fromJsonObj(
            self.con, self._context, result['@graph'])

    def update(
        self, last_modification_date: LastModificationDate
    ) -> Tuple[LastModificationDate, 'ResourceClass']:
        #
        # Note: Knora 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')
            jsondata = json.dumps(jsonobj, cls=SetEncoder, indent=4)
            result = self.con.put('v2/ontologies/classes', jsondata)
            last_modification_date = LastModificationDate(
                result['knora-api:lastModificationDate'])
            something_changed = True
        if 'comment' in self._changed:
            jsonobj = self.toJsonObj(last_modification_date, Actions.Update,
                                     'comment')
            jsondata = json.dumps(jsonobj, cls=SetEncoder, indent=4)
            result = self.con.put('v2/ontologies/classes', jsondata)
            last_modification_date = LastModificationDate(
                result['knora-api:lastModificationDate'])
            something_changed = True
        if something_changed:
            return last_modification_date, ResourceClass.fromJsonObj(
                self.con, self._context, result['@graph'])
        else:
            return last_modification_date, self

    def delete(
            self, last_modification_date: LastModificationDate
    ) -> LastModificationDate:
        result = self.con.delete('v2/ontologies/classes/' +
                                 quote_plus(self._id) +
                                 '?lastModificationDate=' +
                                 str(last_modification_date))
        return LastModificationDate(result['knora-api:lastModificationDate'])

    def print(self, offset: int = 0):
        blank = ' '
        print(f'{blank:>{offset}}Resource Class Info')
        print(f'{blank:>{offset+2}}Name:            {self._name}')
        print(f'{blank:>{offset+2}}Ontology prefix: {self._ontology_id}')
        print(f'{blank:>{offset+2}}Superclasses:')
        if self._superclasses is not None:
            for sc in self._superclasses:
                print(f'{blank:>{offset + 4}}{sc}')
        if self._label is not None:
            print(f'{blank:>{offset + 2}}Labels:')
            self._label.print(offset + 4)
        if self._comment is not None:
            print(f'{blank:>{offset + 2}}Comments:')
            self._comment.print(offset + 4)
        if self._has_properties is not None:
            print(f'{blank:>{offset + 2}}Has properties:')
            if self._has_properties is not None:
                for pid, hp in self._has_properties.items():
                    hp.print(offset + 4)
示例#4
0
class ListNode:
    """
    This class represents a list node or a while list from Knora

    Attributes
    ----------

    con : Connection
        A Connection instance to a Knora server (for some operation a login has to be performedwith valid credentials)

    id : str
        IRI of the project [readonly, cannot be modified after creation of instance]

    project : str
        IRI of project. Only used for the creation of a new list (root node) [write].

    label : LangString
        A LangString instance with language depenedent labels. Setting this attributes overwites all entries
        with the new ones. In order to add/remove a specific entry, use "addLabel" or "rmLabel".
        At least one label is required [read/write].

    comment : LangString
        A LangString instance with language depenedent comments. Setting this attributes overwites all entries
        with the new ones.In order to add/remove a specific entry, use "addComment" or "rmComment".

    name : str
        A unique name for the ListNode (unique regarding the whole list) [read/write].

    parent : IRI | ListNode
        Is required and allowed only for the CREATE operation. Otherwise use the
        "children" attribute [write].

    isRootNode : bool
        Is True if the ListNode is a root node of a list Cannot be set [read].

    children : List[ListNode]
        Contains a list of children nodes. This attribute is only avaliable for nodes that have been read by the
        method "getAllNodes()"! [read]

    rootNodeIri : str
        IRI of the root node. This attribute is only avaliable for nodes that have been read by the
        method "getAllNodes()"! [read]

    Methods
    -------

    create : Knora ListNode information object
        Creates a new project and returns the information from the project as it is in Knora. Used to create new lists
        or append new ListNodes to an existing list. If appending, the attribute "parent" must not be None!

    read : Knora ListNode information object
        Read single list node

    update : Knora ListNode information object
        Updates the changed attributes and returns the updated information from the ListNode as it is in Knora

    delete : Knora result code
        Deletes a ListNode and returns the result code [NOT YET IMPLEMENTED!]

    getAllNodes : ListNode
        Get all nodes of a list. The IRI of the root node must be supplied.

    getAllLists [static]:
        returns all lists of a given project.

    print : None
        Prints the ListNode information to stdout (no recursion for children!)

    """

    _id: str
    _project: str
    _label: LangString
    _comment: LangString
    _name: str
    _parent: str
    _isRootNode: bool
    _children: List['ListNode']
    _rootNodeIri: str
    changed: Set[str]

    def __init__(self,
                 con: Connection,
                 id: Optional[str] = None,
                 project: Optional[Union[Project, str]] = None,
                 label: LangStringParam = None,
                 comment: LangStringParam = None,
                 name: Optional[str] = None,
                 parent: Optional[Union['ListNode', str]] = None,
                 isRootNode: Optional[bool] = None,
                 children: Optional[List['ListNode']] = None,
                 rootNodeIri: Optional[str] = None):
        """
        This is the constructor for the ListNode object. For

        CREATE:
            * The "con" and at least one "label" are required
        READ:
            * The "con" and "id" attributes are required
        UPDATE:
            * Only "label", "comment" and "name" may be changed
        DELETE:
            * Not yet implemented in the Knora-backend

        :param con: A valid Connection instance with a user logged in that has the appropriate permissions
        :param id: IRI of the project [readonly, cannot be modified after creation of instance]
        :param project: IRI of project. Only used for the creation of a new list (root node) [write].
        :param label: A LangString instance with language depenedent labels. Setting this attributes overwites all entries with the new ones. In order to add/remove a specific entry, use "addLabel" or "rmLabel". At least one label is required [read/write].
        :param comment: A LangString instance with language depenedent comments. Setting this attributes overwites all entries with the new ones.In order to add/remove a specific entry, use "addComment" or "rmComment".
        :param name: A unique name for the ListNode (unique regarding the whole list) [read/write].
        :param parent: Is required and allowed only for the CREATE operation. Otherwise use the "children" attribute [write].
        :param isRootNode: Is True if the ListNode is a root node of a list Cannot be set [read].
        :param children: Contains a list of children nodes. This attribute is only avaliable for nodes that have been read by the method "getAllNodes()"! [read]
        :param rootNodeIri: IRI of the root node. This attribute is only avaliable for nodes that have been read by the method "getAllNodes()"! [read]
        """

        if not isinstance(con, Connection):
            raise BaseError ('"con"-parameter must be an instance of Connection')
        self.con = con

        self._project = project.id if isinstance(project, Project) else str(project) if project is not None else None
        self._id = str(id) if id is not None else None
        self._label = LangString(label)
        self._comment = LangString(comment)
        self._name = str(name) if name is not None else None
        if not isinstance(parent, ListNode) and parent is not None:
            raise BaseError('Parent must be ListNode instance or None!')
        if isinstance(parent, ListNode):
            self._parent = parent.id
        else:
            self._parent = parent
        self._isRootNode = isRootNode
        if children is not None:
            if isinstance(children, List) and len(children) > 0 and isinstance(children[0], ListNode):
                self._children = children
            else:
                raise BaseError('Children must be list of ListNodes!')
        else:
            self._children = None
        if not isinstance(rootNodeIri, str) and rootNodeIri is not None:
            raise BaseError('rootNodeIri must be a str!')
        self._rootNodeIri = rootNodeIri
        self.changed = set()

    #
    # Here follows a list of getters/setters
    #
    @property
    def id(self) -> Optional[str]:
        return self._id

    @id.setter
    def id(self, value: str) -> None:
        raise BaseError('ListNode id cannot be modified!')

    @property
    def project(self) -> Optional[str]:
        return self._project

    @project.setter
    def project(self, value: str) -> None:
        raise BaseError('Project id cannot be modified!')

    @property
    def label(self) -> Optional[LangString]:
        return self._label

    @label.setter
    def label(self, value: Optional[Union[LangString, str]]) -> None:
        self._label = LangString(value)
        self.changed.add('label')

    def addLabel(self, lang: Union[Languages, str], value: str) -> None:
        """
        Add/replace a node label with the given language (executed at next update)

        :param lang: The language the label, either a string "EN", "DE", "FR", "IT" or a Language instance
        :param value: The text of the label
        :return: None
        """

        self._label[lang] = value
        self.changed.add('label')

    def rmLabel(self, lang: Union[Languages, str]) -> None:
        """
        Remove a label from a list node (executed at next update)

        :param lang: The language the label to be removed is in, either a string "EN", "DE", "FR", "IT" or a Language instance
        :return: None
        """
        del self._label[lang]
        self.changed.add('label')

    @property
    def comment(self) -> Optional[LangString]:
        return self._comment

    @comment.setter
    def comment(self,  value: Optional[Union[LangString, str]]) -> None:
        self._comment = LangString(value)
        self.changed.add('comment')

    def addComment(self, lang: Union[Languages, str], value: str) -> None:
        """
        Add/replace a node comment with the given language (executed at next update)

        :param lang: The language the comment, either a string "EN", "DE", "FR", "IT" or a Language instance
        :param value: The text of the comment
        :return: None
        """

        self._comment[lang] = value
        self.changed.add('comment')

    def rmComment(self, lang: Union[Languages, str]) -> None:
        """
        Remove a comment from a list node (executed at next update)

        :param lang: The language the comment to be removed is in, either a string "EN", "DE", "FR", "IT" or a Language instance
        :return: None
        """
        del self._comment[lang]
        self.changed.add('comment')

    @property
    def name(self) -> Optional[str]:
        return self._name

    @name.setter
    def name(self, value: str) -> None:
        self._name = str(value)
        self.changed.add('name')

    @property
    def parent(self) -> Optional[str]:
        raise BaseError('Property parent cannot be read!')

    @parent.setter
    def parent(self, value: any):
        raise BaseError('Property parent cannot be set!')

    @property
    def isRootNode(self) -> Optional[bool]:
        return self._isRootNode

    @isRootNode.setter
    def isRootNode(self, value: bool) -> None:
        raise BaseError('Property isRootNode cannot be set!')

    @property
    def children(self) -> Optional[List['ListNode']]:
        return self._children

    @staticmethod
    def _getChildren(con: Connection, children: List[Any]) -> Optional[List['ListNode']]:
        """
        Internal method! Should not be used directly!

        Static method gets a recursive List of children nodes

        :param con: Valid Connection instance
        :param children: json object of children
        :return: List of ListNode instances
        """

        if len(children) == 0:
            return None
        child_nodes = []
        for child in children:
            child_nodes.append(ListNode.fromJsonObj(con, child))
        return child_nodes

    @property
    def rootNodeIri(self) -> Optional[str]:
        return self._rootNodeIri

    @rootNodeIri.setter
    def rootNodeIri(self, value: str):
        raise BaseError('rootNodeIri cannot be set!')

    @classmethod
    def fromJsonObj(cls, con: Connection, json_obj: Any) -> Any:
        """
        Internal method! Should not be used directly!

        This method is used to create a ListNode instance from the JSON data returned by Knora

        :param con: Connection instance
        :param json_obj: JSON data returned by Knora as python3 object
        :return: ListNode instance
        """

        id = json_obj.get('id')
        project = json_obj.get('projectIri')
        if id is None:
            raise BaseError('ListNode id is missing')

        label = LangString.fromJsonObj(json_obj.get('labels'))
        comment = LangString.fromJsonObj(json_obj.get('comments'))
        name = json_obj.get('name')
        parent = json_obj.get('parentNodeIri')
        isRootNode = json_obj.get('isRootNode')

        child_info = json_obj.get('children')
        children = None
        if child_info is not None:
            children = ListNode._getChildren(con, child_info)

        rootNodeIri = json_obj.get('hasRootNode')

        return cls(con=con,
                   id=id,
                   project=project,
                   label=label,
                   comment=comment,
                   name=name,
                   parent=parent,
                   isRootNode=isRootNode,
                   children=children,
                   rootNodeIri=rootNodeIri)

    def toJsonObj(self, action: Actions, listIri: str = None) -> Any:
        """
        Internal method! Should not be used directly!

        Creates a JSON-object from the ListNode instance that can be used to call Knora

        :param action: Action the object is used for (Action.CREATE or Action.UPDATE)
        :return: JSON-object
        """

        tmp = {}
        if action == Actions.Create:
            if self._project is None:
                raise BaseError("There must be a project id given!")
            tmp['projectIri'] = self._project
            if self._label.isEmpty():
                raise BaseError("There must be a valid ListNode label!")
            tmp['labels'] = self._label.toJsonObj()
            if not self._comment.isEmpty():
                tmp['comments'] = self._comment.toJsonObj()
            else:
                tmp['comments'] = []
            if self._name is not None:
                tmp['name'] = self._name
            if self._parent is not None:
                tmp['parentNodeIri'] = self._parent
        elif action == Actions.Update:
            if self.id is None:
                raise BaseError("There must be a node id given!")
            tmp['listIri'] = listIri
            if self._project is None:
                raise BaseError("There must be a project id given!")
            tmp['projectIri'] = self._project
            if not self._label.isEmpty() and 'label' in self.changed:
                tmp['labels'] = self._label.toJsonObj()
            if not self._comment.isEmpty() and 'comment' in self.changed:
                tmp['comments'] = self._comment.toJsonObj()
            if self._name is not None and 'name' in self.changed:
                tmp['name'] = self._name
        return tmp

    def create(self) -> 'ListNode':
        """
        Create a new List in Knora

        :return: JSON-object from Knora
        """

        jsonobj = self.toJsonObj(Actions.Create)
        jsondata = json.dumps(jsonobj, cls=SetEncoder)
        if self._parent is not None:
            result = self.con.post('/admin/lists/'+ quote_plus(self._parent), jsondata)
            return ListNode.fromJsonObj(self.con, result['nodeinfo'])
        else:
            result = self.con.post('/admin/lists', jsondata)
            return ListNode.fromJsonObj(self.con, result['list']['listinfo'])

    def read(self) -> Any:
        """
        Read a project from Knora

        :return: JSON-object from Knora
        """

        result = self.con.get('/admin/lists/nodes/' + quote_plus(self._id))
        return self.fromJsonObj(self.con, result['nodeinfo'])

    def update(self) -> Union[Any, None]:
        """
        Udate the ListNode info in Knora with the modified data in this ListNode instance

        :return: JSON-object from Knora refecting the update
        """

        jsonobj = self.toJsonObj(Actions.Update, self.id)
        if jsonobj:
            jsondata = json.dumps(jsonobj, cls=SetEncoder)
            result = self.con.put('/admin/lists/infos/' + quote_plus(self.id), jsondata)
            return ListNode.fromJsonObj(self.con, result['listinfo'])
        else:
            return None

    def delete(self) -> None:
        """
        Delete the given ListNode

        :return: Knora response
        """
        raise BaseError("NOT YET IMPLEMENTED BY KNORA BACKEND!")
        result = self.con.delete('/admin/lists/' + quote_plus(self._id))
        return result
        #return Project.fromJsonObj(self.con, result['project'])

    def getAllNodes(self):
        """
        Get all nodes of the list. Mus be called from a ListNode instance that has at least set the
        list iri!

        :return: Root node of list with recursive ListNodes ("children"-attributes)
        """

        result = self.con.get('/admin/lists/' + quote_plus(self._id))
        if 'list' not in result:
            raise BaseError("Request got no list!")
        if 'listinfo' not in result['list']:
            raise BaseError("Request got no proper list information!")
        root = ListNode.fromJsonObj(self.con, result['list']['listinfo'])
        if 'children' in result['list']:
            root._children = ListNode._getChildren(self.con, result['list']['children'])
        return root

    @staticmethod
    def getAllLists(con: Connection, project_iri: str) -> List['ListNode']:
        """
        Get all lists of the specified project

        :param con: Connection instance
        :param project_iri: Iri/id of project
        :return: list of ListNodes
        """
        result = con.get('/admin/lists?projectIri=' + quote_plus(project_iri))
        if 'lists' not in result:
            raise BaseError("Request got no lists!")
        return list(map(lambda a: ListNode.fromJsonObj(con, a), result['lists']))


    def print(self):
        """
        print info to stdout

        :return: None
        """

        print('Node Info:')
        print('  Id:        {}'.format(self._id))
        print('  Project:   {}'.format(self._project))
        print('  Name:      {}'.format(self._name))
        print('  Label:     ')
        if self._label is not None:
            for lbl in self._label.items():
                print('             {}: {}'.format(lbl[0], lbl[1]))
        else:
            print('             None')
        print('  Comment:   ')
        if self._comment is not None:
            for lbl in self._comment.items():
                print('             {}: {}'.format(lbl[0], lbl[1]))
        else:
            print('             None')
        print('  Parent', self._parent)
        print('  IsRootNode: {}'.format(self._isRootNode))