Coverage for lode / reader / reader.py: 48%
120 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-10-05 16:02 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-10-05 16:02 +0000
1# reader.py - ORCHESTRATOR GENERICO
2from lode.reader.loader import Loader
3from lode.reader.config_manager import get_configuration
4from lode.models import *
6class Reader:
7 """
8 Generic RDF Reader/Orchestrator.
10 Responsibilities:
11 - Calls the loader to parse input RDF (via rdflib)
12 - Orchestrates Python model population phases
13 - Delegates specific logics for extraction and population to other modules
14 """
16 def __init__(self):
17 self._instance_cache = {}
18 self._logic = None # Logic specializzata (OWL, SKOS, RDF, RDFS)
19 self._graph = None
20 self._configuration = None
21 self._imported_uris = set() # needed to get the model before imported/closure models
22 self._ontology_iri = None
24 def get_warnings(self) -> list:
25 if not getattr(self, '_warnings_enabled', False):
26 return []
27 if self._logic and hasattr(self._logic, '_warnings'):
28 return self._logic._warnings
29 return []
31 def load_instances(self, graph_path: str, read_as: str, imported=None, closure=None, partial_import=None, warnings=False):
32 """Carica e processa grafo RDF"""
33 self._warnings_enabled = warnings
35 # 1. Parse generico
36 loader = Loader(graph_path, imported=imported, closure=closure, partial_import=partial_import)
37 self._graph = loader.get_graph()
38 self._ontology_iri = loader.ontology_iri
40 # 1.5 saves the imported/closure uris to be reused in viewer so the model metadata are not overwritten
41 self._imported_uris = getattr(loader, "imported_uris", set())
43 # 2. Seleziona strategia
44 self._configuration = get_configuration(read_as)
46 # 3. Crea Logic specializzata
47 self._logic = self._configuration.create_logic(self._graph, self._instance_cache)
49 # 4. Esecuzione fasi
50 self._extract_instances()
53 def get_instance(self, uri: str, instance_type=None):
54 """Ottiene istanze per URI"""
55 uri_identifier = None
57 for identifier in self._instance_cache.keys():
58 if str(identifier) == uri:
59 uri_identifier = identifier
60 break
62 if uri_identifier is None:
63 return None
65 instances = self._instance_cache[uri_identifier]
67 if instance_type is None:
68 return instances
70 for instance in instances:
71 if isinstance(instance, instance_type):
72 return instance
74 return None
76 def get_instances(self) -> dict:
77 """Raggruppa istanze per tipo"""
78 grouped = {}
80 for uri_identifier, instances in self._instance_cache.items():
81 if isinstance(uri_identifier, str) and uri_identifier.startswith("LITERAL::"):
82 continue
84 instances_list = instances if isinstance(instances, set) else [instances]
86 for instance in instances_list:
87 class_name = instance.__class__.__name__
88 if class_name not in grouped:
89 grouped[class_name] = []
90 grouped[class_name].append(instance)
92 return grouped
94 def get_triples_for_instance(self, instance):
95 """Ottiene le triple RDF associate a un'istanza Python"""
96 if self._logic and hasattr(self._logic, '_triples_map'):
97 return self._logic._triples_map.get(instance, set())
98 return set()
100 def get_all_triples_map(self) -> dict:
101 """Ottiene la mappa completa instance → triple"""
102 if self._logic and hasattr(self._logic, '_triples_map'):
103 return self._logic._triples_map
104 return {}
106 def get_provenance_subgraph(self, instance):
107 """Return the rdflib.Graph subgraph that provenanced `instance`."""
108 if self._logic and hasattr(self._logic, 'build_provenance_subgraph'):
109 return self._logic.build_provenance_subgraph(instance)
110 from rdflib import Graph
111 return Graph()
114 # function reused by the api to push instances
115 def to_dict(self, instance) -> dict:
116 """Serializza un'istanza Python in dict JSON, includendo le triple RDF"""
117 result = {
118 "instance": str(instance),
119 "type": type(instance).__name__,
120 "uri": str(instance.has_identifier) if instance.has_identifier else None,
121 "properties": {},
122 "rdf_triples": []
123 }
125 # Serializza le proprietà dell'istanza
126 for attr_name in instance.__dict__.keys():
127 value = getattr(instance, attr_name)
128 # removes empty values from the properties
129 if value is not None and not (isinstance(value, (list, set)) and not value):
130 result["properties"][attr_name] = str(value)
132 # Aggiungi le triple RDF associate
133 triples = self.get_triples_for_instance(instance)
134 for s, p, o in triples:
135 triple_dict = {
136 "subject": str(s),
137 "predicate": str(p),
138 "object": str(o)
139 }
140 result["rdf_triples"].append(triple_dict)
142 return result
144 def _serialize_value(self, value):
145 """Helper per serializzare diversi tipi di valori"""
146 if isinstance(value, (str, int, float, bool)):
147 return value
148 elif isinstance(value, (list, set)):
149 return [self._serialize_value(v) for v in value]
150 elif hasattr(value, 'uri'):
151 return str(value.uri)
152 else:
153 return str(value)
155 def get_viewer(self):
156 """Ottiene il viewer appropriato per il formato corrente."""
157 if not self._configuration:
158 raise ValueError("No configuration loaded. Call load_instances() first.")
160 return self._configuration.create_viewer(self)
162 def clear_cache(self):
163 """Pulisce la cache"""
164 self._instance_cache.clear()
165 if self._logic:
166 self._logic.clear_cache()
168 # ==================== ESTRAZIONE (ORCHESTRAZIONE) ====================
170 def _extract_instances(self):
171 """Estrazione in 6 fasi orchestrate"""
172 # print("\n" + "="*60)
173 # print("ESTRAZIONE INSTANCES")
174 # print("="*60)
176 # FASE 0: Pre-crea datatypes (comune a tutti)
177 # self._phase0_create_datatypes()
179 # FASE 1-4: Delegate alla Logic specifica
180 self._logic.phase1_classify_from_predicates()
181 self._logic.phase2_create_from_types()
182 self._logic.phase3_populate_properties()
183 self._logic.phase4_process_group_axioms()
185 # FASE 5: Fallback (comune)
186 self._logic.phase5_fallback()
188 # FASE 6: Statements
189 self._logic.phase6_create_statements()
191 # FASE 7: Namespaces
192 self._logic.populate_namespaces()
194 # Post-pipeline warning checks
195 if self._warnings_enabled:
196 from lode.reader.warnings import owl_warnings
197 owl_warnings.run_post_checks(self._logic)
199 if self._logic._warnings:
200 print(f"WARNINGS ({len(self._logic._warnings)}):")
201 for w in self._logic._warnings:
202 print(f" [{w['code']}] {w['message']}")
204 self._logic.save_warnings()
206 # def _phase0_create_datatypes(self):
207 # """Pre-crea tutti i Datatype (comune a tutti i formati)"""
208 # print("\n--- FASE 0: Datatypes ---")
210 # created = 0
212 # # 1. URI XSD
213 # for s, p, o in self._graph:
214 # if isinstance(s, URIRef) and str(s).startswith(str(XSD)):
215 # if s not in self._instance_cache:
216 # self._logic.create_empty_instance(s, Datatype)
217 # created += 1
219 # if isinstance(o, URIRef) and str(o).startswith(str(XSD)):
220 # if o not in self._instance_cache:
221 # self._logic.create_empty_instance(o, Datatype)
222 # created += 1
224 # # 2. rdfs:Literal
225 # if RDFS.Literal not in self._instance_cache:
226 # self._logic.create_empty_instance(RDFS.Literal, Datatype)
227 # created += 1
229 # # 3. BNode Datatypes
230 # for bnode in self._graph.subjects(RDF.type, RDFS.Datatype):
231 # if isinstance(bnode, BNode) and bnode not in self._instance_cache:
232 # self._logic.create_empty_instance(bnode, Datatype)
233 # created += 1
235 # print(f" Creati {created} datatypes")
237 def _phase5_fallback(self):
238 """Fallback per risorse non categorizzate (comune)"""
239 #print("\n--- FASE 5: Fallback ---")
241 fallback_class = self._configuration.get_fallback_class()
242 if not fallback_class:
243 print(" Nessun fallback configurato")
244 return
246 all_subjects = set(self._graph.subjects())
247 fallback_count = 0
249 for subj in all_subjects:
250 if subj not in self._instance_cache:
251 self._logic.get_or_create(subj, fallback_class)
252 fallback_count += 1
254 #print(f" Fallback: {fallback_count} risorse -> {fallback_class.__name__}")
256 # def get_ontology_metadata(self) -> Model:
257 # """
258 # Extracts metadata and returns a populated Model object.
259 # """
260 # if self._graph is None:
261 # return Model()
263 # # 1. Create the Model instance
264 # ontology_model = Model()
266 # # 2. Find the owl:Ontology node
267 # ontology_node = self._graph.value(predicate=RDF.type, object=OWL.Ontology)
269 # if ontology_node:
270 # # --- IDENTIFIER (IRI) ---
271 # ontology_model.set_has_identifier(str(ontology_node))
273 # # --- TITLE (Label) ---
274 # title = (
275 # self._graph.value(ontology_node, DCTERMS.title) or
276 # self._graph.value(ontology_node, DC.title) or
277 # self._graph.value(ontology_node, RDFS.label)
278 # )
279 # if title:
280 # ontology_model.set_has_label(str(title))
282 # # --- DESCRIPTION (Comment) ---
283 # comment = (
284 # self._graph.value(ontology_node, DCTERMS.description) or
285 # self._graph.value(ontology_node, RDFS.comment)
286 # )
287 # if comment:
288 # ontology_model.set_has_comment(str(comment))
290 # # --- VERSION INFO ---
291 # version_info = self._graph.value(ontology_node, OWL.versionInfo)
292 # if version_info:
293 # ontology_model.set_has_version_info(str(version_info))
295 # # --- VERSION IRI ---
296 # version_iri = self._graph.value(ontology_node, OWL.versionIRI)
297 # if version_iri:
298 # v_model = Model()
299 # v_model.set_has_identifier(str(version_iri))
300 # ontology_model.set_has_version(v_model)
302 # # --- IMPORTS ---
303 # for imported_iri in self._graph.objects(ontology_node, OWL.imports):
304 # imported_model = Model()
305 # imported_model.set_has_identifier(str(imported_iri))
306 # ontology_model.set_imports(imported_model)
308 # # --- CREATORS (Custom handling) ---
309 # for pred in [DCTERMS.creator, DC.creator]:
310 # for creator in self._graph.objects(ontology_node, pred):
311 # ontology_model.set_has_creator(creator)
314 # # --- CONTRIBUTORS (Custom handling) ---
315 # for pred in [DCTERMS.contributor, DC.contributor]:
316 # for contributor in self._graph.objects(ontology_node, pred):
317 # ontology_model.set_has_contributor(contributor)
319 # return ontology_model