503 lines
17 KiB
Python
503 lines
17 KiB
Python
"""Mock triple store implementations for testing.
|
|
|
|
This module provides mock implementations of triple store managers that simulate
|
|
the behavior of real triple stores (Fuseki, Neo4j) without requiring external
|
|
services. These mocks are useful for testing and development.
|
|
|
|
The mocks maintain in-memory storage and provide the same interface as the
|
|
real implementations, allowing tests to run without external dependencies.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Any, Dict, List
|
|
|
|
from pydantic import Field
|
|
from rdflib import Graph, URIRef
|
|
from rdflib.namespace import OWL, RDF
|
|
|
|
from ontocast.onto.ontology import Ontology
|
|
from ontocast.onto.rdfgraph import RDFGraph
|
|
from ontocast.onto.util import derive_ontology_id
|
|
from ontocast.tool.triple_manager.core import (
|
|
TripleStoreManager,
|
|
TripleStoreManagerWithAuth,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class MockTripleStoreManager(TripleStoreManager):
|
|
"""Mock triple store manager for testing.
|
|
|
|
This class provides an in-memory implementation of triple store operations
|
|
that simulates the behavior of real triple stores without requiring external
|
|
services. It stores ontologies and graphs in memory and provides the same
|
|
interface as concrete implementations.
|
|
|
|
Attributes:
|
|
ontologies: In-memory storage for ontologies.
|
|
graphs: In-memory storage for RDF graphs.
|
|
"""
|
|
|
|
model_config = {"arbitrary_types_allowed": True}
|
|
|
|
ontologies: List[Ontology] = Field(
|
|
default_factory=list, description="In-memory storage for ontologies"
|
|
)
|
|
graphs: Dict[str, Graph] = Field(
|
|
default_factory=dict, description="In-memory storage for RDF graphs"
|
|
)
|
|
|
|
def __init__(self, **kwargs):
|
|
"""Initialize the mock triple store manager.
|
|
|
|
Args:
|
|
**kwargs: Additional keyword arguments passed to the parent class.
|
|
"""
|
|
super().__init__(**kwargs)
|
|
|
|
def fetch_ontologies(self) -> List[Ontology]:
|
|
"""Fetch all available ontologies from the mock store.
|
|
|
|
Returns:
|
|
List[Ontology]: List of available ontologies with their graphs.
|
|
"""
|
|
return self.ontologies.copy()
|
|
|
|
def serialize_graph(self, graph: Graph, **kwargs) -> bool | None:
|
|
"""Store an RDF graph in the mock store.
|
|
|
|
Args:
|
|
graph: The RDF graph to store.
|
|
**kwargs: Optional keyword arguments including graph_uri.
|
|
|
|
Returns:
|
|
bool: True if the graph was stored successfully.
|
|
"""
|
|
graph_uri = kwargs.get("graph_uri")
|
|
# Create a new Graph and copy all triples
|
|
new_graph = Graph()
|
|
for triple in graph:
|
|
new_graph.add(triple)
|
|
|
|
if graph_uri:
|
|
self.graphs[graph_uri] = new_graph
|
|
else:
|
|
# Generate a default URI based on graph content
|
|
graph_uri = f"mock://graph/{len(self.graphs)}"
|
|
self.graphs[graph_uri] = new_graph
|
|
|
|
# Try to extract ontology information from the graph
|
|
ontology_id = self._extract_ontology_id(graph)
|
|
if ontology_id:
|
|
ontology = Ontology(
|
|
ontology_id=ontology_id,
|
|
title=f"Mock Ontology {ontology_id}",
|
|
description="Mock ontology for testing",
|
|
version="1.0.0",
|
|
iri=graph_uri,
|
|
graph=self._create_rdf_graph_from_graph(graph),
|
|
)
|
|
# Update existing ontology or add new one
|
|
existing = next(
|
|
(o for o in self.ontologies if o.ontology_id == ontology_id), None
|
|
)
|
|
if existing:
|
|
existing.graph = self._create_rdf_graph_from_graph(graph)
|
|
existing.iri = graph_uri
|
|
else:
|
|
self.ontologies.append(ontology)
|
|
|
|
return True
|
|
|
|
def serialize(self, o: Ontology | RDFGraph, **kwargs) -> bool | None:
|
|
"""Store an Ontology or RDFGraph in the mock store.
|
|
|
|
Args:
|
|
o: Ontology or RDFGraph object to store.
|
|
**kwargs: Additional keyword arguments.
|
|
|
|
Returns:
|
|
bool: True if the object was stored successfully.
|
|
"""
|
|
if isinstance(o, Ontology):
|
|
graph = o.graph
|
|
graph_uri = o.iri
|
|
elif isinstance(o, RDFGraph):
|
|
graph = o
|
|
graph_uri = kwargs.get("graph_uri")
|
|
else:
|
|
raise TypeError(f"unsupported obj of type {type(o)} received")
|
|
|
|
return self.serialize_graph(graph, graph_uri=graph_uri)
|
|
|
|
def _extract_ontology_id(self, graph: Graph) -> str | None:
|
|
"""Extract ontology ID from graph content.
|
|
|
|
Args:
|
|
graph: The RDF graph to analyze.
|
|
|
|
Returns:
|
|
str | None: The extracted ontology ID, or None if not found.
|
|
"""
|
|
# Look for owl:Ontology declarations
|
|
for s, p, o in graph.triples((None, RDF.type, OWL.Ontology)):
|
|
if isinstance(s, URIRef):
|
|
return derive_ontology_id(str(s))
|
|
return None
|
|
|
|
def clear(self):
|
|
"""Clear all stored data."""
|
|
self.ontologies.clear()
|
|
self.graphs.clear()
|
|
|
|
async def clean(self, dataset: str | None = None) -> None:
|
|
"""Clean/flush data from the mock triple store.
|
|
|
|
Args:
|
|
dataset: Optional dataset name (ignored for mock, kept for interface compatibility).
|
|
"""
|
|
self.clear()
|
|
|
|
def _create_rdf_graph_from_graph(self, graph: Graph) -> RDFGraph:
|
|
"""Create an RDFGraph from a regular Graph by copying all triples.
|
|
|
|
Args:
|
|
graph: The source graph to copy from.
|
|
|
|
Returns:
|
|
RDFGraph: A new RDFGraph with all triples copied.
|
|
"""
|
|
rdf_graph = RDFGraph()
|
|
for triple in graph:
|
|
rdf_graph.add(triple)
|
|
return rdf_graph
|
|
|
|
|
|
class MockFusekiTripleStoreManager(TripleStoreManagerWithAuth):
|
|
"""Mock Fuseki triple store manager for testing.
|
|
|
|
This class simulates the behavior of FusekiTripleStoreManager without
|
|
requiring an actual Fuseki server. It maintains in-memory storage and
|
|
provides the same interface as the real implementation.
|
|
|
|
Attributes:
|
|
dataset: The mock dataset name.
|
|
ontologies_dataset: The mock ontologies dataset name.
|
|
ontologies: In-memory storage for ontologies.
|
|
graphs: In-memory storage for RDF graphs.
|
|
"""
|
|
|
|
model_config = {"arbitrary_types_allowed": True}
|
|
|
|
dataset: str | None = None
|
|
ontologies_dataset: str = "ontologies"
|
|
ontologies: List[Ontology] = Field(
|
|
default_factory=list, description="In-memory storage for ontologies"
|
|
)
|
|
graphs: Dict[str, Graph] = Field(
|
|
default_factory=dict, description="In-memory storage for RDF graphs"
|
|
)
|
|
|
|
def __init__(
|
|
self,
|
|
uri=None,
|
|
auth=None,
|
|
dataset=None,
|
|
ontologies_dataset=None,
|
|
clean=False,
|
|
**kwargs,
|
|
):
|
|
"""Initialize the mock Fuseki triple store manager.
|
|
|
|
Args:
|
|
uri: Mock URI (ignored but kept for interface compatibility).
|
|
auth: Mock authentication (ignored but kept for interface compatibility).
|
|
dataset: Mock dataset name.
|
|
ontologies_dataset: Mock ontologies dataset name.
|
|
clean: Whether to clean the store on initialization.
|
|
**kwargs: Additional keyword arguments.
|
|
"""
|
|
super().__init__(uri=uri, auth=auth, **kwargs)
|
|
self.dataset = dataset or "test"
|
|
self.ontologies_dataset = ontologies_dataset or "ontologies"
|
|
|
|
if clean:
|
|
self.clear()
|
|
|
|
def fetch_ontologies(self) -> List[Ontology]:
|
|
"""Fetch all available ontologies from the mock store.
|
|
|
|
Returns:
|
|
List[Ontology]: List of available ontologies with their graphs.
|
|
"""
|
|
return self.ontologies.copy()
|
|
|
|
def serialize_graph(self, graph: Graph, **kwargs) -> bool | None:
|
|
"""Store an RDF graph in the mock store.
|
|
|
|
Args:
|
|
graph: The RDF graph to store.
|
|
**kwargs: Optional keyword arguments including graph_uri.
|
|
|
|
Returns:
|
|
bool: True if the graph was stored successfully.
|
|
"""
|
|
graph_uri = kwargs.get("graph_uri")
|
|
# Create a new Graph and copy all triples
|
|
new_graph = Graph()
|
|
for triple in graph:
|
|
new_graph.add(triple)
|
|
|
|
if graph_uri:
|
|
self.graphs[graph_uri] = new_graph
|
|
else:
|
|
# Generate a default URI based on graph content
|
|
graph_uri = f"mock://{self.dataset}/graph/{len(self.graphs)}"
|
|
self.graphs[graph_uri] = new_graph
|
|
|
|
# Try to extract ontology information from the graph
|
|
ontology_id = self._extract_ontology_id(graph)
|
|
if ontology_id:
|
|
ontology = Ontology(
|
|
ontology_id=ontology_id,
|
|
title=f"Mock Ontology {ontology_id}",
|
|
description="Mock ontology for testing",
|
|
version="1.0.0",
|
|
iri=graph_uri,
|
|
graph=self._create_rdf_graph_from_graph(graph),
|
|
)
|
|
# Update existing ontology or add new one
|
|
existing = next(
|
|
(o for o in self.ontologies if o.ontology_id == ontology_id), None
|
|
)
|
|
if existing:
|
|
existing.graph = self._create_rdf_graph_from_graph(graph)
|
|
existing.iri = graph_uri
|
|
else:
|
|
self.ontologies.append(ontology)
|
|
|
|
return True
|
|
|
|
def serialize(self, o: Ontology | RDFGraph, **kwargs) -> bool | None:
|
|
"""Store an Ontology or RDFGraph in the mock store.
|
|
|
|
Args:
|
|
o: Ontology or RDFGraph object to store.
|
|
**kwargs: Additional keyword arguments.
|
|
|
|
Returns:
|
|
bool: True if the object was stored successfully.
|
|
"""
|
|
if isinstance(o, Ontology):
|
|
graph = o.graph
|
|
graph_uri = o.iri
|
|
elif isinstance(o, RDFGraph):
|
|
graph = o
|
|
graph_uri = kwargs.get("graph_uri")
|
|
else:
|
|
raise TypeError(f"unsupported obj of type {type(o)} received")
|
|
|
|
return self.serialize_graph(graph, graph_uri=graph_uri)
|
|
|
|
def _extract_ontology_id(self, graph: Graph) -> str | None:
|
|
"""Extract ontology ID from graph content.
|
|
|
|
Args:
|
|
graph: The RDF graph to analyze.
|
|
|
|
Returns:
|
|
str | None: The extracted ontology ID, or None if not found.
|
|
"""
|
|
# Look for owl:Ontology declarations
|
|
for s, p, o in graph.triples((None, RDF.type, OWL.Ontology)):
|
|
if isinstance(s, URIRef):
|
|
return derive_ontology_id(str(s))
|
|
return None
|
|
|
|
def clear(self):
|
|
"""Clear all stored data."""
|
|
self.ontologies.clear()
|
|
self.graphs.clear()
|
|
|
|
async def clean(self, dataset: str | None = None) -> None:
|
|
"""Clean/flush data from the mock Fuseki triple store.
|
|
|
|
Args:
|
|
dataset: Optional dataset name (ignored for mock, kept for interface compatibility).
|
|
"""
|
|
self.clear()
|
|
|
|
def _create_rdf_graph_from_graph(self, graph: Graph) -> RDFGraph:
|
|
"""Create an RDFGraph from a regular Graph by copying all triples.
|
|
|
|
Args:
|
|
graph: The source graph to copy from.
|
|
|
|
Returns:
|
|
RDFGraph: A new RDFGraph with all triples copied.
|
|
"""
|
|
rdf_graph = RDFGraph()
|
|
for triple in graph:
|
|
rdf_graph.add(triple)
|
|
return rdf_graph
|
|
|
|
|
|
class MockNeo4jTripleStoreManager(TripleStoreManagerWithAuth):
|
|
"""Mock Neo4j triple store manager for testing.
|
|
|
|
This class simulates the behavior of Neo4jTripleStoreManager without
|
|
requiring an actual Neo4j server. It maintains in-memory storage and
|
|
provides the same interface as the real implementation.
|
|
|
|
Attributes:
|
|
ontologies: In-memory storage for ontologies.
|
|
graphs: In-memory storage for RDF graphs.
|
|
"""
|
|
|
|
model_config = {"arbitrary_types_allowed": True}
|
|
|
|
ontologies: List[Ontology] = Field(
|
|
default_factory=list, description="In-memory storage for ontologies"
|
|
)
|
|
graphs: Dict[str, Graph] = Field(
|
|
default_factory=dict, description="In-memory storage for RDF graphs"
|
|
)
|
|
|
|
def __init__(self, uri=None, auth=None, clean=False, **kwargs):
|
|
"""Initialize the mock Neo4j triple store manager.
|
|
|
|
Args:
|
|
uri: Mock URI (ignored but kept for interface compatibility).
|
|
auth: Mock authentication (ignored but kept for interface compatibility).
|
|
clean: Whether to clean the store on initialization.
|
|
**kwargs: Additional keyword arguments.
|
|
"""
|
|
super().__init__(uri=uri, auth=auth, **kwargs)
|
|
|
|
if clean:
|
|
self.clear()
|
|
|
|
def fetch_ontologies(self) -> List[Ontology]:
|
|
"""Fetch all available ontologies from the mock store.
|
|
|
|
Returns:
|
|
List[Ontology]: List of available ontologies with their graphs.
|
|
"""
|
|
return self.ontologies.copy()
|
|
|
|
def serialize_graph(self, graph: Graph, **kwargs) -> Dict[str, Any] | None: # type: ignore[override]
|
|
"""Store an RDF graph in the mock store.
|
|
|
|
Args:
|
|
graph: The RDF graph to store.
|
|
**kwargs: Optional keyword arguments including graph_uri.
|
|
|
|
Returns:
|
|
Dict[str, Any]: Mock summary of the operation.
|
|
"""
|
|
graph_uri = kwargs.get("graph_uri")
|
|
# Create a new Graph and copy all triples
|
|
new_graph = Graph()
|
|
for triple in graph:
|
|
new_graph.add(triple)
|
|
|
|
if graph_uri:
|
|
self.graphs[graph_uri] = new_graph
|
|
else:
|
|
# Generate a default URI based on graph content
|
|
graph_uri = f"mock://neo4j/graph/{len(self.graphs)}"
|
|
self.graphs[graph_uri] = new_graph
|
|
|
|
# Try to extract ontology information from the graph
|
|
ontology_id = self._extract_ontology_id(graph)
|
|
if ontology_id:
|
|
ontology = Ontology(
|
|
ontology_id=ontology_id,
|
|
title=f"Mock Ontology {ontology_id}",
|
|
description="Mock ontology for testing",
|
|
version="1.0.0",
|
|
iri=graph_uri,
|
|
graph=self._create_rdf_graph_from_graph(graph),
|
|
)
|
|
# Update existing ontology or add new one
|
|
existing = next(
|
|
(o for o in self.ontologies if o.ontology_id == ontology_id), None
|
|
)
|
|
if existing:
|
|
existing.graph = self._create_rdf_graph_from_graph(graph)
|
|
existing.iri = graph_uri
|
|
else:
|
|
self.ontologies.append(ontology)
|
|
|
|
# Return mock summary similar to Neo4j
|
|
return {
|
|
"nodes_created": len(graph),
|
|
"relationships_created": 0,
|
|
"properties_set": len(graph),
|
|
"labels_added": 1,
|
|
}
|
|
|
|
def serialize(self, o: Ontology | RDFGraph, **kwargs) -> Dict[str, Any] | None: # type: ignore[override]
|
|
"""Store an Ontology or RDFGraph in the mock store.
|
|
|
|
Args:
|
|
o: Ontology or RDFGraph object to store.
|
|
**kwargs: Additional keyword arguments.
|
|
|
|
Returns:
|
|
Dict[str, Any]: Mock summary of the operation.
|
|
"""
|
|
if isinstance(o, Ontology):
|
|
graph = o.graph
|
|
graph_uri = o.iri
|
|
elif isinstance(o, RDFGraph):
|
|
graph = o
|
|
graph_uri = kwargs.get("graph_uri")
|
|
else:
|
|
raise TypeError(f"unsupported obj of type {type(o)} received")
|
|
|
|
return self.serialize_graph(graph, graph_uri=graph_uri)
|
|
|
|
def _extract_ontology_id(self, graph: Graph) -> str | None:
|
|
"""Extract ontology ID from graph content.
|
|
|
|
Args:
|
|
graph: The RDF graph to analyze.
|
|
|
|
Returns:
|
|
str | None: The extracted ontology ID, or None if not found.
|
|
"""
|
|
# Look for owl:Ontology declarations
|
|
for s, p, o in graph.triples((None, RDF.type, OWL.Ontology)):
|
|
if isinstance(s, URIRef):
|
|
return derive_ontology_id(str(s))
|
|
return None
|
|
|
|
def clear(self):
|
|
"""Clear all stored data."""
|
|
self.ontologies.clear()
|
|
self.graphs.clear()
|
|
|
|
async def clean(self, dataset: str | None = None) -> None:
|
|
"""Clean/flush data from the mock Neo4j triple store.
|
|
|
|
Args:
|
|
dataset: Optional dataset name (ignored for Neo4j mock, kept for interface compatibility).
|
|
"""
|
|
self.clear()
|
|
|
|
def _create_rdf_graph_from_graph(self, graph: Graph) -> RDFGraph:
|
|
"""Create an RDFGraph from a regular Graph by copying all triples.
|
|
|
|
Args:
|
|
graph: The source graph to copy from.
|
|
|
|
Returns:
|
|
RDFGraph: A new RDFGraph with all triples copied.
|
|
"""
|
|
rdf_graph = RDFGraph()
|
|
for triple in graph:
|
|
rdf_graph.add(triple)
|
|
return rdf_graph
|