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
« prev ^ index » next coverage.py v7.13.4, created at 2026-05-08 20:23 +0000
1#!/usr/bin/python
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
8# -*- coding: utf-8 -*-
9from __future__ import annotations
11from abc import ABC
12from typing import TYPE_CHECKING
14from triplelite import TripleLite
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
19if TYPE_CHECKING:
20 from typing import ClassVar, Dict, List, Optional
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 """
29 short_name_to_type_iri: ClassVar[Dict[str, str]] = {}
31 def __init__(self) -> None:
32 self.g: TripleLite = TripleLite()
33 self.res: str = ""
34 self.short_name: str = ""
36 def remove_every_triple(self) -> None:
37 """
38 Remover method that removes every triple from the current entity.
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.**
45 :return: None
46 """
47 self.g.remove((None, None, None)) # type: ignore[arg-type]
49 # LABEL
50 def get_label(self) -> Optional[str]:
51 """
52 Getter method corresponding to the ``rdfs:label`` RDF predicate.
54 :return: The requested value if found, None otherwise
55 """
56 return self._get_literal(RDFS_LABEL)
58 def create_label(self, string: str) -> None:
59 """
60 Setter method corresponding to the ``rdfs:label`` RDF predicate.
62 **WARNING: this is a functional property, hence any existing value will be overwritten!**
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)
71 def remove_label(self) -> None:
72 """
73 Remover method corresponding to the ``rdfs:label`` RDF predicate.
75 :return: None
76 """
77 self.g.remove((self.res, RDFS_LABEL, None))
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
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)
95 # TYPE
96 def get_types(self) -> List[str]:
97 """
98 Getter method corresponding to the ``rdf:type`` RDF predicate.
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
105 def _create_type(self, res_type: str) -> None:
106 """
107 Setter method corresponding to the ``rdf:type`` RDF predicate.
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!**
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)
120 def remove_type(self) -> None:
121 """
122 Remover method corresponding to the ``rdf:type`` RDF predicate.
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.**
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)
134 # Overrides __str__ method
135 def __str__(self) -> str:
136 return str(self.res)
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
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
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
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