From e8c9581bbc45070aa0bff62f9fe9d837e44e28e0 Mon Sep 17 00:00:00 2001 From: arcangelo7 Date: Sat, 30 Nov 2024 14:10:42 +0100 Subject: [PATCH] is_restored --- pyproject.toml | 2 +- rdflib_ocdm/ocdm_graph.py | 27 +++++++++++++++++----- rdflib_ocdm/prov/provenance.py | 12 ++++++++++ test/provenance_test.py | 41 +++++++++++++++++++++++++++++++++- test/storer_test.py | 1 - 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ad03047..9fb689c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rdflib-ocdm" -version = "0.3.12" +version = "0.4.0" description = "" authors = ["arcangelo7 "] license = "ISC" diff --git a/rdflib_ocdm/ocdm_graph.py b/rdflib_ocdm/ocdm_graph.py index 83b9861..9f5e2fe 100644 --- a/rdflib_ocdm/ocdm_graph.py +++ b/rdflib_ocdm/ocdm_graph.py @@ -53,7 +53,7 @@ def __init__(self, counter_handler: CounterHandler): def preexisting_finished(self: Graph|ConjunctiveGraph|OCDMGraphCommons, resp_agent: str = None, primary_source: str = None, c_time: str = None): self.preexisting_graph = deepcopy(self) for subject in self.subjects(unique=True): - self.entity_index[subject] = {'to_be_deleted': False, 'resp_agent': resp_agent, 'source': primary_source} + self.entity_index[subject] = {'to_be_deleted': False, 'is_restored': False, 'resp_agent': resp_agent, 'source': primary_source} self.all_entities.add(subject) count = self.provenance.counter_handler.read_counter(subject) if count == 0: @@ -79,6 +79,21 @@ def merge(self: Graph|ConjunctiveGraph|OCDMGraphCommons, res: URIRef, other: URI def mark_as_deleted(self, res: URIRef) -> None: self.entity_index[res]['to_be_deleted'] = True + def mark_as_restored(self, res: URIRef) -> None: + """ + Marks an entity as being restored after deletion. + This will: + 1. Set is_restored flag to True in the entity_index + 2. Set to_be_deleted flag to False + + :param res: The URI reference of the entity to restore + :type res: URIRef + :return: None + """ + if res in self.entity_index: + self.entity_index[res]['is_restored'] = True + self.entity_index[res]['to_be_deleted'] = False + @property def merge_index(self) -> dict: return self.__merge_index @@ -90,7 +105,7 @@ def entity_index(self) -> dict: def generate_provenance(self, c_time: float = None) -> None: return self.provenance.generate_provenance(c_time) - def get_entity(self, res: str) -> Optional[ProvEntity]: + def get_entity(self, res: str) -> Optional[SnapshotEntity]: return self.provenance.get_entity(res) def commit_changes(self): @@ -124,7 +139,7 @@ def add(self, triple: "_TripleType", resp_agent = None, primary_source = None): self.all_entities.add(s) if s not in self.entity_index: - self.entity_index[s] = {'to_be_deleted': False, 'resp_agent': resp_agent, 'source': primary_source} + self.entity_index[s] = {'to_be_deleted': False, 'is_restored': True, 'resp_agent': resp_agent, 'source': primary_source} return self @@ -268,7 +283,7 @@ def parse( self.all_entities.add(subject) if subject not in self.entity_index: - self.entity_index[subject] = {'to_be_deleted': False, 'resp_agent': resp_agent, 'source': primary_source} + self.entity_index[subject] = {'to_be_deleted': False, 'is_restored': True, 'resp_agent': resp_agent, 'source': primary_source} return self @@ -305,7 +320,7 @@ def add( self.all_entities.add(s) if s not in self.entity_index: - self.entity_index[s] = {'to_be_deleted': False, 'resp_agent': resp_agent, 'source': primary_source} + self.entity_index[s] = {'to_be_deleted': False, 'is_restored': True, 'resp_agent': resp_agent, 'source': primary_source} return self @@ -378,7 +393,7 @@ def parse( self.all_entities.add(subject) if subject not in self.entity_index: - self.entity_index[subject] = {'to_be_deleted': False, 'resp_agent': resp_agent, 'source': primary_source} + self.entity_index[subject] = {'to_be_deleted': False, 'is_restored': True, 'resp_agent': resp_agent, 'source': primary_source} return context diff --git a/rdflib_ocdm/prov/provenance.py b/rdflib_ocdm/prov/provenance.py index 2bf99ed..47c4187 100644 --- a/rdflib_ocdm/prov/provenance.py +++ b/rdflib_ocdm/prov/provenance.py @@ -67,6 +67,18 @@ def generate_provenance(self, c_time: float = None) -> None: cur_snapshot.has_invalidation_time(cur_time) cur_snapshot.has_description(f"The entity '{str(cur_subj)}' has been deleted.") cur_snapshot.has_update_action(update_query) + elif cur_subj_metadata['is_restored']: + # RESTORATION SNAPSHOT + last_snapshot: SnapshotEntity = self.add_se(prov_subject=cur_subj, res=last_snapshot_res) + # Non settiamo l'invalidation time per il precedente snapshot in caso di restore + + cur_snapshot: SnapshotEntity = self._create_snapshot(cur_subj, cur_time) + cur_snapshot.derives_from(last_snapshot) + cur_snapshot.has_description(f"The entity '{str(cur_subj)}' has been restored.") + + update_query: str = get_update_query(self.prov_g, cur_subj)[0] + if update_query: + cur_snapshot.has_update_action(update_query) else: if last_snapshot_res is None: # CREATION SNAPSHOT diff --git a/test/provenance_test.py b/test/provenance_test.py index d33324f..1904316 100644 --- a/test/provenance_test.py +++ b/test/provenance_test.py @@ -46,7 +46,6 @@ def test_generate_provenance_creation_no_snapshot_modification_ocdm_graph(self): result = ocdm_graph.generate_provenance(c_time=self.cur_time) self.assertIsNone(result) se_a: SnapshotEntity = ocdm_graph.get_entity(f'{self.subject}/prov/se/1') - print(ocdm_graph.get_entity(f'{self.subject}/prov/se/1')) self.assertIsNotNone(se_a) self.assertIsInstance(se_a, SnapshotEntity) self.assertEqual(URIRef(self.subject), se_a.get_is_snapshot_of()) @@ -124,5 +123,45 @@ def test_generate_provenance_after_merge(self): self.assertEqual(se_id_0636064270_2.get_description(), "The entity 'https://w3id.org/oc/meta/id/0636064270' has been deleted.") self.assertEqual(se_id_0636064270_2.get_update_action(), "DELETE DATA { GRAPH { . } }") + def test_restore_deleted_entity(self): + # Setup grafo e entità iniziale + ocdm_graph = OCDMGraph() + subject_uri = URIRef(self.subject) + ocdm_graph.add((subject_uri, URIRef('http://purl.org/dc/terms/title'), Literal('Test Title'))) + ocdm_graph.preexisting_finished(c_time=self.cur_time) + + # Prima otteniamo lo snapshot di creazione + initial_snapshot: SnapshotEntity = ocdm_graph.get_entity(f'{self.subject}/prov/se/1') + self.assertIsNotNone(initial_snapshot) + self.assertEqual(initial_snapshot.get_description(), f"The entity '{self.subject}' has been created.") + + # Cancelliamo l'entità e generiamo la provenance + ocdm_graph.mark_as_deleted(subject_uri) + deletion_time = '2020-12-08T21:17:34+00:00' + ocdm_graph.generate_provenance(c_time=1607462254.846196) + + # Verifichiamo lo snapshot di cancellazione + deletion_snapshot: SnapshotEntity = ocdm_graph.get_entity(f'{self.subject}/prov/se/2') + self.assertEqual(deletion_snapshot.get_generation_time(), deletion_time) + self.assertEqual(deletion_snapshot.get_invalidation_time(), deletion_time) + self.assertEqual(deletion_snapshot.get_description(), f"The entity '{self.subject}' has been deleted.") + + # Ripristiniamo l'entità e aggiungiamo una modifica + ocdm_graph.mark_as_restored(subject_uri) + ocdm_graph.add((subject_uri, URIRef('http://purl.org/dc/terms/title'), Literal('Restored Title'))) + + # Generiamo la provenance dopo il ripristino + restore_time = '2020-12-09T21:17:34+00:00' + ocdm_graph.generate_provenance(c_time=1607548654.846196) + + # Verifichiamo lo snapshot di ripristino + restore_snapshot = ocdm_graph.get_entity(f'{self.subject}/prov/se/3') + self.assertIsNotNone(restore_snapshot) + self.assertEqual(restore_snapshot.get_generation_time(), restore_time) + self.assertIsNone(restore_snapshot.get_invalidation_time()) + self.assertEqual(restore_snapshot.get_description(), f"The entity '{self.subject}' has been restored.") + self.assertEqual(restore_snapshot.get_derives_from()[0].res, deletion_snapshot.res) + self.assertIsNotNone(restore_snapshot.get_update_action()) + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/test/storer_test.py b/test/storer_test.py index 5e52c9d..d3ef661 100644 --- a/test/storer_test.py +++ b/test/storer_test.py @@ -130,7 +130,6 @@ def test_upload_all_provenance(self): self.ts.setQuery(query) results = self.ts.queryAndConvert() results = {(result['g']['value'], result['s']['value'], result['p']['value'], result['o']['value']) for result in results['results']['bindings']} - print(results) expected_result = { ('https://w3id.org/oc/meta/br/0605/prov/', 'https://w3id.org/oc/meta/br/0605/prov/se/2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'http://www.w3.org/ns/prov#Entity'), ('https://w3id.org/oc/meta/br/0636066666/prov/', 'https://w3id.org/oc/meta/br/0636066666/prov/se/1', 'http://www.w3.org/ns/prov#generatedAtTime', '2020-12-07T21:17:34Z'),