Coverage for oc_ocdm / metadata / entities / dataset.py: 93%

137 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-28 18:52 +0000

1#!/usr/bin/python 

2 

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

4# 

5# SPDX-License-Identifier: ISC 

6 

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

8from __future__ import annotations 

9 

10from typing import TYPE_CHECKING 

11 

12from oc_ocdm.decorators import accepts_only 

13from oc_ocdm.metadata.metadata_entity import MetadataEntity 

14from rdflib import Literal, XSD 

15 

16if TYPE_CHECKING: 

17 from typing import List 

18 from rdflib import URIRef 

19 from oc_ocdm.metadata.entities.distribution import Distribution 

20 

21 

22class Dataset(MetadataEntity): 

23 """Dataset (short: not applicable and strictly dependent on the implementation of the 

24 dataset infrastructure): a set of collected information about something.""" 

25 

26 def _merge_properties(self, other: MetadataEntity) -> None: 

27 """ 

28 The merge operation allows combining two ``Dataset`` entities into a single one, 

29 by marking the second entity as to be deleted while also copying its data into the current 

30 ``Dataset``. Moreover, every triple from the containing ``MetadataSet`` referring to the second 

31 entity gets "redirected" to the current entity: **every other reference contained inside a 

32 different source (e.g. a triplestore) must be manually handled by the user!** 

33 

34 In case of functional properties, values from the current entity get overwritten 

35 by those coming from the second entity while, in all other cases, values from the 

36 second entity are simply appended to those of the current entity. In this context, 

37 ``rdfs:label`` is considered as a functional property, while ``rdf:type`` is not. 

38 

39 :param other: The entity which will be marked as to be deleted and whose properties will 

40 be merged into the current entity. 

41 :type other: Dataset 

42 :raises TypeError: if the parameter is of the wrong type 

43 :return: None 

44 """ 

45 super()._merge_properties(other) 

46 assert isinstance(other, Dataset) 

47 

48 title: str | None = other.get_title() 

49 if title is not None: 

50 self.has_title(title) 

51 

52 description: str | None = other.get_description() 

53 if description is not None: 

54 self.has_description(description) 

55 

56 pub_date: str | None = other.get_publication_date() 

57 if pub_date is not None: 

58 self.has_publication_date(pub_date) 

59 

60 mod_date: str | None = other.get_modification_date() 

61 if mod_date is not None: 

62 self.has_modification_date(mod_date) 

63 

64 keywords_list: List[str] = other.get_keywords() 

65 for cur_keyword in keywords_list: 

66 self.has_keyword(cur_keyword) 

67 

68 subjects_list: List[URIRef] = other.get_subjects() 

69 for cur_subject in subjects_list: 

70 self.has_subject(cur_subject) 

71 

72 landing_page: URIRef | None = other.get_landing_page() 

73 if landing_page is not None: 

74 self.has_landing_page(landing_page) 

75 

76 sub_datasets_list: List[Dataset] = other.get_sub_datasets() 

77 for cur_sub_dataset in sub_datasets_list: 

78 self.has_sub_dataset(cur_sub_dataset) 

79 

80 sparql_endpoint: URIRef | None = other.get_sparql_endpoint() 

81 if sparql_endpoint is not None: 

82 self.has_sparql_endpoint(sparql_endpoint) 

83 

84 distributions_list: List[Distribution] = other.get_distributions() 

85 for cur_distribution in distributions_list: 

86 self.has_distribution(cur_distribution) 

87 

88 # HAS TITLE 

89 def get_title(self) -> str | None: 

90 """ 

91 Getter method corresponding to the ``dcterms:title`` RDF predicate. 

92 

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

94 """ 

95 return self._get_literal(MetadataEntity.iri_title) 

96 

97 @accepts_only('literal') 

98 def has_title(self, string: str) -> None: 

99 """ 

100 Setter method corresponding to the ``dcterms:title`` RDF predicate. 

101 

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

103 

104 `The title of the dataset.` 

105 

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

107 :type string: str 

108 :raises TypeError: if the parameter is of the wrong type 

109 :return: None 

110 """ 

111 self.remove_title() 

112 self._create_literal(MetadataEntity.iri_title, string) 

113 

114 def remove_title(self) -> None: 

115 """ 

116 Remover method corresponding to the ``dcterms:title`` RDF predicate. 

117 

118 :return: None 

119 """ 

120 self.g.remove((self.res, MetadataEntity.iri_title, None)) 

121 

122 # HAS DESCRIPTION 

123 def get_description(self) -> str | None: 

124 """ 

125 Getter method corresponding to the ``dcterms:description`` RDF predicate. 

126 

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

128 """ 

129 return self._get_literal(MetadataEntity.iri_description) 

130 

131 @accepts_only('literal') 

132 def has_description(self, string: str) -> None: 

133 """ 

134 Setter method corresponding to the ``dcterms:description`` RDF predicate. 

135 

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

137 

138 `A short textual description of the content of the dataset.` 

139 

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

141 :type string: str 

142 :raises TypeError: if the parameter is of the wrong type 

143 :return: None 

144 """ 

145 self.remove_description() 

146 self._create_literal(MetadataEntity.iri_description, string) 

147 

148 def remove_description(self) -> None: 

149 """ 

150 Remover method corresponding to the ``dcterms:description`` RDF predicate. 

151 

152 :return: None 

153 """ 

154 self.g.remove((self.res, MetadataEntity.iri_description, None)) 

155 

156 # HAS PUBLICATION DATE 

157 def get_publication_date(self) -> str | None: 

158 """ 

159 Getter method corresponding to the ``dcterms:issued`` RDF predicate. 

160 

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

162 """ 

163 return self._get_literal(MetadataEntity.iri_issued) 

164 

165 @accepts_only('literal') 

166 def has_publication_date(self, string: str) -> None: 

167 """ 

168 Setter method corresponding to the ``dcterms:issued`` RDF predicate. 

169 

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

171 

172 `The date of first publication of the dataset.` 

173 

174 :param string: The value that will be set as the object of the property related to this method. **It must 

175 be a string compliant with the** ``xsd:dateTime`` **datatype.** 

176 :type string: str 

177 :raises TypeError: if the parameter is of the wrong type 

178 :return: None 

179 """ 

180 self.remove_publication_date() 

181 self._create_literal(MetadataEntity.iri_issued, string, XSD.dateTime, False) 

182 

183 def remove_publication_date(self) -> None: 

184 """ 

185 Remover method corresponding to the ``dcterms:issued`` RDF predicate. 

186 

187 :return: None 

188 """ 

189 self.g.remove((self.res, MetadataEntity.iri_issued, None)) 

190 

191 # HAS MODIFICATION DATE 

192 def get_modification_date(self) -> str | None: 

193 """ 

194 Getter method corresponding to the ``dcterms:modified`` RDF predicate. 

195 

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

197 """ 

198 return self._get_literal(MetadataEntity.iri_modified) 

199 

200 @accepts_only('literal') 

201 def has_modification_date(self, string: str) -> None: 

202 """ 

203 Setter method corresponding to the ``dcterms:modified`` RDF predicate. 

204 

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

206 

207 `The date on which the dataset has been modified.` 

208 

209 :param string: The value that will be set as the object of the property related to this method. **It must 

210 be a string compliant with the** ``xsd:dateTime`` **datatype.** 

211 :type string: str 

212 :raises TypeError: if the parameter is of the wrong type 

213 :return: None 

214 """ 

215 self.remove_modification_date() 

216 self._create_literal(MetadataEntity.iri_modified, string, XSD.dateTime, False) 

217 

218 def remove_modification_date(self) -> None: 

219 """ 

220 Remover method corresponding to the ``dcterms:modified`` RDF predicate. 

221 

222 :return: None 

223 """ 

224 self.g.remove((self.res, MetadataEntity.iri_modified, None)) 

225 

226 # HAS KEYWORD 

227 def get_keywords(self) -> List[str]: 

228 """ 

229 Getter method corresponding to the ``dcat:keyword`` RDF predicate. 

230 

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

232 """ 

233 return self._get_multiple_literals(MetadataEntity.iri_keyword) 

234 

235 @accepts_only('literal') 

236 def has_keyword(self, string: str) -> None: 

237 """ 

238 Setter method corresponding to the ``dcat:keyword`` RDF predicate. 

239 

240 `A keyword or phrase describing the content of the dataset.` 

241 

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

243 :type string: str 

244 :raises TypeError: if the parameter is of the wrong type 

245 :return: None 

246 """ 

247 self._create_literal(MetadataEntity.iri_keyword, string) 

248 

249 @accepts_only('literal') 

250 def remove_keyword(self, string: str | None = None) -> None: 

251 """ 

252 Remover method corresponding to the ``dcat:keyword`` RDF predicate. 

253 

254 **WARNING: this is a non-functional property, hence, if the parameter 

255 is None, any existing value will be removed!** 

256 

257 :param string: If not None, the specific object value that will be removed from the property 

258 related to this method (defaults to None) 

259 :type string: str 

260 :raises TypeError: if the parameter is of the wrong type 

261 :return: None 

262 """ 

263 if string is not None: 

264 self.g.remove((self.res, MetadataEntity.iri_keyword, Literal(string))) 

265 else: 

266 self.g.remove((self.res, MetadataEntity.iri_keyword, None)) 

267 

268 # HAS SUBJECT 

269 def get_subjects(self) -> List[URIRef]: 

270 """ 

271 Getter method corresponding to the ``dcat:theme`` RDF predicate. 

272 

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

274 """ 

275 uri_list: List[URIRef] = self._get_multiple_uri_references(MetadataEntity.iri_subject) 

276 return uri_list 

277 

278 @accepts_only('thing') 

279 def has_subject(self, thing_res: URIRef) -> None: 

280 """ 

281 Setter method corresponding to the ``dcat:theme`` RDF predicate. 

282 

283 `A concept describing the primary subject of the dataset.` 

284 

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

286 :type thing_res: URIRef 

287 :raises TypeError: if the parameter is of the wrong type 

288 :return: None 

289 """ 

290 self.g.add((self.res, MetadataEntity.iri_subject, thing_res)) 

291 

292 @accepts_only('thing') 

293 def remove_subject(self, thing_res: URIRef | None = None) -> None: 

294 """ 

295 Remover method corresponding to the ``dcat:theme`` RDF predicate. 

296 

297 **WARNING: this is a non-functional property, hence, if the parameter 

298 is None, any existing value will be removed!** 

299 

300 :param thing_res: If not None, the specific object value that will be removed from the property 

301 related to this method (defaults to None) 

302 :type thing_res: URIRef 

303 :raises TypeError: if the parameter is of the wrong type 

304 :return: None 

305 """ 

306 if thing_res is not None: 

307 self.g.remove((self.res, MetadataEntity.iri_subject, thing_res)) 

308 else: 

309 self.g.remove((self.res, MetadataEntity.iri_subject, None)) 

310 

311 # HAS LANDING PAGE 

312 def get_landing_page(self) -> URIRef | None: 

313 """ 

314 Getter method corresponding to the ``dcat:landingPage`` RDF predicate. 

315 

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

317 """ 

318 return self._get_uri_reference(MetadataEntity.iri_landing_page) 

319 

320 @accepts_only('thing') 

321 def has_landing_page(self, thing_res: URIRef) -> None: 

322 """ 

323 Setter method corresponding to the ``dcat:landingPage`` RDF predicate. 

324 

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

326 

327 `An HTML page (indicated by its URL) representing a browsable page for the dataset.` 

328 

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

330 :type thing_res: URIRef 

331 :raises TypeError: if the parameter is of the wrong type 

332 :return: None 

333 """ 

334 self.remove_landing_page() 

335 self.g.add((self.res, MetadataEntity.iri_landing_page, thing_res)) 

336 

337 def remove_landing_page(self) -> None: 

338 """ 

339 Remover method corresponding to the ``dcat:landingPage`` RDF predicate. 

340 

341 :return: None 

342 """ 

343 self.g.remove((self.res, MetadataEntity.iri_landing_page, None)) 

344 

345 # HAS SUB-DATASET 

346 def get_sub_datasets(self) -> List[Dataset]: 

347 """ 

348 Getter method corresponding to the ``void:subset`` RDF predicate. 

349 

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

351 """ 

352 uri_list: List[URIRef] = self._get_multiple_uri_references(MetadataEntity.iri_subset, '_dataset_') 

353 result: List[Dataset] = [] 

354 for uri in uri_list: 

355 result.append(self.m_set.add_dataset(self.dataset_name, self.resp_agent or "", self.source, uri)) 

356 return result 

357 

358 @accepts_only('_dataset_') 

359 def has_sub_dataset(self, obj: Dataset) -> None: 

360 """ 

361 Setter method corresponding to the ``void:subset`` RDF predicate. 

362 

363 `A link to a subset of the present dataset.` 

364 

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

366 :type obj: Dataset 

367 :raises TypeError: if the parameter is of the wrong type 

368 :return: None 

369 """ 

370 self.g.add((self.res, MetadataEntity.iri_subset, obj.res)) 

371 

372 @accepts_only('_dataset_') 

373 def remove_sub_dataset(self, dataset_res: Dataset | None = None) -> None: 

374 """ 

375 Remover method corresponding to the ``void:subset`` RDF predicate. 

376 

377 **WARNING: this is a non-functional property, hence, if the parameter 

378 is None, any existing value will be removed!** 

379 

380 :param dataset_res: If not None, the specific object value that will be removed from the property 

381 related to this method (defaults to None) 

382 :type dataset_res: Dataset 

383 :raises TypeError: if the parameter is of the wrong type 

384 :return: None 

385 """ 

386 if dataset_res is not None: 

387 self.g.remove((self.res, MetadataEntity.iri_subset, dataset_res.res)) 

388 else: 

389 self.g.remove((self.res, MetadataEntity.iri_subset, None)) 

390 

391 # HAS SPARQL ENDPOINT 

392 def get_sparql_endpoint(self) -> URIRef | None: 

393 """ 

394 Getter method corresponding to the ``void:sparqlEndpoint`` RDF predicate. 

395 

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

397 """ 

398 uri: URIRef | None = self._get_uri_reference(MetadataEntity.iri_sparql_endpoint) 

399 return uri 

400 

401 @accepts_only('thing') 

402 def has_sparql_endpoint(self, thing_res: URIRef) -> None: 

403 """ 

404 Setter method corresponding to the ``void:sparqlEndpoint`` RDF predicate. 

405 

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

407 

408 `The link to the SPARQL endpoint for querying the dataset.` 

409 

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

411 :type thing_res: URIRef 

412 :raises TypeError: if the parameter is of the wrong type 

413 :return: None 

414 """ 

415 self.remove_sparql_endpoint() 

416 self.g.add((self.res, MetadataEntity.iri_sparql_endpoint, thing_res)) 

417 

418 def remove_sparql_endpoint(self) -> None: 

419 """ 

420 Remover method corresponding to the ``void:sparqlEndpoint`` RDF predicate. 

421 

422 :return: None 

423 """ 

424 self.g.remove((self.res, MetadataEntity.iri_sparql_endpoint, None)) 

425 

426 # HAS DISTRIBUTION (Distribution) 

427 def get_distributions(self) -> List[Distribution]: 

428 """ 

429 Getter method corresponding to the ``dcat:distribution`` RDF predicate. 

430 

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

432 """ 

433 uri_list: List[URIRef] = self._get_multiple_uri_references(MetadataEntity.iri_distribution, 'di') 

434 result: List[Distribution] = [] 

435 for uri in uri_list: 

436 result.append(self.m_set.add_di(self.dataset_name, self.resp_agent or "", self.source, uri)) 

437 return result 

438 

439 @accepts_only('di') 

440 def has_distribution(self, obj: Distribution) -> None: 

441 """ 

442 Setter method corresponding to the ``dcat:distribution`` RDF predicate. 

443 

444 `A distribution of the dataset.` 

445 

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

447 :type obj: Distribution 

448 :raises TypeError: if the parameter is of the wrong type 

449 :return: None 

450 """ 

451 self.g.add((self.res, MetadataEntity.iri_distribution, obj.res)) 

452 

453 @accepts_only('di') 

454 def remove_distribution(self, di_res: Distribution | None = None) -> None: 

455 """ 

456 Remover method corresponding to the ``dcat:distribution`` RDF predicate. 

457 

458 **WARNING: this is a non-functional property, hence, if the parameter 

459 is None, any existing value will be removed!** 

460 

461 :param di_res: If not None, the specific object value that will be removed from the property 

462 related to this method (defaults to None) 

463 :type di_res: Distribution 

464 :raises TypeError: if the parameter is of the wrong type 

465 :return: None 

466 """ 

467 if di_res is not None: 

468 self.g.remove((self.res, MetadataEntity.iri_distribution, di_res.res)) 

469 else: 

470 self.g.remove((self.res, MetadataEntity.iri_distribution, None))