import tempfile
import unittest
from pathlib import Path
from rdflib import Graph
from ontoenv import OntoEnv
class DictGraphStore:
def __init__(self) -> None:
self.graphs: dict[str, Graph] = {}
def add_graph(self, iri: str, graph: Graph, overwrite: bool = False) -> None:
if not overwrite and iri in self.graphs:
return
self.graphs[iri] = graph
def get_graph(self, iri: str) -> Graph:
return self.graphs[iri]
def remove_graph(self, iri: str) -> None:
del self.graphs[iri]
def graph_ids(self) -> list[str]:
return list(self.graphs.keys())
def size(self) -> dict[str, int]:
return {
"num_graphs": len(self.graphs),
"num_triples": sum(len(g) for g in self.graphs.values()),
}
TTL_DEMO = "\n".join(
[
"@prefix owl: <http://www.w3.org/2002/07/owl#> .",
"<http://example.com/demo> a owl:Ontology .",
"<http://example.com/demo> <http://example.com/p> \"v\" .",
]
)
TTL_IMPORTS = "\n".join(
[
"@prefix owl: <http://www.w3.org/2002/07/owl#> .",
"<http://example.com/base> a owl:Ontology .",
"<http://example.com/base> owl:imports <http://example.com/demo> .",
]
)
class TestPythonGraphStore(unittest.TestCase):
def test_python_graph_store_add_get(self) -> None:
with tempfile.TemporaryDirectory() as td:
ttl_path = Path(td) / "demo.ttl"
ttl_path.write_text(TTL_DEMO)
store = DictGraphStore()
env = OntoEnv(graph_store=store, temporary=True)
iri = env.add(str(ttl_path))
self.assertIn(iri, store.graphs)
g = env.get_graph(iri)
self.assertEqual(len(g), len(store.graphs[iri]))
def test_init_from_store_loads_existing_graphs(self) -> None:
from rdflib import Graph as RdflibGraph, URIRef
from rdflib.namespace import OWL, RDF
store = DictGraphStore()
g = RdflibGraph()
iri = "http://example.com/preloaded"
g.add((URIRef(iri), RDF.type, OWL.Ontology))
g.add((URIRef(iri), URIRef("http://example.com/p"), URIRef("http://example.com/o")))
store.add_graph(iri, g)
env = OntoEnv(graph_store=store, temporary=True, init_from_store=True)
names = env.get_ontology_names()
self.assertIn(iri, names, f"expected {iri} in {names}")
def test_init_from_store_empty_store(self) -> None:
store = DictGraphStore()
env = OntoEnv(graph_store=store, temporary=True, init_from_store=True)
self.assertEqual(env.get_ontology_names(), [])
def test_refresh_from_store_reflects_external_changes(self) -> None:
from rdflib import Graph as RdflibGraph, URIRef
from rdflib.namespace import OWL, RDF
store = DictGraphStore()
env = OntoEnv(graph_store=store, temporary=True, init_from_store=True)
self.assertEqual(env.get_ontology_names(), [])
g = RdflibGraph()
iri = "http://example.com/external"
g.add((URIRef(iri), RDF.type, OWL.Ontology))
store.add_graph(iri, g)
self.assertNotIn(iri, env.get_ontology_names())
env.refresh_from_store()
names_after = env.get_ontology_names()
self.assertIn(iri, names_after, f"expected {iri} in {names_after}")
def test_refresh_from_store_removes_deleted_graphs(self) -> None:
from rdflib import Graph as RdflibGraph, URIRef
from rdflib.namespace import OWL, RDF
store = DictGraphStore()
g = RdflibGraph()
iri = "http://example.com/todelete"
g.add((URIRef(iri), RDF.type, OWL.Ontology))
store.add_graph(iri, g)
env = OntoEnv(graph_store=store, temporary=True, init_from_store=True)
self.assertIn(iri, env.get_ontology_names())
store.remove_graph(iri)
env.refresh_from_store()
self.assertNotIn(iri, env.get_ontology_names())
class TestTransientGraphQueries(unittest.TestCase):
def _make_env_with_two_ontologies(self) -> tuple:
from rdflib import Graph as G, URIRef
from rdflib.namespace import OWL, RDF
dep_iri = "http://example.com/dep"
base_iri = "http://example.com/base"
store = DictGraphStore()
dep_g = G()
dep_g.add((URIRef(dep_iri), RDF.type, OWL.Ontology))
store.add_graph(dep_iri, dep_g)
base_g = G()
base_g.add((URIRef(base_iri), RDF.type, OWL.Ontology))
base_g.add((URIRef(base_iri), OWL.imports, URIRef(dep_iri)))
store.add_graph(base_iri, base_g)
env = OntoEnv(graph_store=store, temporary=True, init_from_store=True)
return env, base_iri, dep_iri
def test_list_closure_string_uri_unchanged(self) -> None:
env, base_iri, dep_iri = self._make_env_with_two_ontologies()
closure = env.list_closure(base_iri)
self.assertIn(base_iri, closure)
self.assertIn(dep_iri, closure)
def test_list_closure_transient_graph_returns_import_closure(self) -> None:
from rdflib import Graph as G, URIRef
from rdflib.namespace import OWL, RDF
env, base_iri, dep_iri = self._make_env_with_two_ontologies()
new_iri = "http://example.com/new"
transient = G()
transient.add((URIRef(new_iri), RDF.type, OWL.Ontology))
transient.add((URIRef(new_iri), OWL.imports, URIRef(base_iri)))
closure = env.list_closure(transient)
self.assertEqual(closure[0], new_iri)
self.assertIn(base_iri, closure)
self.assertIn(dep_iri, closure)
def test_list_closure_transient_graph_unresolvable_import_omitted(self) -> None:
from rdflib import Graph as G, URIRef
from rdflib.namespace import OWL, RDF
env, _, _ = self._make_env_with_two_ontologies()
ghost_iri = "http://example.com/ghost"
transient = G()
transient.add((URIRef("http://example.com/new2"), RDF.type, OWL.Ontology))
transient.add((URIRef("http://example.com/new2"), OWL.imports, URIRef(ghost_iri)))
closure = env.list_closure(transient)
self.assertNotIn(ghost_iri, closure)
def test_list_closure_wrong_type_raises(self) -> None:
env, _, _ = self._make_env_with_two_ontologies()
with self.assertRaises(TypeError):
env.list_closure(12345)
def test_missing_imports_string_uri_unchanged(self) -> None:
env, base_iri, _ = self._make_env_with_two_ontologies()
self.assertEqual(env.missing_imports(base_iri), [])
def test_missing_imports_transient_graph_all_present(self) -> None:
from rdflib import Graph as G, URIRef
from rdflib.namespace import OWL, RDF
env, base_iri, _ = self._make_env_with_two_ontologies()
transient = G()
transient.add((URIRef("http://example.com/top"), RDF.type, OWL.Ontology))
transient.add((URIRef("http://example.com/top"), OWL.imports, URIRef(base_iri)))
self.assertEqual(env.missing_imports(transient), [])
def test_missing_imports_transient_graph_direct_missing(self) -> None:
from rdflib import Graph as G, URIRef
from rdflib.namespace import OWL, RDF
env, _, _ = self._make_env_with_two_ontologies()
ghost_iri = "http://example.com/ghost"
transient = G()
transient.add((URIRef("http://example.com/top"), RDF.type, OWL.Ontology))
transient.add((URIRef("http://example.com/top"), OWL.imports, URIRef(ghost_iri)))
missing = env.missing_imports(transient)
self.assertIn(ghost_iri, missing)
def test_missing_imports_transient_graph_transitive_missing(self) -> None:
from rdflib import Graph as G, URIRef
from rdflib.namespace import OWL, RDF
env, base_iri, dep_iri = self._make_env_with_two_ontologies()
ghost_iri = "http://example.com/ghost"
mid_iri = "http://example.com/mid"
store_ext = DictGraphStore()
mid_g = G()
mid_g.add((URIRef(mid_iri), RDF.type, OWL.Ontology))
mid_g.add((URIRef(mid_iri), OWL.imports, URIRef(ghost_iri)))
store_ext.add_graph(mid_iri, mid_g)
env2 = OntoEnv(graph_store=store_ext, temporary=True, init_from_store=True)
transient = G()
transient.add((URIRef("http://example.com/top"), RDF.type, OWL.Ontology))
transient.add((URIRef("http://example.com/top"), OWL.imports, URIRef(mid_iri)))
missing = env2.missing_imports(transient)
self.assertIn(ghost_iri, missing)
def test_missing_imports_wrong_type_raises(self) -> None:
env, _, _ = self._make_env_with_two_ontologies()
with self.assertRaises(TypeError):
env.missing_imports(42)
if __name__ == "__main__":
unittest.main()