Skip to content

Commit da6920c

Browse files
fix: Align Python API add/add_no_imports overwrite and expose ontology metadata
Co-authored-by: aider (gemini/gemini-2.5-pro) <[email protected]>
1 parent 946dacc commit da6920c

File tree

1 file changed

+93
-7
lines changed

1 file changed

+93
-7
lines changed

python/src/lib.rs

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use ::ontoenv::api::{OntoEnv as OntoEnvRs, ResolveTarget};
22
use ::ontoenv::config;
33
use ::ontoenv::consts::{IMPORTS, ONTOLOGY, TYPE};
44
use ::ontoenv::ToUriString;
5-
use ::ontoenv::ontology::{OntologyLocation, GraphIdentifier};
5+
use ::ontoenv::ontology::{Ontology as OntologyRs, OntologyLocation, GraphIdentifier};
66
use ::ontoenv::transform;
77
use anyhow::Error;
88
use oxigraph::model::{BlankNode, Literal, NamedNode, SubjectRef, Term};
@@ -11,7 +11,7 @@ use pyo3::{
1111
types::{IntoPyDict, PyString, PyTuple},
1212
};
1313
use std::borrow::Borrow;
14-
use std::collections::HashSet;
14+
use std::collections::{HashMap, HashSet};
1515
use std::path::PathBuf;
1616
use std::sync::{Arc, Mutex, Once};
1717

@@ -171,6 +171,67 @@ impl Config {
171171
}
172172

173173

174+
#[pyclass(name = "Ontology")]
175+
#[derive(Clone)]
176+
struct PyOntology {
177+
inner: OntologyRs,
178+
}
179+
180+
#[pymethods]
181+
impl PyOntology {
182+
#[getter]
183+
fn id(&self) -> PyResult<String> {
184+
Ok(self.inner.id().to_uri_string())
185+
}
186+
187+
#[getter]
188+
fn name(&self) -> PyResult<String> {
189+
Ok(self.inner.name().to_uri_string())
190+
}
191+
192+
#[getter]
193+
fn imports(&self) -> PyResult<Vec<String>> {
194+
Ok(self
195+
.inner
196+
.imports
197+
.iter()
198+
.map(|i| i.to_uri_string())
199+
.collect())
200+
}
201+
202+
#[getter]
203+
fn location(&self) -> PyResult<Option<String>> {
204+
Ok(self.inner.location().map(|l| l.to_string()))
205+
}
206+
207+
#[getter]
208+
fn last_updated(&self) -> PyResult<Option<String>> {
209+
Ok(self.inner.last_updated.map(|dt| dt.to_rfc3339()))
210+
}
211+
212+
#[getter]
213+
fn version_properties(&self) -> PyResult<HashMap<String, String>> {
214+
Ok(self
215+
.inner
216+
.version_properties()
217+
.iter()
218+
.map(|(k, v)| (k.to_uri_string(), v.clone()))
219+
.collect())
220+
}
221+
222+
#[getter]
223+
fn namespace_map(&self) -> PyResult<HashMap<String, String>> {
224+
Ok(self.inner.namespace_map().clone())
225+
}
226+
227+
fn __repr__(&self) -> PyResult<String> {
228+
Ok(format!(
229+
"<Ontology: {}>",
230+
self.inner.name().to_uri_string()
231+
))
232+
}
233+
}
234+
174235
#[pyclass]
175236
struct OntoEnv {
176237
inner: Arc<Mutex<Option<OntoEnvRs>>>,
@@ -544,7 +605,8 @@ impl OntoEnv {
544605
}
545606

546607
/// Add a new ontology to the OntoEnv
547-
fn add(&self, location: &Bound<'_, PyAny>) -> PyResult<String> {
608+
#[pyo3(signature = (location, overwrite = false))]
609+
fn add(&self, location: &Bound<'_, PyAny>, overwrite: bool) -> PyResult<String> {
548610
let inner = self.inner.clone();
549611
let mut guard = inner.lock().unwrap();
550612
let env = guard.as_mut().ok_or_else(|| {
@@ -570,29 +632,32 @@ impl OntoEnv {
570632
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
571633

572634
let location_to_add = OntologyLocation::File(path.clone());
573-
let result = env.add(location_to_add, true);
635+
let result = env.add(location_to_add, overwrite);
574636
let _ = std::fs::remove_file(&path);
575637
result
576638
.map(|id| id.to_uri_string())
577639
.map_err(anyhow_to_pyerr)
578640
} else {
579641
let location =
580642
OntologyLocation::from_str(&location.to_string()).map_err(anyhow_to_pyerr)?;
581-
let graph_id = env.add(location, true).map_err(anyhow_to_pyerr)?;
643+
let graph_id = env.add(location, overwrite).map_err(anyhow_to_pyerr)?;
582644
Ok(graph_id.to_uri_string())
583645
}
584646
}
585647

586648
/// Add a new ontology to the OntoEnv without exploring owl:imports.
587-
fn add_no_imports(&self, location: &Bound<'_, PyAny>) -> PyResult<String> {
649+
#[pyo3(signature = (location, overwrite = false))]
650+
fn add_no_imports(&self, location: &Bound<'_, PyAny>, overwrite: bool) -> PyResult<String> {
588651
let inner = self.inner.clone();
589652
let mut guard = inner.lock().unwrap();
590653
let env = guard.as_mut().ok_or_else(|| {
591654
PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
592655
})?;
593656
let location =
594657
OntologyLocation::from_str(&location.to_string()).map_err(anyhow_to_pyerr)?;
595-
let graph_id = env.add_no_imports(location, true).map_err(anyhow_to_pyerr)?;
658+
let graph_id = env
659+
.add_no_imports(location, overwrite)
660+
.map_err(anyhow_to_pyerr)?;
596661
Ok(graph_id.to_uri_string())
597662
}
598663

@@ -611,6 +676,26 @@ impl OntoEnv {
611676
Ok(names)
612677
}
613678

679+
/// Get the ontology metadata with the given URI
680+
fn get_ontology(&self, uri: &str) -> PyResult<PyOntology> {
681+
let iri = NamedNode::new(uri)
682+
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
683+
let inner = self.inner.clone();
684+
let guard = inner.lock().unwrap();
685+
let env = guard.as_ref().ok_or_else(|| {
686+
PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
687+
})?;
688+
let graphid = env
689+
.resolve(ResolveTarget::Graph(iri.clone()))
690+
.ok_or_else(|| {
691+
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
692+
"Failed to resolve graph for URI: {uri}"
693+
))
694+
})?;
695+
let ont = env.get_ontology(&graphid).map_err(anyhow_to_pyerr)?;
696+
Ok(PyOntology { inner: ont })
697+
}
698+
614699
/// Get the graph with the given URI as an rdflib.Graph
615700
fn get_graph(&self, py: Python, uri: &Bound<'_, PyString>) -> PyResult<Py<PyAny>> {
616701
let rdflib = py.import("rdflib")?;
@@ -861,6 +946,7 @@ impl OntoEnv {
861946
fn ontoenv(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
862947
m.add_class::<Config>()?;
863948
m.add_class::<OntoEnv>()?;
949+
m.add_class::<PyOntology>()?;
864950
// add version attribute
865951
m.add("version", env!("CARGO_PKG_VERSION"))?;
866952
Ok(())

0 commit comments

Comments
 (0)