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))
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))