mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
feat(ontology): Implement OntologyIngestor and update exports
- Added OntologyIngestor in semantica/ingest/ontology_ingestor.py - Updated semantica/ontology/__init__.py to export OntologyIngestor - Updated semantica/ingest/methods.py to use OntologyIngestor - Added tests for ontology ingestion - Cleaned up temporary files
This commit is contained in:
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **Ontology Ingestion Module**:
|
||||
- Implemented `OntologyIngestor` in `semantica.ingest` for parsing RDF/OWL files (Turtle, RDF/XML, JSON-LD, N3) into standardized `OntologyData` objects.
|
||||
- Added `ingest_ontology` convenience function and integrated it into the unified `ingest(source_type="ontology")` interface.
|
||||
- Added recursive directory scanning support for batch ontology ingestion.
|
||||
- Exposed ingestion tools in `semantica.ontology` for better discoverability.
|
||||
- Added `OntologyData` dataclass for consistent metadata handling (source path, format, timestamps).
|
||||
- **Documentation**:
|
||||
- **Ontology Usage Guide**: Updated `ontology_usage.md` with comprehensive examples for single-file and directory ingestion.
|
||||
- **API Reference**: Updated `ontology.md` with `OntologyIngestor` class documentation and method details.
|
||||
- **Tests**:
|
||||
- **Comprehensive Test Suite**: Added `tests/ingest/test_ontology_ingestor.py` covering all supported formats, error handling, and unified interface integration.
|
||||
- **Demo Script**: Added `examples/demo_ontology_ingest.py` for end-to-end usage demonstration.
|
||||
|
||||
## [0.2.3] - 2026-01-20
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -63,6 +63,29 @@ The module uses several inference algorithms:
|
||||
|
||||
---
|
||||
|
||||
## Ontology Ingestion
|
||||
|
||||
Ingest existing ontology files directly into usable data structures using `OntologyIngestor`.
|
||||
|
||||
**Function:** `ingest_ontology(source, method="file")`
|
||||
|
||||
| Argument | Description |
|
||||
|----------|-------------|
|
||||
| `source` | File path, directory path, or list of paths |
|
||||
| `method` | Ingestion method (default: "file") |
|
||||
|
||||
**Example:**
|
||||
|
||||
```python
|
||||
from semantica.ontology import ingest_ontology
|
||||
|
||||
# Ingest file
|
||||
data = ingest_ontology("ontology.ttl")
|
||||
|
||||
# Ingest directory
|
||||
dataset = ingest_ontology("ontologies/")
|
||||
```
|
||||
|
||||
## Main Classes
|
||||
|
||||
### OntologyEngine
|
||||
@@ -170,6 +193,17 @@ Manages external dependencies.
|
||||
| `import_external_ontology(uri, ontology)` | Load and merge external ontology |
|
||||
| `evaluate_alignment(uri, ontology)` | Assess alignment and compatibility |
|
||||
|
||||
### OntologyIngestor
|
||||
|
||||
Handles ingestion of existing ontologies from files and directories.
|
||||
|
||||
**Methods:**
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `ingest_ontology(file_path)` | Ingest a single ontology file |
|
||||
| `ingest_directory(directory_path)` | Recursively ingest ontology files from a directory |
|
||||
|
||||
---
|
||||
|
||||
## Unified Engine Examples
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from semantica.ingest import ingest, ingest_ontology, OntologyData
|
||||
|
||||
def demo_ontology_ingestion():
|
||||
print("=== Ontology Ingestion Demo ===")
|
||||
|
||||
# Create a sample ontology file
|
||||
sample_ttl = """
|
||||
@prefix : <http://example.org/demo/> .
|
||||
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
||||
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
|
||||
<http://example.org/demo/> rdf:type owl:Ontology ;
|
||||
rdfs:label "Demo Ontology" ;
|
||||
rdfs:comment "A simple ontology for demonstration." .
|
||||
|
||||
:DemoClass rdf:type owl:Class ;
|
||||
rdfs:label "Demo Class" .
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".ttl", mode="w") as tmp:
|
||||
tmp.write(sample_ttl)
|
||||
tmp_path = tmp.name
|
||||
|
||||
print(f"\nCreated temporary ontology file: {tmp_path}")
|
||||
|
||||
try:
|
||||
# 1. Use ingest_ontology convenience function
|
||||
print("\n--- Method 1: ingest_ontology() ---")
|
||||
result = ingest_ontology(tmp_path)
|
||||
|
||||
if isinstance(result, OntologyData):
|
||||
print(f"Success! Ingested ontology: {result.data.get('name')}")
|
||||
print(f"Format: {result.metadata.get('format')}")
|
||||
print(f"Classes found: {len(result.data.get('classes', []))}")
|
||||
for cls in result.data.get('classes', []):
|
||||
print(f" - {cls.get('name')} ({cls.get('uri')})")
|
||||
else:
|
||||
print("Unexpected result type:", type(result))
|
||||
|
||||
# 2. Use unified ingest function
|
||||
print("\n--- Method 2: Unified ingest() ---")
|
||||
# Explicitly setting source_type="ontology" ensures it uses OntologyIngestor
|
||||
unified_result = ingest(tmp_path, source_type="ontology")
|
||||
|
||||
if "ontology" in unified_result:
|
||||
ont_data = unified_result["ontology"]
|
||||
if isinstance(ont_data, OntologyData):
|
||||
print(f"Success! Ingested via unified interface.")
|
||||
print(f"Ontology Name: {ont_data.data.get('name')}")
|
||||
else:
|
||||
print(f"Got 'ontology' key but value is {type(ont_data)}")
|
||||
else:
|
||||
print("Unified ingest result keys:", unified_result.keys())
|
||||
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.remove(tmp_path)
|
||||
print(f"\nCleaned up temporary file.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo_ontology_ingestion()
|
||||
@@ -86,6 +86,7 @@ Main Classes:
|
||||
- RepoIngestor: Git repository processing
|
||||
- EmailIngestor: Email protocol handling
|
||||
- DBIngestor: Database export handling
|
||||
- OntologyIngestor: Ontology file processing
|
||||
- MethodRegistry: Registry for custom ingestion methods
|
||||
- IngestConfig: Configuration manager for ingest module
|
||||
|
||||
@@ -98,6 +99,7 @@ Convenience Functions:
|
||||
- ingest_repository: Repository ingestion wrapper
|
||||
- ingest_email: Email ingestion wrapper
|
||||
- ingest_database: Database ingestion wrapper
|
||||
- ingest_ontology: Ontology ingestion wrapper
|
||||
|
||||
|
||||
Example Usage:
|
||||
@@ -134,6 +136,7 @@ from .methods import (
|
||||
ingest_feed,
|
||||
ingest_file,
|
||||
ingest_mcp,
|
||||
ingest_ontology,
|
||||
ingest_repository,
|
||||
ingest_stream,
|
||||
ingest_web,
|
||||
@@ -166,6 +169,8 @@ from .web_ingestor import (
|
||||
WebIngestor,
|
||||
)
|
||||
|
||||
from .ontology_ingestor import OntologyData, OntologyIngestor
|
||||
|
||||
__all__ = [
|
||||
# File ingestion
|
||||
"FileIngestor",
|
||||
@@ -216,6 +221,9 @@ __all__ = [
|
||||
"MCPClient",
|
||||
"MCPResource",
|
||||
"MCPTool",
|
||||
# Ontology ingestion
|
||||
"OntologyIngestor",
|
||||
"OntologyData",
|
||||
# Registry and Methods
|
||||
"MethodRegistry",
|
||||
"method_registry",
|
||||
@@ -227,6 +235,7 @@ __all__ = [
|
||||
"ingest_repository",
|
||||
"ingest_email",
|
||||
"ingest_database",
|
||||
"ingest_ontology",
|
||||
"ingest_mcp",
|
||||
"get_ingest_method",
|
||||
"list_available_methods",
|
||||
|
||||
@@ -150,6 +150,7 @@ from .email_ingestor import EmailData, EmailIngestor
|
||||
from .feed_ingestor import FeedData, FeedIngestor
|
||||
from .file_ingestor import FileIngestor, FileObject
|
||||
from .mcp_ingestor import MCPData, MCPIngestor
|
||||
from .ontology_ingestor import OntologyData, OntologyIngestor
|
||||
from .registry import method_registry
|
||||
from .repo_ingestor import RepoIngestor
|
||||
from .stream_ingestor import StreamIngestor, StreamProcessor
|
||||
@@ -537,6 +538,66 @@ def ingest_email(
|
||||
raise
|
||||
|
||||
|
||||
def ingest_ontology(
|
||||
source: Union[str, Path, List[Union[str, Path]]], method: str = "file", **kwargs
|
||||
) -> Union[OntologyData, List[OntologyData]]:
|
||||
"""
|
||||
Ingest ontology from source (convenience function).
|
||||
|
||||
This is a user-friendly wrapper that ingests ontologies using the specified method.
|
||||
|
||||
Args:
|
||||
source: Ontology file path, directory path, or list of paths
|
||||
method: Ingestion method (default: "file")
|
||||
- "file": Single file ingestion
|
||||
- "directory": Directory ingestion with recursive scanning
|
||||
**kwargs: Additional options passed to OntologyIngestor
|
||||
|
||||
Returns:
|
||||
OntologyData, List[OntologyData] with ingestion results
|
||||
|
||||
Examples:
|
||||
>>> from semantica.ingest.methods import ingest_ontology
|
||||
>>> ontology = ingest_ontology("ontology.ttl")
|
||||
>>> ontologies = ingest_ontology("./ontologies", method="directory")
|
||||
"""
|
||||
# Check for custom method in registry
|
||||
custom_method = method_registry.get("ontology", method)
|
||||
if custom_method and custom_method != ingest_ontology:
|
||||
try:
|
||||
return custom_method(source, **kwargs)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Custom method {method} failed: {e}, falling back to default"
|
||||
)
|
||||
|
||||
try:
|
||||
# Get config
|
||||
config = ingest_config.get_method_config("ontology")
|
||||
config.update(kwargs)
|
||||
|
||||
ingestor = OntologyIngestor(**config)
|
||||
|
||||
source_path = str(source) if isinstance(source, (str, Path)) else None
|
||||
|
||||
if method == "file" and source_path:
|
||||
if isinstance(source, list):
|
||||
return [ingestor.ingest_ontology(str(s), **kwargs) for s in source]
|
||||
return ingestor.ingest_ontology(source_path, **kwargs)
|
||||
elif method == "directory" and source_path:
|
||||
recursive = kwargs.get("recursive", ingest_config.get("recursive", True))
|
||||
return ingestor.ingest_directory(source_path, recursive=recursive, **kwargs)
|
||||
else:
|
||||
# Default: try as file
|
||||
if isinstance(source, list):
|
||||
return [ingestor.ingest_ontology(str(s), **kwargs) for s in source]
|
||||
return ingestor.ingest_ontology(str(source), **kwargs)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to ingest ontology: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def ingest_database(
|
||||
source: Union[str, Dict[str, Any]], method: Optional[str] = None, **kwargs
|
||||
) -> Union[TableData, List[TableData], Dict[str, Any]]:
|
||||
@@ -769,6 +830,7 @@ def ingest(
|
||||
- "repo": Repository ingestion
|
||||
- "email": Email ingestion
|
||||
- "db": Database ingestion
|
||||
- "ontology": Ontology ingestion
|
||||
method: Optional specific ingestion method
|
||||
**kwargs: Additional options passed to ingestor
|
||||
|
||||
@@ -802,6 +864,8 @@ def ingest(
|
||||
("git@", "https://github.com", "https://gitlab.com")
|
||||
):
|
||||
source_type = "repo"
|
||||
elif source_str.endswith((".ttl", ".owl", ".rdf", ".jsonld", ".n3", ".nt")):
|
||||
source_type = "ontology"
|
||||
else:
|
||||
source_type = "file"
|
||||
else:
|
||||
@@ -830,6 +894,8 @@ def ingest(
|
||||
raise ProcessingError("Email ingestion requires configuration dictionary")
|
||||
elif source_type == "db":
|
||||
return {"data": ingest_database(sources, method=method, **kwargs)}
|
||||
elif source_type == "ontology":
|
||||
return {"ontology": ingest_ontology(sources, method=method or "file", **kwargs)}
|
||||
elif source_type == "mcp":
|
||||
return {"data": ingest_mcp(sources, method=method or "resources", **kwargs)}
|
||||
else:
|
||||
@@ -909,5 +975,8 @@ method_registry.register("mcp", "default", ingest_mcp)
|
||||
method_registry.register("mcp", "resources", ingest_mcp)
|
||||
method_registry.register("mcp", "tools", ingest_mcp)
|
||||
method_registry.register("mcp", "all", ingest_mcp)
|
||||
method_registry.register("ontology", "default", ingest_ontology)
|
||||
method_registry.register("ontology", "file", ingest_ontology)
|
||||
method_registry.register("ontology", "directory", ingest_ontology)
|
||||
method_registry.register("ingest", "default", ingest)
|
||||
method_registry.register("ingest", "unified", ingest)
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
"""
|
||||
Ontology Ingestion Module
|
||||
|
||||
This module provides capabilities to ingest external ontologies from files (OWL, RDF, TTL, etc.)
|
||||
and convert them into Semantica's internal ontology dictionary format.
|
||||
|
||||
Supported Formats:
|
||||
- Turtle (.ttl): Terse RDF Triple Language. A concise, human-readable
|
||||
format for representing RDF graphs. Commonly used for writing
|
||||
ontologies by hand.
|
||||
- RDF/XML (.rdf, .owl): The XML serialization of RDF. The standard
|
||||
format for OWL (Web Ontology Language) ontologies and often used
|
||||
for data interchange.
|
||||
- JSON-LD (.jsonld): JSON for Linked Data. A lightweight Linked Data
|
||||
format that is easy for humans to read and for machines to parse
|
||||
and generate. Ideal for web-based applications.
|
||||
- N-Triples (.nt): A line-based, plain text format for encoding an
|
||||
RDF graph. Each line represents a single triple. Very simple to
|
||||
parse but verbose.
|
||||
- Notation3 (.n3): A superset of Turtle that adds features like logic
|
||||
and rules.
|
||||
|
||||
Key Features:
|
||||
- Support for multiple RDF formats (Turtle, RDF/XML, JSON-LD, N3, NT)
|
||||
- Automatic parsing using rdflib
|
||||
- Conversion to Semantica ontology structure
|
||||
- Batch processing of ontology files
|
||||
- Extraction of classes, properties, and metadata
|
||||
|
||||
Example Usage:
|
||||
>>> from semantica.ingest import OntologyIngestor
|
||||
>>> ingestor = OntologyIngestor()
|
||||
>>> ontology = ingestor.ingest_ontology("my_ontology.ttl")
|
||||
"""
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
import rdflib
|
||||
from rdflib import RDF, RDFS, OWL, Graph
|
||||
|
||||
from ..utils.exceptions import ProcessingError, ValidationError
|
||||
from ..utils.logging import get_logger
|
||||
from ..utils.progress_tracker import get_progress_tracker
|
||||
|
||||
|
||||
@dataclass
|
||||
class OntologyData:
|
||||
"""Ontology data representation."""
|
||||
|
||||
data: Dict[str, Any]
|
||||
source_path: str
|
||||
format: str
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
ingested_at: datetime = field(default_factory=datetime.now)
|
||||
|
||||
|
||||
class OntologyIngestor:
|
||||
"""
|
||||
Ontology ingestion handler.
|
||||
|
||||
This class parses OWL/RDF files and converts them to Semantica's ontology dictionary format.
|
||||
"""
|
||||
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||
"""
|
||||
Initialize ontology ingestor.
|
||||
|
||||
Args:
|
||||
config: Optional configuration dictionary
|
||||
**kwargs: Additional configuration parameters
|
||||
"""
|
||||
self.logger = get_logger("ontology_ingestor")
|
||||
self.progress = get_progress_tracker()
|
||||
self.config = config or {}
|
||||
self.config.update(kwargs)
|
||||
|
||||
def ingest_ontology(self, file_path: Union[str, Path], format: Optional[str] = None, **kwargs) -> OntologyData:
|
||||
"""
|
||||
Ingest an ontology file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the ontology file (string or Path object)
|
||||
format: Optional format hint (e.g., 'turtle', 'xml'). If None, rdflib guesses.
|
||||
**kwargs: Additional arguments for rdflib parsing
|
||||
|
||||
Returns:
|
||||
OntologyData object containing the parsed ontology and metadata
|
||||
"""
|
||||
file_path = Path(file_path)
|
||||
|
||||
# Track file ingestion
|
||||
tracking_id = self.progress.start_tracking(
|
||||
file=str(file_path),
|
||||
module="ingest",
|
||||
submodule="OntologyIngestor",
|
||||
message=f"Ontology: {file_path.name}",
|
||||
)
|
||||
|
||||
try:
|
||||
# Validate file exists
|
||||
if not file_path.exists():
|
||||
raise ValidationError(f"File not found: {file_path}")
|
||||
|
||||
self.progress.update_tracking(tracking_id, message="Parsing RDF graph...")
|
||||
g = Graph()
|
||||
|
||||
# Use provided format or let rdflib guess based on extension
|
||||
parse_kwargs = kwargs.copy()
|
||||
if format:
|
||||
parse_kwargs['format'] = format
|
||||
|
||||
try:
|
||||
g.parse(file_path, **parse_kwargs)
|
||||
except Exception as e:
|
||||
# Fallback: try to guess format from extension if not provided and initial parse failed
|
||||
if not format:
|
||||
ext = os.path.splitext(file_path)[1].lower()
|
||||
fmt_map = {
|
||||
'.ttl': 'turtle',
|
||||
'.owl': 'xml', # OWL is often XML
|
||||
'.rdf': 'xml',
|
||||
'.jsonld': 'json-ld',
|
||||
'.n3': 'n3',
|
||||
'.nt': 'nt'
|
||||
}
|
||||
guessed_fmt = fmt_map.get(ext)
|
||||
if guessed_fmt:
|
||||
self.logger.info(f"Retrying with guessed format: {guessed_fmt}")
|
||||
g.parse(file_path, format=guessed_fmt, **kwargs)
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
raise e
|
||||
|
||||
self.progress.update_tracking(tracking_id, message="Converting to internal format...")
|
||||
|
||||
# Determine format for metadata
|
||||
used_format = format
|
||||
if not used_format:
|
||||
ext = os.path.splitext(file_path)[1].lower()
|
||||
fmt_map = {
|
||||
'.ttl': 'turtle',
|
||||
'.owl': 'xml',
|
||||
'.rdf': 'xml',
|
||||
'.jsonld': 'json-ld',
|
||||
'.n3': 'n3',
|
||||
'.nt': 'nt'
|
||||
}
|
||||
used_format = fmt_map.get(ext, 'unknown')
|
||||
|
||||
ontology_dict = self._convert_to_dict(g, source_path=str(file_path), format=used_format)
|
||||
|
||||
ontology_data = OntologyData(
|
||||
data=ontology_dict,
|
||||
source_path=str(file_path),
|
||||
format=used_format,
|
||||
metadata=ontology_dict.get("metadata", {}).copy()
|
||||
)
|
||||
|
||||
self.progress.stop_tracking(
|
||||
tracking_id,
|
||||
status="completed",
|
||||
message=f"Successfully ingested ontology from {file_path}",
|
||||
)
|
||||
|
||||
return ontology_data
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to ingest ontology: {str(e)}")
|
||||
self.progress.stop_tracking(
|
||||
tracking_id, status="failed", message=str(e)
|
||||
)
|
||||
raise ProcessingError(f"Failed to ingest ontology: {str(e)}") from e
|
||||
|
||||
def ingest_directory(self, directory_path: Union[str, Path], recursive: bool = True, **kwargs) -> List[OntologyData]:
|
||||
"""
|
||||
Ingest all ontology files in a directory.
|
||||
|
||||
Args:
|
||||
directory_path: Path to the directory (string or Path object)
|
||||
recursive: Whether to search recursively
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
List of OntologyData objects
|
||||
"""
|
||||
directory_path = Path(directory_path)
|
||||
ontologies = []
|
||||
extensions = {'.ttl', '.owl', '.rdf', '.jsonld', '.n3', '.nt'}
|
||||
|
||||
# Track directory ingestion
|
||||
tracking_id = self.progress.start_tracking(
|
||||
file=str(directory_path),
|
||||
module="ingest",
|
||||
submodule="OntologyIngestor",
|
||||
message=f"Directory: {directory_path.name}",
|
||||
)
|
||||
|
||||
try:
|
||||
if not directory_path.exists():
|
||||
raise ValidationError(f"Directory not found: {directory_path}")
|
||||
|
||||
if not directory_path.is_dir():
|
||||
raise ValidationError(f"Path is not a directory: {directory_path}")
|
||||
|
||||
files_to_process = []
|
||||
for root, _, files in os.walk(directory_path):
|
||||
for file in files:
|
||||
ext = os.path.splitext(file)[1].lower()
|
||||
if ext in extensions:
|
||||
files_to_process.append(os.path.join(root, file))
|
||||
|
||||
if not recursive:
|
||||
break
|
||||
|
||||
total_files = len(files_to_process)
|
||||
self.progress.update_tracking(
|
||||
tracking_id, message=f"Processing {total_files} ontology files"
|
||||
)
|
||||
|
||||
for idx, file_path in enumerate(files_to_process, 1):
|
||||
try:
|
||||
ont_data = self.ingest_ontology(file_path, **kwargs)
|
||||
ontologies.append(ont_data)
|
||||
|
||||
self.progress.update_progress(
|
||||
tracking_id,
|
||||
processed=idx,
|
||||
total=total_files,
|
||||
message=f"Processing {idx}/{total_files}: {Path(file_path).name}"
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Skipping {file_path}: {e}")
|
||||
|
||||
self.progress.stop_tracking(
|
||||
tracking_id,
|
||||
status="completed",
|
||||
message=f"Ingested {len(ontologies)} ontologies",
|
||||
)
|
||||
return ontologies
|
||||
|
||||
except Exception as e:
|
||||
self.progress.stop_tracking(
|
||||
tracking_id, status="failed", message=str(e)
|
||||
)
|
||||
raise
|
||||
|
||||
def _convert_to_dict(self, graph: Graph, source_path: str, format: str = "unknown") -> Dict[str, Any]:
|
||||
"""
|
||||
Convert rdflib Graph to Semantica ontology dictionary.
|
||||
|
||||
Args:
|
||||
graph: Parsed rdflib Graph
|
||||
source_path: Source file path
|
||||
format: Format of the ontology file
|
||||
|
||||
Returns:
|
||||
Ontology dictionary
|
||||
"""
|
||||
ontology = {
|
||||
"uri": "",
|
||||
"name": os.path.basename(source_path),
|
||||
"version": "1.0",
|
||||
"classes": [],
|
||||
"properties": [],
|
||||
"metadata": {
|
||||
"source_path": source_path,
|
||||
"ingested_at": datetime.now().isoformat(),
|
||||
"format": format
|
||||
}
|
||||
}
|
||||
|
||||
# 1. Extract Ontology Metadata
|
||||
for s, p, o in graph.triples((None, RDF.type, OWL.Ontology)):
|
||||
ontology["uri"] = str(s)
|
||||
|
||||
# Try to find label/comment/versionInfo
|
||||
for _, _, label in graph.triples((s, RDFS.label, None)):
|
||||
ontology["name"] = str(label)
|
||||
|
||||
for _, _, comment in graph.triples((s, RDFS.comment, None)):
|
||||
ontology["description"] = str(comment)
|
||||
|
||||
for _, _, version in graph.triples((s, OWL.versionInfo, None)):
|
||||
ontology["version"] = str(version)
|
||||
|
||||
# Break after first ontology definition found (usually only one per file)
|
||||
break
|
||||
|
||||
# 2. Extract Classes
|
||||
classes = {}
|
||||
# Union of owl:Class and rdfs:Class
|
||||
class_types = [OWL.Class, RDFS.Class]
|
||||
for c_type in class_types:
|
||||
for s, p, o in graph.triples((None, RDF.type, c_type)):
|
||||
if isinstance(s, rdflib.BNode):
|
||||
continue # Skip blank nodes for now
|
||||
|
||||
uri = str(s)
|
||||
if uri not in classes:
|
||||
cls_def = {
|
||||
"uri": uri,
|
||||
"name": self._get_local_name(uri),
|
||||
"type": "class"
|
||||
}
|
||||
|
||||
# Add label/comment
|
||||
label = graph.value(s, RDFS.label)
|
||||
if label:
|
||||
cls_def["label"] = str(label)
|
||||
cls_def["name"] = str(label) # Prefer label as name if available? Or keep URI fragment?
|
||||
# Keeping local name from URI is safer for internal IDs, label for display.
|
||||
# But Semantica seems to use "name" for the identifier in some examples.
|
||||
# Let's keep name as local name or label if simple.
|
||||
|
||||
comment = graph.value(s, RDFS.comment)
|
||||
if comment:
|
||||
cls_def["description"] = str(comment)
|
||||
|
||||
# Superclasses
|
||||
parents = []
|
||||
for _, _, parent in graph.triples((s, RDFS.subClassOf, None)):
|
||||
if isinstance(parent, rdflib.URIRef):
|
||||
parents.append(str(parent))
|
||||
if parents:
|
||||
cls_def["parents"] = parents
|
||||
|
||||
classes[uri] = cls_def
|
||||
|
||||
ontology["classes"] = list(classes.values())
|
||||
|
||||
# 3. Extract Properties
|
||||
properties = {}
|
||||
# Object Properties
|
||||
for s, p, o in graph.triples((None, RDF.type, OWL.ObjectProperty)):
|
||||
self._add_property(graph, s, "object", properties)
|
||||
|
||||
# Datatype Properties
|
||||
for s, p, o in graph.triples((None, RDF.type, OWL.DatatypeProperty)):
|
||||
self._add_property(graph, s, "data", properties)
|
||||
|
||||
# RDF Properties (generic)
|
||||
for s, p, o in graph.triples((None, RDF.type, RDF.Property)):
|
||||
if str(s) not in properties: # Don't overwrite if already found as specific type
|
||||
self._add_property(graph, s, "annotation", properties) # Default to annotation or generic
|
||||
|
||||
ontology["properties"] = list(properties.values())
|
||||
|
||||
return ontology
|
||||
|
||||
def _add_property(self, graph: Graph, subject: rdflib.term.Node, prop_type: str, properties_dict: Dict):
|
||||
if isinstance(subject, rdflib.BNode):
|
||||
return
|
||||
|
||||
uri = str(subject)
|
||||
if uri in properties_dict:
|
||||
return
|
||||
|
||||
prop_def = {
|
||||
"uri": uri,
|
||||
"name": self._get_local_name(uri),
|
||||
"type": prop_type
|
||||
}
|
||||
|
||||
label = graph.value(subject, RDFS.label)
|
||||
if label:
|
||||
prop_def["label"] = str(label)
|
||||
|
||||
comment = graph.value(subject, RDFS.comment)
|
||||
if comment:
|
||||
prop_def["description"] = str(comment)
|
||||
|
||||
# Domain and Range
|
||||
domain = graph.value(subject, RDFS.domain)
|
||||
if domain and isinstance(domain, rdflib.URIRef):
|
||||
prop_def["domain"] = str(domain)
|
||||
|
||||
range_val = graph.value(subject, RDFS.range)
|
||||
if range_val and isinstance(range_val, rdflib.URIRef):
|
||||
prop_def["range"] = str(range_val)
|
||||
|
||||
properties_dict[uri] = prop_def
|
||||
|
||||
def _get_local_name(self, uri: str) -> str:
|
||||
"""Extract local name from URI."""
|
||||
if '#' in uri:
|
||||
return uri.split('#')[-1]
|
||||
return uri.split('/')[-1]
|
||||
@@ -109,12 +109,14 @@ Convenience Functions:
|
||||
- create_associative_class: Associative class creation wrapper
|
||||
- get_ontology_method: Get ontology method by name
|
||||
- list_available_methods: List registered methods
|
||||
- ingest_ontology: Ingest ontology from file or directory
|
||||
|
||||
Example Usage:
|
||||
>>> from semantica.ontology import generate_ontology, infer_classes, OntologyGenerator
|
||||
>>> from semantica.ontology import generate_ontology, infer_classes, OntologyGenerator, ingest_ontology
|
||||
>>> # Using convenience functions
|
||||
>>> ontology = generate_ontology({"entities": [...], "relationships": [...]}, method="default")
|
||||
>>> classes = infer_classes(entities, method="default")
|
||||
>>> data = ingest_ontology("ontology.ttl")
|
||||
>>> # Using classes directly
|
||||
>>> from semantica.ontology import OntologyGenerator, ClassInferrer, PropertyGenerator
|
||||
>>> generator = OntologyGenerator(base_uri="https://example.org/ontology/")
|
||||
@@ -155,6 +157,8 @@ from .registry import MethodRegistry, method_registry
|
||||
from .requirements_spec import RequirementsSpec, RequirementsSpecManager
|
||||
from .reuse_manager import ReuseDecision, ReuseManager
|
||||
from .version_manager import OntologyVersion, VersionManager
|
||||
from semantica.ingest import OntologyData, OntologyIngestor
|
||||
from .methods import ingest_ontology
|
||||
|
||||
__all__ = [
|
||||
# Main generators
|
||||
@@ -200,4 +204,7 @@ __all__ = [
|
||||
# Configuration
|
||||
"OntologyConfig",
|
||||
"ontology_config",
|
||||
"ingest_ontology",
|
||||
"OntologyData",
|
||||
"OntologyIngestor",
|
||||
]
|
||||
|
||||
@@ -111,15 +111,19 @@ Main Functions:
|
||||
- create_associative_class: Associative class creation wrapper
|
||||
- get_ontology_method: Get ontology method by name
|
||||
- list_available_methods: List registered methods
|
||||
- ingest_ontology: Ingest ontology from file or directory (via semantica.ingest)
|
||||
|
||||
Example Usage:
|
||||
>>> from semantica.ontology.methods import generate_ontology, infer_classes
|
||||
>>> from semantica.ontology.methods import generate_ontology, infer_classes, ingest_ontology
|
||||
>>> ontology = generate_ontology({"entities": [...], "relationships": [...]}, method="default")
|
||||
>>> classes = infer_classes(entities, method="default")
|
||||
>>> data = ingest_ontology("ontology.ttl")
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
from typing import Any, Callable, Dict, List, Optional, Union
|
||||
from pathlib import Path
|
||||
|
||||
from semantica.ingest import ingest_ontology as _ingest_ontology, OntologyData
|
||||
from .registry import method_registry
|
||||
|
||||
|
||||
@@ -172,4 +176,23 @@ def list_available_methods(task: Optional[str] = None) -> Dict[str, List[str]]:
|
||||
return method_registry.list_all(task)
|
||||
|
||||
|
||||
pass
|
||||
def ingest_ontology(
|
||||
source: Union[str, Path, List[Union[str, Path]]],
|
||||
method: str = "file",
|
||||
**kwargs
|
||||
) -> Union[OntologyData, List[OntologyData]]:
|
||||
"""
|
||||
Ingest ontology from source.
|
||||
|
||||
This is a convenience wrapper around semantica.ingest.ingest_ontology.
|
||||
|
||||
Args:
|
||||
source: Ontology file path, directory path, or list of paths
|
||||
method: Ingestion method (default: "file")
|
||||
**kwargs: Additional options
|
||||
|
||||
Returns:
|
||||
OntologyData or List[OntologyData]
|
||||
"""
|
||||
return _ingest_ontology(source, method=method, **kwargs)
|
||||
|
||||
|
||||
@@ -42,6 +42,18 @@ classes = inferrer.infer_classes(entities, build_hierarchy=True)
|
||||
properties = prop_gen.infer_properties(entities, relationships, classes)
|
||||
```
|
||||
|
||||
### Ingesting Ontologies
|
||||
|
||||
```python
|
||||
from semantica.ingest import OntologyIngestor
|
||||
|
||||
# Create ingestor
|
||||
ingestor = OntologyIngestor()
|
||||
|
||||
# Ingest ontology
|
||||
ontology_data = ingestor.ingest_ontology("ontology.ttl")
|
||||
```
|
||||
|
||||
## Ontology Generation
|
||||
|
||||
### Basic Ontology Generation
|
||||
@@ -115,6 +127,49 @@ ontology = engine.from_data(
|
||||
)
|
||||
```
|
||||
|
||||
## Ontology Ingestion
|
||||
|
||||
### Basic Ingestion
|
||||
|
||||
Ingest existing ontologies from files (Turtle, RDF/XML, JSON-LD, etc.) into `OntologyData` objects.
|
||||
|
||||
```python
|
||||
from semantica.ontology import ingest_ontology
|
||||
|
||||
# Ingest a single file
|
||||
ontology_data = ingest_ontology("path/to/ontology.ttl")
|
||||
|
||||
print(f"Source: {ontology_data.source_path}")
|
||||
print(f"Format: {ontology_data.format}")
|
||||
print(f"Data keys: {ontology_data.data.keys()}")
|
||||
```
|
||||
|
||||
### Ingesting Directories
|
||||
|
||||
Ingest all ontology files in a directory recursively.
|
||||
|
||||
```python
|
||||
from semantica.ontology import ingest_ontology
|
||||
|
||||
# Ingest a directory
|
||||
ontologies = ingest_ontology("path/to/ontologies_dir/")
|
||||
|
||||
for ont in ontologies:
|
||||
print(f"Ingested: {ont.source_path} ({ont.format})")
|
||||
```
|
||||
|
||||
### Unified Ingestion Interface
|
||||
|
||||
You can also use the unified `semantica.ingest` interface.
|
||||
|
||||
```python
|
||||
from semantica.ingest import ingest
|
||||
|
||||
# Ingest as "ontology" source type
|
||||
result = ingest("path/to/ontology.ttl", source_type="ontology")
|
||||
ontology_data = result["ontology"]
|
||||
```
|
||||
|
||||
## Class Inference
|
||||
|
||||
### Basic Class Inference
|
||||
|
||||
@@ -363,3 +363,90 @@ class ReuseManager:
|
||||
def list_known_ontologies(self) -> List[str]:
|
||||
"""List known ontology URIs."""
|
||||
return list(self.known_ontologies.keys())
|
||||
|
||||
def merge_ontology_data(
|
||||
self, target: Dict[str, Any], source: Dict[str, Any], **options
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Merge source ontology data into target ontology.
|
||||
|
||||
Merges classes, properties, and metadata from source to target.
|
||||
Handles deduplication based on URI and name.
|
||||
|
||||
Args:
|
||||
target: Target ontology dictionary (modified in-place)
|
||||
source: Source ontology dictionary
|
||||
**options: Merge options:
|
||||
- overwrite: Whether to overwrite existing elements (default: False)
|
||||
- merge_metadata: Whether to merge metadata (default: True)
|
||||
|
||||
Returns:
|
||||
Merged target ontology
|
||||
"""
|
||||
tracking_id = self.progress_tracker.start_tracking(
|
||||
module="ontology",
|
||||
submodule="ReuseManager",
|
||||
message=f"Merging ontology {source.get('name', 'unknown')} into {target.get('name', 'unknown')}",
|
||||
)
|
||||
|
||||
try:
|
||||
overwrite = options.get("overwrite", False)
|
||||
|
||||
# Helper to merge lists of dicts (classes/properties)
|
||||
def merge_lists(target_list, source_list, key_field="uri"):
|
||||
existing_keys = {item.get(key_field): i for i, item in enumerate(target_list) if item.get(key_field)}
|
||||
|
||||
for item in source_list:
|
||||
key = item.get(key_field)
|
||||
if not key:
|
||||
# Fallback to name if URI missing
|
||||
key = item.get("name")
|
||||
|
||||
if key in existing_keys:
|
||||
if overwrite:
|
||||
target_list[existing_keys[key]] = item
|
||||
else:
|
||||
target_list.append(item)
|
||||
if key:
|
||||
existing_keys[key] = len(target_list) - 1
|
||||
|
||||
# Merge Classes
|
||||
if "classes" in source:
|
||||
if "classes" not in target:
|
||||
target["classes"] = []
|
||||
merge_lists(target["classes"], source["classes"])
|
||||
|
||||
# Merge Properties
|
||||
if "properties" in source:
|
||||
if "properties" not in target:
|
||||
target["properties"] = []
|
||||
merge_lists(target["properties"], source["properties"])
|
||||
|
||||
# Merge Metadata
|
||||
if options.get("merge_metadata", True) and "metadata" in source:
|
||||
if "metadata" not in target:
|
||||
target["metadata"] = {}
|
||||
# Update with source metadata, preserving target's specific fields if needed
|
||||
# Here we just update
|
||||
target["metadata"].update(source["metadata"])
|
||||
|
||||
# Merge Imports
|
||||
if "imports" in source:
|
||||
if "imports" not in target:
|
||||
target["imports"] = []
|
||||
for imp in source["imports"]:
|
||||
if imp not in target["imports"]:
|
||||
target["imports"].append(imp)
|
||||
|
||||
self.progress_tracker.stop_tracking(
|
||||
tracking_id,
|
||||
status="completed",
|
||||
message=f"Merged ontology data successfully",
|
||||
)
|
||||
return target
|
||||
|
||||
except Exception as e:
|
||||
self.progress_tracker.stop_tracking(
|
||||
tracking_id, status="failed", message=str(e)
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from semantica.ingest import OntologyIngestor, ingest, ingest_ontology, OntologyData
|
||||
|
||||
class TestOntologyIngestor:
|
||||
@pytest.fixture
|
||||
def sample_ttl_content(self):
|
||||
return """
|
||||
@prefix : <http://example.org/ontology/> .
|
||||
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
||||
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
||||
|
||||
<http://example.org/ontology/> rdf:type owl:Ontology ;
|
||||
rdfs:label "Test Ontology" .
|
||||
|
||||
:Person rdf:type owl:Class ;
|
||||
rdfs:label "Person" .
|
||||
|
||||
:hasName rdf:type owl:DatatypeProperty ;
|
||||
rdfs:domain :Person ;
|
||||
rdfs:range xsd:string .
|
||||
"""
|
||||
|
||||
def test_ingest_single_file(self, sample_ttl_content):
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".ttl", mode="w") as tmp:
|
||||
tmp.write(sample_ttl_content)
|
||||
tmp_path = tmp.name
|
||||
|
||||
try:
|
||||
ingestor = OntologyIngestor()
|
||||
result = ingestor.ingest_ontology(tmp_path)
|
||||
|
||||
assert isinstance(result, OntologyData)
|
||||
assert result.data["name"] == "Test Ontology" or result.data["name"] == os.path.basename(tmp_path)
|
||||
assert any(cls["name"] == "Person" for cls in result.data["classes"])
|
||||
assert any(prop["name"] == "hasName" for prop in result.data["properties"])
|
||||
assert result.metadata["format"] == "ttl" or result.metadata["format"] == "turtle"
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.remove(tmp_path)
|
||||
|
||||
def test_ingest_directory(self, sample_ttl_content):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
# Create two ontology files
|
||||
file1 = os.path.join(tmp_dir, "ont1.ttl")
|
||||
file2 = os.path.join(tmp_dir, "ont2.rdf")
|
||||
|
||||
with open(file1, "w") as f:
|
||||
f.write(sample_ttl_content)
|
||||
|
||||
# Simple RDF/XML content for the second file
|
||||
rdf_content = """
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:owl="http://www.w3.org/2002/07/owl#">
|
||||
<owl:Ontology rdf:about="http://example.org/ont2"/>
|
||||
<owl:Class rdf:about="http://example.org/ont2/Animal"/>
|
||||
</rdf:RDF>
|
||||
"""
|
||||
with open(file2, "w") as f:
|
||||
f.write(rdf_content)
|
||||
|
||||
ingestor = OntologyIngestor()
|
||||
results = ingestor.ingest_directory(tmp_dir)
|
||||
|
||||
assert len(results) == 2
|
||||
assert all(isinstance(r, OntologyData) for r in results)
|
||||
|
||||
# Verify results contain expected classes
|
||||
classes = [cls["name"] for res in results for cls in res.data["classes"]]
|
||||
assert "Person" in classes
|
||||
assert "Animal" in classes
|
||||
|
||||
def test_unified_ingest_function(self, sample_ttl_content):
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".ttl", mode="w") as tmp:
|
||||
tmp.write(sample_ttl_content)
|
||||
tmp_path = tmp.name
|
||||
|
||||
try:
|
||||
# Test auto-detection via unified ingest
|
||||
result = ingest(tmp_path)
|
||||
assert "ontology" in result
|
||||
assert isinstance(result["ontology"], OntologyData)
|
||||
assert len(result["ontology"].data["classes"]) > 0
|
||||
|
||||
# Test explicit source type
|
||||
result_explicit = ingest(tmp_path, source_type="ontology")
|
||||
assert "ontology" in result_explicit
|
||||
assert result_explicit["ontology"].metadata["source_path"] == tmp_path
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.remove(tmp_path)
|
||||
|
||||
def test_convenience_function(self, sample_ttl_content):
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".n3", mode="w") as tmp:
|
||||
tmp.write(sample_ttl_content)
|
||||
tmp_path = tmp.name
|
||||
|
||||
try:
|
||||
result = ingest_ontology(tmp_path)
|
||||
assert isinstance(result, OntologyData)
|
||||
assert len(result.data["classes"]) > 0
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.remove(tmp_path)
|
||||
|
||||
def test_ingest_formats(self):
|
||||
"""Test ingestion of all supported formats."""
|
||||
ingestor = OntologyIngestor()
|
||||
|
||||
# 1. JSON-LD
|
||||
jsonld_content = """
|
||||
{
|
||||
"@context": {
|
||||
"owl": "http://www.w3.org/2002/07/owl#",
|
||||
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
||||
"rdfs": "http://www.w3.org/2000/01/rdf-schema#"
|
||||
},
|
||||
"@id": "http://example.org/jsonld",
|
||||
"@type": "owl:Ontology",
|
||||
"rdfs:label": "JSON-LD Ontology",
|
||||
"owl:versionInfo": "1.0"
|
||||
}
|
||||
"""
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".jsonld", mode="w") as tmp:
|
||||
tmp.write(jsonld_content)
|
||||
tmp_path = tmp.name
|
||||
try:
|
||||
result = ingestor.ingest_ontology(tmp_path)
|
||||
assert result.data["name"] == "JSON-LD Ontology"
|
||||
assert result.metadata["format"] == "json-ld"
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.remove(tmp_path)
|
||||
|
||||
# 2. N-Triples
|
||||
nt_content = '<http://example.org/nt/Class> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#Class> .\n'
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".nt", mode="w") as tmp:
|
||||
tmp.write(nt_content)
|
||||
tmp_path = tmp.name
|
||||
try:
|
||||
result = ingestor.ingest_ontology(tmp_path)
|
||||
# N-Triples often doesn't have ontology metadata, so name might default to basename
|
||||
assert result.data["name"] == os.path.basename(tmp_path)
|
||||
assert any(cls["uri"] == "http://example.org/nt/Class" for cls in result.data["classes"])
|
||||
assert result.metadata["format"] == "nt"
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.remove(tmp_path)
|
||||
|
||||
# 3. Notation3
|
||||
n3_content = """
|
||||
@prefix : <http://example.org/n3/> .
|
||||
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
||||
:N3Class a owl:Class .
|
||||
"""
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".n3", mode="w") as tmp:
|
||||
tmp.write(n3_content)
|
||||
tmp_path = tmp.name
|
||||
try:
|
||||
result = ingestor.ingest_ontology(tmp_path)
|
||||
assert any(cls["uri"] == "http://example.org/n3/N3Class" for cls in result.data["classes"])
|
||||
# format might be 'n3' or 'turtle' depending on rdflib detection as they are similar
|
||||
assert result.metadata["format"] in ["n3", "turtle"]
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.remove(tmp_path)
|
||||
|
||||
# 4. RDF/XML (.owl)
|
||||
owl_content = """
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:owl="http://www.w3.org/2002/07/owl#"
|
||||
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
|
||||
<owl:Ontology rdf:about="http://example.org/owl"/>
|
||||
<owl:Class rdf:about="http://example.org/owl/OwlClass">
|
||||
<rdfs:label>OwlClass</rdfs:label>
|
||||
</owl:Class>
|
||||
</rdf:RDF>
|
||||
"""
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".owl", mode="w") as tmp:
|
||||
tmp.write(owl_content)
|
||||
tmp_path = tmp.name
|
||||
try:
|
||||
result = ingestor.ingest_ontology(tmp_path)
|
||||
assert any(cls["name"] == "OwlClass" for cls in result.data["classes"])
|
||||
assert result.metadata["format"] in ["xml", "rdf", "owl"]
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.remove(tmp_path)
|
||||
|
||||
def test_error_handling(self):
|
||||
ingestor = OntologyIngestor()
|
||||
with pytest.raises(Exception): # Specific exception type depends on implementation, likely ValidationError or FileNotFoundError
|
||||
ingestor.ingest_ontology("non_existent_file.ttl")
|
||||
|
||||
def test_invalid_content(self):
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".ttl", mode="w") as tmp:
|
||||
tmp.write("This is not valid turtle content")
|
||||
tmp_path = tmp.name
|
||||
|
||||
try:
|
||||
ingestor = OntologyIngestor()
|
||||
# Depending on implementation, this might raise an exception or return partial/empty result with error in metadata
|
||||
# Given current implementation uses g.parse(), it likely raises an exception which is caught or propagated
|
||||
# If propagated:
|
||||
with pytest.raises(Exception):
|
||||
ingestor.ingest_ontology(tmp_path)
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.remove(tmp_path)
|
||||
Reference in New Issue
Block a user