Files
AI/참고/ontocast-main/ontocast/tool/triple_manager/mock.py
2026-05-12 19:40:31 +09:00

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