Coverage for oc_ocdm / abstract_entity.py: 97%

67 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-05-08 20:23 +0000

1#!/usr/bin/python 

2 

3# SPDX-FileCopyrightText: 2020-2022 Simone Persiani <iosonopersia@gmail.com> 

4# SPDX-FileCopyrightText: 2026 Arcangelo Massari <arcangelo.massari@unibo.it> 

5# 

6# SPDX-License-Identifier: ISC 

7 

8# -*- coding: utf-8 -*- 

9from __future__ import annotations 

10 

11from abc import ABC 

12from typing import TYPE_CHECKING 

13 

14from triplelite import TripleLite 

15 

16from oc_ocdm.constants import RDF_TYPE, RDFS_LABEL 

17from oc_ocdm.support.support import create_literal, create_type, get_short_name, is_dataset, is_string_empty 

18 

19if TYPE_CHECKING: 

20 from typing import ClassVar, Dict, List, Optional 

21 

22 

23class AbstractEntity(ABC): 

24 """ 

25 Abstract class which represents a generic entity from the OCDM. It sits 

26 at the top of the entity class hierarchy. 

27 """ 

28 

29 short_name_to_type_iri: ClassVar[Dict[str, str]] = {} 

30 

31 def __init__(self) -> None: 

32 self.g: TripleLite = TripleLite() 

33 self.res: str = "" 

34 self.short_name: str = "" 

35 

36 def remove_every_triple(self) -> None: 

37 """ 

38 Remover method that removes every triple from the current entity. 

39 

40 **WARNING: the OCDM specification requires that every entity has at least 

41 one triple that defines its type (through the** ``rdf:type`` **RDF predicate). If 

42 such triple is not subsequently restored by the user, the entity will be considered 

43 as to be deleted since it wouldn't be valid anymore.** 

44 

45 :return: None 

46 """ 

47 self.g.remove((None, None, None)) # type: ignore[arg-type] 

48 

49 # LABEL 

50 def get_label(self) -> Optional[str]: 

51 """ 

52 Getter method corresponding to the ``rdfs:label`` RDF predicate. 

53 

54 :return: The requested value if found, None otherwise 

55 """ 

56 return self._get_literal(RDFS_LABEL) 

57 

58 def create_label(self, string: str) -> None: 

59 """ 

60 Setter method corresponding to the ``rdfs:label`` RDF predicate. 

61 

62 **WARNING: this is a functional property, hence any existing value will be overwritten!** 

63 

64 :param string: The value that will be set as the object of the property related to this method 

65 :type string: str 

66 :return: None 

67 """ 

68 self.remove_label() 

69 self._create_literal(RDFS_LABEL, string) 

70 

71 def remove_label(self) -> None: 

72 """ 

73 Remover method corresponding to the ``rdfs:label`` RDF predicate. 

74 

75 :return: None 

76 """ 

77 self.g.remove((self.res, RDFS_LABEL, None)) 

78 

79 def _create_literal(self, p: str, s: str, dt: str | None = None, nor: bool = True) -> None: 

80 """ 

81 Adds an RDF triple with a literal object inside the graph of the entity 

82 

83 :param p: The predicate 

84 :type p: str 

85 :param s: The string to add as a literal value 

86 :type s: str 

87 :param dt: The object's datatype, if present 

88 :type dt: str, optional 

89 :param nor: Whether to normalize the graph or not 

90 :type nor: bool, optional 

91 :return: None 

92 """ 

93 create_literal(self.g, self.res, p, s, dt, nor) 

94 

95 # TYPE 

96 def get_types(self) -> List[str]: 

97 """ 

98 Getter method corresponding to the ``rdf:type`` RDF predicate. 

99 

100 :return: A list containing the requested values if found, None otherwise 

101 """ 

102 uri_list: List[str] = self._get_multiple_uri_references(RDF_TYPE) 

103 return uri_list 

104 

105 def _create_type(self, res_type: str) -> None: 

106 """ 

107 Setter method corresponding to the ``rdf:type`` RDF predicate. 

108 

109 **WARNING: the OCDM specification admits at most two types for an entity. 

110 The main type cannot be edited or removed. Any existing secondary type 

111 will be overwritten!** 

112 

113 :param res_type: The value that will be set as the object of the property related to this method 

114 :type res_type: str 

115 :return: None 

116 """ 

117 self.remove_type() # <-- It doesn't remove the main type! 

118 create_type(self.g, self.res, res_type) 

119 

120 def remove_type(self) -> None: 

121 """ 

122 Remover method corresponding to the ``rdf:type`` RDF predicate. 

123 

124 **WARNING: the OCDM specification requires at least one type for an entity. 

125 This method removes any existing secondary type, without removing the main type.** 

126 

127 :return: None 

128 """ 

129 self.g.remove((self.res, RDF_TYPE, None)) 

130 # Restore the main type IRI 

131 iri_main_type: str = self.short_name_to_type_iri[self.short_name] 

132 create_type(self.g, self.res, iri_main_type) 

133 

134 # Overrides __str__ method 

135 def __str__(self) -> str: 

136 return str(self.res) 

137 

138 def _get_literal(self, predicate: str) -> Optional[str]: 

139 for o in self.g.objects(self.res, predicate): 

140 if o.type == "literal": 

141 return o.value 

142 return None 

143 

144 def _get_multiple_literals(self, predicate: str) -> List[str]: 

145 result: List[str] = [] 

146 for o in self.g.objects(self.res, predicate): 

147 if o.type == "literal": 

148 result.append(o.value) 

149 return result 

150 

151 def _get_uri_reference(self, predicate: str, short_name: Optional[str] = None) -> Optional[str]: 

152 for o in self.g.objects(self.res, predicate): 

153 if o.type != "uri": 

154 continue 

155 uri = o.value 

156 if not is_string_empty(short_name): 

157 if (short_name == '_dataset_' and is_dataset(uri)) or get_short_name(uri) == short_name: 

158 return uri 

159 else: 

160 return uri 

161 return None 

162 

163 def _get_multiple_uri_references(self, predicate: str, short_name: Optional[str] = None) -> List[str]: 

164 result: List[str] = [] 

165 for o in self.g.objects(self.res, predicate): 

166 if o.type != "uri": 

167 continue 

168 uri = o.value 

169 if not is_string_empty(short_name): 

170 if (short_name == '_dataset_' and is_dataset(uri)) or get_short_name(uri) == short_name: 

171 result.append(uri) 

172 else: 

173 result.append(uri) 

174 return result