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

141 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2025-05-30 22:05 +0000

1#!/usr/bin/python 

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

3# Copyright (c) 2016, Silvio Peroni <essepuntato@gmail.com> 

4# 

5# Permission to use, copy, modify, and/or distribute this software for any purpose 

6# with or without fee is hereby granted, provided that the above copyright notice 

7# and this permission notice appear in all copies. 

8# 

9# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 

10# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 

11# FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, 

12# OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, 

13# DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 

14# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 

15# SOFTWARE. 

16from __future__ import annotations 

17 

18from typing import TYPE_CHECKING 

19 

20from oc_ocdm.decorators import accepts_only 

21from oc_ocdm.metadata.metadata_entity import MetadataEntity 

22from rdflib import XSD 

23 

24if TYPE_CHECKING: 

25 from typing import Optional, List 

26 from rdflib import URIRef 

27 from oc_ocdm.metadata.entities.distribution import Distribution 

28 

29 

30class Dataset(MetadataEntity): 

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

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

33 

34 @accepts_only('_dataset_') 

35 def merge(self, other: Dataset) -> None: 

36 """ 

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

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

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

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

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

42 

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

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

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

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

47 

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

49 be merged into the current entity. 

50 :type other: Dataset 

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

52 :return: None 

53 """ 

54 super(Dataset, self).merge(other) 

55 

56 title: Optional[str] = other.get_title() 

57 if title is not None: 

58 self.has_title(title) 

59 

60 description: Optional[str] = other.get_description() 

61 if description is not None: 

62 self.has_description(description) 

63 

64 pub_date: Optional[str] = other.get_publication_date() 

65 if pub_date is not None: 

66 self.has_publication_date(pub_date) 

67 

68 mod_date: Optional[str] = other.get_modification_date() 

69 if mod_date is not None: 

70 self.has_modification_date(mod_date) 

71 

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

73 for cur_keyword in keywords_list: 

74 self.has_keyword(cur_keyword) 

75 

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

77 for cur_subject in subjects_list: 

78 self.has_subject(cur_subject) 

79 

80 landing_page: Optional[URIRef] = other.get_landing_page() 

81 if landing_page is not None: 

82 self.has_landing_page(landing_page) 

83 

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

85 for cur_sub_dataset in sub_datasets_list: 

86 self.has_sub_dataset(cur_sub_dataset) 

87 

88 sparql_endpoint: Optional[URIRef] = other.get_sparql_endpoint() 

89 if sparql_endpoint is not None: 

90 self.has_sparql_endpoint(sparql_endpoint) 

91 

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

93 for cur_distribution in distributions_list: 

94 self.has_distribution(cur_distribution) 

95 

96 # HAS TITLE 

97 def get_title(self) -> Optional[str]: 

98 """ 

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

100 

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

102 """ 

103 return self._get_literal(MetadataEntity.iri_title) 

104 

105 @accepts_only('literal') 

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

107 """ 

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

109 

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

111 

112 `The title of the dataset.` 

113 

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

115 :type string: str 

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

117 :return: None 

118 """ 

119 self.remove_title() 

120 self._create_literal(MetadataEntity.iri_title, string) 

121 

122 def remove_title(self) -> None: 

123 """ 

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

125 

126 :return: None 

127 """ 

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

129 

130 # HAS DESCRIPTION 

131 def get_description(self) -> Optional[str]: 

132 """ 

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

134 

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

136 """ 

137 return self._get_literal(MetadataEntity.iri_description) 

138 

139 @accepts_only('literal') 

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

141 """ 

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

143 

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

145 

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

147 

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

149 :type string: str 

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

151 :return: None 

152 """ 

153 self.remove_description() 

154 self._create_literal(MetadataEntity.iri_description, string) 

155 

156 def remove_description(self) -> None: 

157 """ 

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

159 

160 :return: None 

161 """ 

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

163 

164 # HAS PUBLICATION DATE 

165 def get_publication_date(self) -> Optional[str]: 

166 """ 

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

168 

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

170 """ 

171 return self._get_literal(MetadataEntity.iri_issued) 

172 

173 @accepts_only('literal') 

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

175 """ 

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

177 

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

179 

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

181 

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

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

184 :type string: str 

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

186 :return: None 

187 """ 

188 self.remove_publication_date() 

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

190 

191 def remove_publication_date(self) -> None: 

192 """ 

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

194 

195 :return: None 

196 """ 

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

198 

199 # HAS MODIFICATION DATE 

200 def get_modification_date(self) -> Optional[str]: 

201 """ 

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

203 

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

205 """ 

206 return self._get_literal(MetadataEntity.iri_modified) 

207 

208 @accepts_only('literal') 

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

210 """ 

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

212 

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

214 

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

216 

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

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

219 :type string: str 

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

221 :return: None 

222 """ 

223 self.remove_modification_date() 

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

225 

226 def remove_modification_date(self) -> None: 

227 """ 

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

229 

230 :return: None 

231 """ 

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

233 

234 # HAS KEYWORD 

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

236 """ 

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

238 

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

240 """ 

241 return self._get_multiple_literals(MetadataEntity.iri_keyword) 

242 

243 @accepts_only('literal') 

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

245 """ 

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

247 

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

249 

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

251 :type string: str 

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

253 :return: None 

254 """ 

255 self._create_literal(MetadataEntity.iri_keyword, string) 

256 

257 @accepts_only('literal') 

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

259 """ 

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

261 

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

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

264 

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

266 related to this method (defaults to None) 

267 :type string: str 

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

269 :return: None 

270 """ 

271 if string is not None: 

272 self.g.remove((self.res, MetadataEntity.iri_keyword, string)) 

273 else: 

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

275 

276 # HAS SUBJECT 

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

278 """ 

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

280 

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

282 """ 

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

284 return uri_list 

285 

286 @accepts_only('thing') 

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

288 """ 

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

290 

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

292 

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

294 :type thing_res: URIRef 

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

296 :return: None 

297 """ 

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

299 

300 @accepts_only('thing') 

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

302 """ 

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

304 

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

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

307 

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

309 related to this method (defaults to None) 

310 :type thing_res: URIRef 

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

312 :return: None 

313 """ 

314 if thing_res is not None: 

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

316 else: 

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

318 

319 # HAS LANDING PAGE 

320 def get_landing_page(self) -> Optional[URIRef]: 

321 """ 

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

323 

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

325 """ 

326 return self._get_literal(MetadataEntity.iri_landing_page) 

327 

328 @accepts_only('thing') 

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

330 """ 

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

332 

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

334 

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

336 

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

338 :type thing_res: URIRef 

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

340 :return: None 

341 """ 

342 self.remove_landing_page() 

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

344 

345 def remove_landing_page(self) -> None: 

346 """ 

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

348 

349 :return: None 

350 """ 

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

352 

353 # HAS SUB-DATASET 

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

355 """ 

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

357 

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

359 """ 

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

361 result: List[Dataset] = [] 

362 for uri in uri_list: 

363 result.append(self.m_set.add_dataset(self.resp_agent, self.source, uri)) 

364 return result 

365 

366 @accepts_only('_dataset_') 

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

368 """ 

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

370 

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

372 

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

374 :type obj: Dataset 

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

376 :return: None 

377 """ 

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

379 

380 @accepts_only('_dataset_') 

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

382 """ 

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

384 

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

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

387 

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

389 related to this method (defaults to None) 

390 :type dataset_res: Dataset 

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

392 :return: None 

393 """ 

394 if dataset_res is not None: 

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

396 else: 

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

398 

399 # HAS SPARQL ENDPOINT 

400 def get_sparql_endpoint(self) -> Optional[URIRef]: 

401 """ 

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

403 

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

405 """ 

406 uri: Optional[URIRef] = self._get_uri_reference(MetadataEntity.iri_sparql_endpoint) 

407 return uri 

408 

409 @accepts_only('thing') 

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

411 """ 

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

413 

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

415 

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

417 

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

419 :type thing_res: URIRef 

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

421 :return: None 

422 """ 

423 self.remove_sparql_endpoint() 

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

425 

426 def remove_sparql_endpoint(self) -> None: 

427 """ 

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

429 

430 :return: None 

431 """ 

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

433 

434 # HAS DISTRIBUTION (Distribution) 

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

436 """ 

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

438 

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

440 """ 

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

442 result: List[Distribution] = [] 

443 for uri in uri_list: 

444 result.append(self.m_set.add_di(self.resp_agent, self.source, uri)) 

445 return result 

446 

447 @accepts_only('di') 

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

449 """ 

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

451 

452 `A distribution of the dataset.` 

453 

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

455 :type obj: Distribution 

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

457 :return: None 

458 """ 

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

460 

461 @accepts_only('di') 

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

463 """ 

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

465 

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

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

468 

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

470 related to this method (defaults to None) 

471 :type di_res: Distribution 

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

473 :return: None 

474 """ 

475 if di_res is not None: 

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

477 else: 

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