1use ::ontoenv::api::{OntoEnv as OntoEnvRs, ResolveTarget};
2use ::ontoenv::config;
3use ::ontoenv::consts::{IMPORTS, ONTOLOGY, TYPE};
4use ::ontoenv::ontology::{Ontology as OntologyRs, OntologyLocation};
5use ::ontoenv::options::{CacheMode, Overwrite, RefreshStrategy};
6use ::ontoenv::transform;
7use ::ontoenv::ToUriString;
8use anyhow::Error;
9#[cfg(feature = "cli")]
10use ontoenv_cli;
11use oxigraph::model::{BlankNode, Literal, NamedNode, NamedOrBlankNodeRef, Term};
12use pyo3::{
13 prelude::*,
14 types::{IntoPyDict, PyString, PyTuple},
15 exceptions::PyValueError,
16};
17#[cfg(not(feature = "cli"))]
18use pyo3::exceptions::PyRuntimeError;
19use std::borrow::Borrow;
20use std::collections::{HashMap, HashSet};
21use std::path::PathBuf;
22use std::ffi::OsStr;
23use std::sync::{Arc, Mutex};
24
25fn anyhow_to_pyerr(e: Error) -> PyErr {
26 PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string())
27}
28
29fn format_path_for_error(path: &std::path::Path) -> String {
31 path.to_string_lossy().replace('\\', "/")
32}
33
34#[allow(dead_code)]
35struct MyTerm(Term);
36impl From<Result<Bound<'_, PyAny>, pyo3::PyErr>> for MyTerm {
37 fn from(s: Result<Bound<'_, PyAny>, pyo3::PyErr>) -> Self {
38 let s = s.unwrap();
39 let typestr = s.get_type().name().unwrap();
40 let typestr = typestr.to_string();
41 let data_type: Option<NamedNode> = match s.getattr("datatype") {
42 Ok(dt) => {
43 if dt.is_none() {
44 None
45 } else {
46 Some(NamedNode::new(dt.to_string()).unwrap())
47 }
48 }
49 Err(_) => None,
50 };
51 let lang: Option<String> = match s.getattr("language") {
52 Ok(l) => {
53 if l.is_none() {
54 None
55 } else {
56 Some(l.to_string())
57 }
58 }
59 Err(_) => None,
60 };
61 let n: Term = match typestr.borrow() {
62 "URIRef" => Term::NamedNode(NamedNode::new(s.to_string()).unwrap()),
63 "Literal" => match (data_type, lang) {
64 (Some(dt), None) => Term::Literal(Literal::new_typed_literal(s.to_string(), dt)),
65 (None, Some(l)) => {
66 Term::Literal(Literal::new_language_tagged_literal(s.to_string(), l).unwrap())
67 }
68 (_, _) => Term::Literal(Literal::new_simple_literal(s.to_string())),
69 },
70 "BNode" => Term::BlankNode(BlankNode::new(s.to_string()).unwrap()),
71 _ => Term::NamedNode(NamedNode::new(s.to_string()).unwrap()),
72 };
73 MyTerm(n)
74 }
75}
76
77fn term_to_python<'a>(
78 py: Python,
79 rdflib: &Bound<'a, PyModule>,
80 node: Term,
81) -> PyResult<Bound<'a, PyAny>> {
82 let dtype: Option<String> = match &node {
83 Term::Literal(lit) => {
84 let mut s = lit.datatype().to_string();
85 s.remove(0);
86 s.remove(s.len() - 1);
87 Some(s)
88 }
89 _ => None,
90 };
91 let lang: Option<&str> = match &node {
92 Term::Literal(lit) => lit.language(),
93 _ => None,
94 };
95
96 let res: Bound<'_, PyAny> = match &node {
97 Term::NamedNode(uri) => {
98 let mut uri = uri.to_string();
99 uri.remove(0);
100 uri.remove(uri.len() - 1);
101 rdflib.getattr("URIRef")?.call1((uri,))?
102 }
103 Term::Literal(literal) => {
104 match (dtype, lang) {
105 (_, Some(lang)) => {
107 rdflib
108 .getattr("Literal")?
109 .call1((literal.value(), lang, py.None()))?
110 }
111 (Some(dtype), None) => {
112 rdflib
113 .getattr("Literal")?
114 .call1((literal.value(), py.None(), dtype))?
115 }
116 (None, None) => rdflib.getattr("Literal")?.call1((literal.value(),))?,
117 }
118 }
119 Term::BlankNode(id) => rdflib
120 .getattr("BNode")?
121 .call1((id.clone().into_string(),))?,
122 };
123 Ok(res)
124}
125
126#[pyfunction]
128#[cfg(feature = "cli")]
129fn run_cli(py: Python<'_>, args: Option<Vec<String>>) -> PyResult<i32> {
130 let argv = args.unwrap_or_else(|| std::env::args().collect());
131 let code = py.allow_threads(move || match ontoenv_cli::run_from_args(argv) {
132 Ok(()) => 0,
133 Err(err) => {
134 eprintln!("{err}");
135 1
136 }
137 });
138 Ok(code)
139}
140
141#[pyfunction]
143#[cfg(not(feature = "cli"))]
144#[allow(unused_variables)]
145fn run_cli(_py: Python<'_>, _args: Option<Vec<String>>) -> PyResult<i32> {
146 Err(PyErr::new::<PyRuntimeError, _>(
147 "ontoenv was built without CLI support; rebuild with the 'cli' feature",
148 ))
149}
150
151#[pyclass(name = "Ontology")]
152#[derive(Clone)]
153struct PyOntology {
154 inner: OntologyRs,
155}
156
157#[pymethods]
158impl PyOntology {
159 #[getter]
160 fn id(&self) -> PyResult<String> {
161 Ok(self.inner.id().to_uri_string())
162 }
163
164 #[getter]
165 fn name(&self) -> PyResult<String> {
166 Ok(self.inner.name().to_uri_string())
167 }
168
169 #[getter]
170 fn imports(&self) -> PyResult<Vec<String>> {
171 Ok(self
172 .inner
173 .imports
174 .iter()
175 .map(|i| i.to_uri_string())
176 .collect())
177 }
178
179 #[getter]
180 fn location(&self) -> PyResult<Option<String>> {
181 Ok(self.inner.location().map(|l| l.to_string()))
182 }
183
184 #[getter]
185 fn last_updated(&self) -> PyResult<Option<String>> {
186 Ok(self.inner.last_updated.map(|dt| dt.to_rfc3339()))
187 }
188
189 #[getter]
190 fn version_properties(&self) -> PyResult<HashMap<String, String>> {
191 Ok(self
192 .inner
193 .version_properties()
194 .iter()
195 .map(|(k, v)| (k.to_uri_string(), v.clone()))
196 .collect())
197 }
198
199 #[getter]
200 fn namespace_map(&self) -> PyResult<HashMap<String, String>> {
201 Ok(self.inner.namespace_map().clone())
202 }
203
204 fn __repr__(&self) -> PyResult<String> {
205 Ok(format!("<Ontology: {}>", self.inner.name().to_uri_string()))
206 }
207}
208
209#[pyclass]
210struct OntoEnv {
211 inner: Arc<Mutex<Option<OntoEnvRs>>>,
212}
213
214#[pymethods]
215impl OntoEnv {
216 #[new]
217 #[pyo3(signature = (path=None, recreate=false, read_only=false, search_directories=None, require_ontology_names=false, strict=false, offline=false, use_cached_ontologies=false, resolution_policy="default".to_owned(), root=".".to_owned(), includes=None, excludes=None, temporary=false, no_search=false))]
218 fn new(
219 _py: Python,
220 path: Option<PathBuf>,
221 recreate: bool,
222 read_only: bool,
223 search_directories: Option<Vec<String>>,
224 require_ontology_names: bool,
225 strict: bool,
226 offline: bool,
227 use_cached_ontologies: bool,
228 resolution_policy: String,
229 root: String,
230 includes: Option<Vec<String>>,
231 excludes: Option<Vec<String>>,
232 temporary: bool,
233 no_search: bool,
234 ) -> PyResult<Self> {
235
236 if path.is_none() && root == "." && !recreate && !temporary {
239 return Err(PyValueError::new_err(
241 "OntoEnv directory not found at \"./.ontoenv\". You must provide a valid path or set recreate=True or temporary=True to create a new OntoEnv.",
242 ));
243 }
244 let mut root_path = path.clone().unwrap_or_else(|| PathBuf::from(root));
245 if root_path
247 .file_name()
248 .map(|n| n == OsStr::new(".ontoenv"))
249 .unwrap_or(false)
250 {
251 if let Some(parent) = root_path.parent() {
252 root_path = parent.to_path_buf();
253 }
254 }
255
256 let mut builder = config::Config::builder()
262 .root(root_path.clone())
263 .require_ontology_names(require_ontology_names)
264 .strict(strict)
265 .offline(offline)
266 .use_cached_ontologies(CacheMode::from(use_cached_ontologies))
267 .resolution_policy(resolution_policy)
268 .temporary(temporary)
269 .no_search(no_search);
270
271 if let Some(dirs) = search_directories {
272 let paths = dirs.into_iter().map(PathBuf::from).collect();
273 builder = builder.locations(paths);
274 }
275 if let Some(incl) = includes {
276 builder = builder.includes(incl);
277 }
278 if let Some(excl) = excludes {
279 builder = builder.excludes(excl);
280 }
281
282 let cfg = builder
283 .build()
284 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
285
286 let env = if cfg.temporary {
287 OntoEnvRs::init(cfg, false).map_err(anyhow_to_pyerr)?
289 } else if recreate {
290 OntoEnvRs::init(cfg, true).map_err(anyhow_to_pyerr)?
292 } else {
293 match ::ontoenv::api::find_ontoenv_root_from(&root_path) {
296 Some(found_root) => OntoEnvRs::load_from_directory(found_root, read_only)
297 .map_err(anyhow_to_pyerr)?,
298 None => {
299 if path.is_some() {
301 return Err(PyValueError::new_err(format!(
302 "OntoEnv directory not found at: \"{}\"",
303 format_path_for_error(&root_path.join(".ontoenv"))
304 )));
305 }
306 if read_only {
307 return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
308 "OntoEnv directory not found at: \"{}\" and read_only=True",
309 format_path_for_error(&root_path.join(".ontoenv"))
310 )));
311 }
312 OntoEnvRs::init(cfg, false).map_err(anyhow_to_pyerr)?
313 }
314 }
315 };
316
317 let inner = Arc::new(Mutex::new(Some(env)));
318
319 Ok(OntoEnv {
320 inner: inner.clone(),
321 })
322 }
323
324 #[pyo3(signature = (all=false))]
325 fn update(&self, all: bool) -> PyResult<()> {
326 let inner = self.inner.clone();
327 let mut guard = inner.lock().unwrap();
328 if let Some(env) = guard.as_mut() {
329 env.update_all(all).map_err(anyhow_to_pyerr)?;
330 env.save_to_directory().map_err(anyhow_to_pyerr)
331 } else {
332 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
333 "OntoEnv is closed",
334 ))
335 }
336 }
337
338 fn __repr__(&self) -> PyResult<String> {
345 let inner = self.inner.clone();
346 let guard = inner.lock().unwrap();
347 if let Some(env) = guard.as_ref() {
348 let stats = env.stats().map_err(anyhow_to_pyerr)?;
349 Ok(format!(
350 "<OntoEnv: {} ontologies, {} graphs, {} triples>",
351 stats.num_ontologies, stats.num_graphs, stats.num_triples,
352 ))
353 } else {
354 Ok("<OntoEnv: closed>".to_string())
355 }
356 }
357
358 fn import_graph(
361 &self,
362 py: Python,
363 destination_graph: &Bound<'_, PyAny>,
364 uri: &str,
365 ) -> PyResult<()> {
366 let inner = self.inner.clone();
367 let mut guard = inner.lock().unwrap();
368 let env = guard
369 .as_mut()
370 .ok_or_else(|| PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed"))?;
371 let rdflib = py.import("rdflib")?;
372 let iri = NamedNode::new(uri)
373 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
374 let graphid = env
375 .resolve(ResolveTarget::Graph(iri.clone()))
376 .ok_or_else(|| {
377 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
378 "Failed to resolve graph for URI: {uri}"
379 ))
380 })?;
381 let mut graph = env.get_graph(&graphid).map_err(anyhow_to_pyerr)?;
382
383 let uriref_constructor = rdflib.getattr("URIRef")?;
384 let type_uri = uriref_constructor.call1((TYPE.as_str(),))?;
385 let ontology_uri = uriref_constructor.call1((ONTOLOGY.as_str(),))?;
386 let kwargs = [("predicate", type_uri), ("object", ontology_uri)].into_py_dict(py)?;
387 let result = destination_graph.call_method("value", (), Some(&kwargs))?;
388 if !result.is_none() {
389 let ontology = NamedNode::new(result.extract::<String>()?)
390 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
391 let base_ontology = NamedOrBlankNodeRef::NamedNode(ontology.as_ref());
392
393 transform::rewrite_sh_prefixes_graph(&mut graph, base_ontology);
394 transform::remove_ontology_declarations_graph(&mut graph, base_ontology);
395 }
396 transform::remove_owl_imports_graph(&mut graph, Some(&[iri.as_ref()]));
398
399 Python::with_gil(|_py| {
400 for triple in graph.into_iter() {
401 let s: Term = triple.subject.into();
402 let p: Term = triple.predicate.into();
403 let o: Term = triple.object.into();
404
405 let t = PyTuple::new(
406 py,
407 &[
408 term_to_python(py, &rdflib, s)?,
409 term_to_python(py, &rdflib, p)?,
410 term_to_python(py, &rdflib, o)?,
411 ],
412 )?;
413
414 destination_graph.getattr("add")?.call1((t,))?;
415 }
416 Ok::<(), PyErr>(())
417 })?;
418 Ok(())
419 }
420
421 #[pyo3(signature = (uri, recursion_depth = -1))]
423 fn list_closure(&self, _py: Python, uri: &str, recursion_depth: i32) -> PyResult<Vec<String>> {
424 let iri = NamedNode::new(uri)
425 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
426 let inner = self.inner.clone();
427 let mut guard = inner.lock().unwrap();
428 let env = guard
429 .as_mut()
430 .ok_or_else(|| PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed"))?;
431 let graphid = env
432 .resolve(ResolveTarget::Graph(iri.clone()))
433 .ok_or_else(|| {
434 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
435 "Failed to resolve graph for URI: {uri}"
436 ))
437 })?;
438 let ont = env.ontologies().get(&graphid).ok_or_else(|| {
439 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Ontology {iri} not found"))
440 })?;
441 let closure = env
442 .get_closure(ont.id(), recursion_depth)
443 .map_err(anyhow_to_pyerr)?;
444 let names: Vec<String> = closure.iter().map(|ont| ont.to_uri_string()).collect();
445 Ok(names)
446 }
447
448 #[pyo3(signature = (uri, destination_graph=None, rewrite_sh_prefixes=true, remove_owl_imports=true, recursion_depth=-1))]
455 fn get_closure<'a>(
456 &self,
457 py: Python<'a>,
458 uri: &str,
459 destination_graph: Option<&Bound<'a, PyAny>>,
460 rewrite_sh_prefixes: bool,
461 remove_owl_imports: bool,
462 recursion_depth: i32,
463 ) -> PyResult<(Bound<'a, PyAny>, Vec<String>)> {
464 let rdflib = py.import("rdflib")?;
465 let iri = NamedNode::new(uri)
466 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
467 let inner = self.inner.clone();
468 let mut guard = inner.lock().unwrap();
469 let env = guard
470 .as_mut()
471 .ok_or_else(|| PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed"))?;
472 let graphid = env
473 .resolve(ResolveTarget::Graph(iri.clone()))
474 .ok_or_else(|| {
475 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("No graph with URI: {uri}"))
476 })?;
477 let ont = env.ontologies().get(&graphid).ok_or_else(|| {
478 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Ontology {iri} not found"))
479 })?;
480 let closure = env
481 .get_closure(ont.id(), recursion_depth)
482 .map_err(anyhow_to_pyerr)?;
483 let closure_names: Vec<String> = closure.iter().map(|ont| ont.to_uri_string()).collect();
484 let destination_graph = match destination_graph {
486 Some(g) => g.clone(),
487 None => rdflib.getattr("Graph")?.call0()?,
488 };
489 let union = env
490 .get_union_graph(
491 &closure,
492 Some(rewrite_sh_prefixes),
493 Some(remove_owl_imports),
494 )
495 .map_err(anyhow_to_pyerr)?;
496 for triple in union.dataset.into_iter() {
497 let s: Term = triple.subject.into();
498 let p: Term = triple.predicate.into();
499 let o: Term = triple.object.into();
500 let t = PyTuple::new(
501 py,
502 &[
503 term_to_python(py, &rdflib, s)?,
504 term_to_python(py, &rdflib, p)?,
505 term_to_python(py, &rdflib, o)?,
506 ],
507 )?;
508 destination_graph.getattr("add")?.call1((t,))?;
509 }
510
511 if remove_owl_imports {
513 for graphid in union.graph_ids {
514 let iri = term_to_python(py, &rdflib, Term::NamedNode(graphid.into()))?;
515 let pred = term_to_python(py, &rdflib, IMPORTS.into())?;
516 let remove_tuple = PyTuple::new(py, &[py.None(), pred.into(), iri.into()])?;
518 destination_graph
519 .getattr("remove")?
520 .call1((remove_tuple,))?;
521 }
522 }
523 Ok((destination_graph, closure_names))
524 }
525
526 #[pyo3(signature = (includes=None))]
528 fn dump(&self, _py: Python, includes: Option<String>) -> PyResult<()> {
529 let inner = self.inner.clone();
530 let guard = inner.lock().unwrap();
531 if let Some(env) = guard.as_ref() {
532 env.dump(includes.as_deref());
533 Ok(())
534 } else {
535 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
536 "OntoEnv is closed",
537 ))
538 }
539 }
540
541 #[pyo3(signature = (graph, recursion_depth=-1, fetch_missing=false))]
548 fn import_dependencies<'a>(
549 &self,
550 py: Python<'a>,
551 graph: &Bound<'a, PyAny>,
552 recursion_depth: i32,
553 fetch_missing: bool,
554 ) -> PyResult<Vec<String>> {
555 let rdflib = py.import("rdflib")?;
556 let py_imports_pred = term_to_python(py, &rdflib, Term::NamedNode(IMPORTS.into()))?;
557
558 let kwargs = [("predicate", py_imports_pred)].into_py_dict(py)?;
559 let objects_iter = graph.call_method("objects", (), Some(&kwargs))?;
560 let builtins = py.import("builtins")?;
561 let objects_list = builtins.getattr("list")?.call1((objects_iter,))?;
562 let imports: Vec<String> = objects_list.extract()?;
563
564 if imports.is_empty() {
565 return Ok(Vec::new());
566 }
567
568 let inner = self.inner.clone();
569 let mut guard = inner.lock().unwrap();
570 let env = guard
571 .as_mut()
572 .ok_or_else(|| PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed"))?;
573
574 let is_strict = env.is_strict();
575 let mut all_ontologies = HashSet::new();
576 let mut all_closure_names: Vec<String> = Vec::new();
577
578 for uri in &imports {
579 let iri = NamedNode::new(uri.as_str())
580 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
581
582 let mut graphid = env.resolve(ResolveTarget::Graph(iri.clone()));
583
584 if graphid.is_none() && fetch_missing {
585 let location = OntologyLocation::from_str(uri.as_str()).map_err(anyhow_to_pyerr)?;
586 match env.add(location, Overwrite::Preserve, RefreshStrategy::UseCache) {
587 Ok(new_id) => {
588 graphid = Some(new_id);
589 }
590 Err(e) => {
591 if is_strict {
592 return Err(anyhow_to_pyerr(e));
593 }
594 println!("Failed to fetch {uri}: {e}");
595 }
596 }
597 }
598
599 let graphid = match graphid {
600 Some(id) => id,
601 None => {
602 if is_strict {
603 return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
604 "Failed to resolve graph for URI: {}",
605 uri
606 )));
607 }
608 println!("Could not find {uri:?}");
609 continue;
610 }
611 };
612
613 let ont = env.ontologies().get(&graphid).ok_or_else(|| {
614 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
615 "Ontology {} not found",
616 uri
617 ))
618 })?;
619
620 let closure = env
621 .get_closure(ont.id(), recursion_depth)
622 .map_err(anyhow_to_pyerr)?;
623 for c_ont in closure {
624 all_closure_names.push(c_ont.to_uri_string());
625 all_ontologies.insert(c_ont.clone());
626 }
627 }
628
629 if all_ontologies.is_empty() {
630 return Ok(Vec::new());
631 }
632
633 let union = env
634 .get_union_graph(&all_ontologies, Some(true), Some(true))
635 .map_err(anyhow_to_pyerr)?;
636
637 for triple in union.dataset.into_iter() {
638 let s: Term = triple.subject.into();
639 let p: Term = triple.predicate.into();
640 let o: Term = triple.object.into();
641 let t = PyTuple::new(
642 py,
643 &[
644 term_to_python(py, &rdflib, s)?,
645 term_to_python(py, &rdflib, p)?,
646 term_to_python(py, &rdflib, o)?,
647 ],
648 )?;
649 graph.getattr("add")?.call1((t,))?;
650 }
651
652 let py_imports_pred_for_remove = term_to_python(py, &rdflib, IMPORTS.into())?;
654 let remove_tuple = PyTuple::new(
655 py,
656 &[py.None(), py_imports_pred_for_remove.into(), py.None()],
657 )?;
658 graph.getattr("remove")?.call1((remove_tuple,))?;
659
660 all_closure_names.sort();
661 all_closure_names.dedup();
662
663 Ok(all_closure_names)
664 }
665
666 #[pyo3(signature = (graph, destination_graph=None, recursion_depth=-1, fetch_missing=false, rewrite_sh_prefixes=true, remove_owl_imports=true))]
689 fn get_dependencies_graph<'a>(
690 &self,
691 py: Python<'a>,
692 graph: &Bound<'a, PyAny>,
693 destination_graph: Option<&Bound<'a, PyAny>>,
694 recursion_depth: i32,
695 fetch_missing: bool,
696 rewrite_sh_prefixes: bool,
697 remove_owl_imports: bool,
698 ) -> PyResult<(Bound<'a, PyAny>, Vec<String>)> {
699 let rdflib = py.import("rdflib")?;
700 let py_imports_pred = term_to_python(py, &rdflib, Term::NamedNode(IMPORTS.into()))?;
701
702 let kwargs = [("predicate", py_imports_pred)].into_py_dict(py)?;
703 let objects_iter = graph.call_method("objects", (), Some(&kwargs))?;
704 let builtins = py.import("builtins")?;
705 let objects_list = builtins.getattr("list")?.call1((objects_iter,))?;
706 let imports: Vec<String> = objects_list.extract()?;
707
708 let destination_graph = match destination_graph {
709 Some(g) => g.clone(),
710 None => rdflib.getattr("Graph")?.call0()?,
711 };
712
713 if imports.is_empty() {
714 return Ok((destination_graph, Vec::new()));
715 }
716
717 let inner = self.inner.clone();
718 let mut guard = inner.lock().unwrap();
719 let env = guard
720 .as_mut()
721 .ok_or_else(|| PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed"))?;
722
723 let is_strict = env.is_strict();
724 let mut all_ontologies = HashSet::new();
725 let mut all_closure_names: Vec<String> = Vec::new();
726
727 for uri in &imports {
728 let iri = NamedNode::new(uri.as_str())
729 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
730
731 let mut graphid = env.resolve(ResolveTarget::Graph(iri.clone()));
732
733 if graphid.is_none() && fetch_missing {
734 let location = OntologyLocation::from_str(uri.as_str()).map_err(anyhow_to_pyerr)?;
735 match env.add(location, Overwrite::Preserve, RefreshStrategy::UseCache) {
736 Ok(new_id) => {
737 graphid = Some(new_id);
738 }
739 Err(e) => {
740 if is_strict {
741 return Err(anyhow_to_pyerr(e));
742 }
743 println!("Failed to fetch {uri}: {e}");
744 }
745 }
746 }
747
748 let graphid = match graphid {
749 Some(id) => id,
750 None => {
751 if is_strict {
752 return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
753 "Failed to resolve graph for URI: {}",
754 uri
755 )));
756 }
757 println!("Could not find {uri:?}");
758 continue;
759 }
760 };
761
762 let ont = env.ontologies().get(&graphid).ok_or_else(|| {
763 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
764 "Ontology {} not found",
765 uri
766 ))
767 })?;
768
769 let closure = env
770 .get_closure(ont.id(), recursion_depth)
771 .map_err(anyhow_to_pyerr)?;
772 for c_ont in closure {
773 all_closure_names.push(c_ont.to_uri_string());
774 all_ontologies.insert(c_ont.clone());
775 }
776 }
777
778 if all_ontologies.is_empty() {
779 return Ok((destination_graph, Vec::new()));
780 }
781
782 let union = env
783 .get_union_graph(
784 &all_ontologies,
785 Some(rewrite_sh_prefixes),
786 Some(remove_owl_imports),
787 )
788 .map_err(anyhow_to_pyerr)?;
789
790 for triple in union.dataset.into_iter() {
791 let s: Term = triple.subject.into();
792 let p: Term = triple.predicate.into();
793 let o: Term = triple.object.into();
794 let t = PyTuple::new(
795 py,
796 &[
797 term_to_python(py, &rdflib, s)?,
798 term_to_python(py, &rdflib, p)?,
799 term_to_python(py, &rdflib, o)?,
800 ],
801 )?;
802 destination_graph.getattr("add")?.call1((t,))?;
803 }
804
805 if remove_owl_imports {
806 for graphid in union.graph_ids {
807 let iri = term_to_python(py, &rdflib, Term::NamedNode(graphid.into()))?;
808 let pred = term_to_python(py, &rdflib, IMPORTS.into())?;
809 let remove_tuple = PyTuple::new(py, &[py.None(), pred.into(), iri.into()])?;
810 destination_graph
811 .getattr("remove")?
812 .call1((remove_tuple,))?;
813 }
814 }
815
816 all_closure_names.sort();
817 all_closure_names.dedup();
818
819 Ok((destination_graph, all_closure_names))
820 }
821
822 #[pyo3(signature = (location, overwrite = false, fetch_imports = true, force = false))]
824 fn add(
825 &self,
826 location: &Bound<'_, PyAny>,
827 overwrite: bool,
828 fetch_imports: bool,
829 force: bool,
830 ) -> PyResult<String> {
831 let inner = self.inner.clone();
832 let mut guard = inner.lock().unwrap();
833 let env = guard
834 .as_mut()
835 .ok_or_else(|| PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed"))?;
836
837 let location =
838 OntologyLocation::from_str(&location.to_string()).map_err(anyhow_to_pyerr)?;
839 let overwrite_flag: Overwrite = overwrite.into();
840 let refresh: RefreshStrategy = force.into();
841 let graph_id = if fetch_imports {
842 env.add(location, overwrite_flag, refresh)
843 } else {
844 env.add_no_imports(location, overwrite_flag, refresh)
845 }
846 .map_err(anyhow_to_pyerr)?;
847 Ok(graph_id.to_uri_string())
848 }
849
850 #[pyo3(signature = (location, overwrite = false, force = false))]
852 fn add_no_imports(
853 &self,
854 location: &Bound<'_, PyAny>,
855 overwrite: bool,
856 force: bool,
857 ) -> PyResult<String> {
858 let inner = self.inner.clone();
859 let mut guard = inner.lock().unwrap();
860 let env = guard
861 .as_mut()
862 .ok_or_else(|| PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed"))?;
863 let location =
864 OntologyLocation::from_str(&location.to_string()).map_err(anyhow_to_pyerr)?;
865 let overwrite_flag: Overwrite = overwrite.into();
866 let refresh: RefreshStrategy = force.into();
867 let graph_id = env
868 .add_no_imports(location, overwrite_flag, refresh)
869 .map_err(anyhow_to_pyerr)?;
870 Ok(graph_id.to_uri_string())
871 }
872
873 fn get_importers(&self, uri: &str) -> PyResult<Vec<String>> {
875 let iri = NamedNode::new(uri)
876 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
877 let inner = self.inner.clone();
878 let guard = inner.lock().unwrap();
879 let env = guard
880 .as_ref()
881 .ok_or_else(|| PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed"))?;
882 let importers = env.get_importers(&iri).map_err(anyhow_to_pyerr)?;
883 let names: Vec<String> = importers.iter().map(|ont| ont.to_uri_string()).collect();
884 Ok(names)
885 }
886
887 fn get_ontology(&self, uri: &str) -> PyResult<PyOntology> {
889 let iri = NamedNode::new(uri)
890 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
891 let inner = self.inner.clone();
892 let guard = inner.lock().unwrap();
893 let env = guard
894 .as_ref()
895 .ok_or_else(|| PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed"))?;
896 let graphid = env
897 .resolve(ResolveTarget::Graph(iri.clone()))
898 .ok_or_else(|| {
899 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
900 "Failed to resolve graph for URI: {uri}"
901 ))
902 })?;
903 let ont = env.get_ontology(&graphid).map_err(anyhow_to_pyerr)?;
904 Ok(PyOntology { inner: ont })
905 }
906
907 fn get_graph(&self, py: Python, uri: &Bound<'_, PyString>) -> PyResult<Py<PyAny>> {
909 let rdflib = py.import("rdflib")?;
910 let iri = NamedNode::new(uri.to_string())
911 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
912 let graph = {
913 let inner = self.inner.clone();
914 let guard = inner.lock().unwrap();
915 let env = guard.as_ref().ok_or_else(|| {
916 PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
917 })?;
918 let graphid = env.resolve(ResolveTarget::Graph(iri)).ok_or_else(|| {
919 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
920 "Failed to resolve graph for URI: {uri}"
921 ))
922 })?;
923
924 env.get_graph(&graphid).map_err(anyhow_to_pyerr)?
925 };
926 let res = rdflib.getattr("Graph")?.call0()?;
927 for triple in graph.into_iter() {
928 let s: Term = triple.subject.into();
929 let p: Term = triple.predicate.into();
930 let o: Term = triple.object.into();
931
932 let t = PyTuple::new(
933 py,
934 &[
935 term_to_python(py, &rdflib, s)?,
936 term_to_python(py, &rdflib, p)?,
937 term_to_python(py, &rdflib, o)?,
938 ],
939 )?;
940
941 res.getattr("add")?.call1((t,))?;
942 }
943 Ok(res.into())
944 }
945
946 fn get_ontology_names(&self) -> PyResult<Vec<String>> {
948 let inner = self.inner.clone();
949 let guard = inner.lock().unwrap();
950 let env = guard
951 .as_ref()
952 .ok_or_else(|| PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed"))?;
953 let names: Vec<String> = env.ontologies().keys().map(|k| k.to_uri_string()).collect();
954 Ok(names)
955 }
956
957 fn to_rdflib_dataset(&self, py: Python) -> PyResult<Py<PyAny>> {
959 let inner = self.inner.clone();
960 let guard = inner.lock().unwrap();
961 let env = guard
962 .as_ref()
963 .ok_or_else(|| PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed"))?;
964 let rdflib = py.import("rdflib")?;
965 let dataset_cls = rdflib.getattr("Dataset")?;
966 let ds = dataset_cls.call0()?;
967 let uriref = rdflib.getattr("URIRef")?;
968
969 for (_gid, ont) in env.ontologies().iter() {
970 let id_str = ont.id().name().as_str();
971 let id_py = uriref.call1((id_str,))?;
972 let kwargs = [("identifier", id_py.clone())].into_py_dict(py)?;
973 let ctx = ds.getattr("graph")?.call((), Some(&kwargs))?;
974
975 let graph = env.get_graph(ont.id()).map_err(anyhow_to_pyerr)?;
976 for t in graph.iter() {
977 let s: Term = t.subject.into();
978 let p: Term = t.predicate.into();
979 let o: Term = t.object.into();
980 let triple = PyTuple::new(
981 py,
982 &[
983 term_to_python(py, &rdflib, s)?,
984 term_to_python(py, &rdflib, p)?,
985 term_to_python(py, &rdflib, o)?,
986 ],
987 )?;
988 ctx.getattr("add")?.call1((triple,))?;
989 }
990 }
991
992 Ok(ds.into())
993 }
994
995 fn is_offline(&self) -> PyResult<bool> {
997 let inner = self.inner.clone();
998 let guard = inner.lock().unwrap();
999 if let Some(env) = guard.as_ref() {
1000 Ok(env.is_offline())
1001 } else {
1002 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
1003 "OntoEnv is closed",
1004 ))
1005 }
1006 }
1007
1008 fn set_offline(&mut self, offline: bool) -> PyResult<()> {
1009 let inner = self.inner.clone();
1010 let mut guard = inner.lock().unwrap();
1011 if let Some(env) = guard.as_mut() {
1012 env.set_offline(offline);
1013 env.save_to_directory().map_err(anyhow_to_pyerr)
1014 } else {
1015 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
1016 "OntoEnv is closed",
1017 ))
1018 }
1019 }
1020
1021 fn is_strict(&self) -> PyResult<bool> {
1022 let inner = self.inner.clone();
1023 let guard = inner.lock().unwrap();
1024 if let Some(env) = guard.as_ref() {
1025 Ok(env.is_strict())
1026 } else {
1027 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
1028 "OntoEnv is closed",
1029 ))
1030 }
1031 }
1032
1033 fn set_strict(&mut self, strict: bool) -> PyResult<()> {
1034 let inner = self.inner.clone();
1035 let mut guard = inner.lock().unwrap();
1036 if let Some(env) = guard.as_mut() {
1037 env.set_strict(strict);
1038 env.save_to_directory().map_err(anyhow_to_pyerr)
1039 } else {
1040 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
1041 "OntoEnv is closed",
1042 ))
1043 }
1044 }
1045
1046 fn requires_ontology_names(&self) -> PyResult<bool> {
1047 let inner = self.inner.clone();
1048 let guard = inner.lock().unwrap();
1049 if let Some(env) = guard.as_ref() {
1050 Ok(env.requires_ontology_names())
1051 } else {
1052 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
1053 "OntoEnv is closed",
1054 ))
1055 }
1056 }
1057
1058 fn set_require_ontology_names(&mut self, require: bool) -> PyResult<()> {
1059 let inner = self.inner.clone();
1060 let mut guard = inner.lock().unwrap();
1061 if let Some(env) = guard.as_mut() {
1062 env.set_require_ontology_names(require);
1063 env.save_to_directory().map_err(anyhow_to_pyerr)
1064 } else {
1065 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
1066 "OntoEnv is closed",
1067 ))
1068 }
1069 }
1070
1071 fn no_search(&self) -> PyResult<bool> {
1072 let inner = self.inner.clone();
1073 let guard = inner.lock().unwrap();
1074 if let Some(env) = guard.as_ref() {
1075 Ok(env.no_search())
1076 } else {
1077 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
1078 "OntoEnv is closed",
1079 ))
1080 }
1081 }
1082
1083 fn set_no_search(&mut self, no_search: bool) -> PyResult<()> {
1084 let inner = self.inner.clone();
1085 let mut guard = inner.lock().unwrap();
1086 if let Some(env) = guard.as_mut() {
1087 env.set_no_search(no_search);
1088 env.save_to_directory().map_err(anyhow_to_pyerr)
1089 } else {
1090 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
1091 "OntoEnv is closed",
1092 ))
1093 }
1094 }
1095
1096 fn resolution_policy(&self) -> PyResult<String> {
1097 let inner = self.inner.clone();
1098 let guard = inner.lock().unwrap();
1099 if let Some(env) = guard.as_ref() {
1100 Ok(env.resolution_policy().to_string())
1101 } else {
1102 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
1103 "OntoEnv is closed",
1104 ))
1105 }
1106 }
1107
1108 fn set_resolution_policy(&mut self, policy: String) -> PyResult<()> {
1109 let inner = self.inner.clone();
1110 let mut guard = inner.lock().unwrap();
1111 if let Some(env) = guard.as_mut() {
1112 env.set_resolution_policy(policy);
1113 env.save_to_directory().map_err(anyhow_to_pyerr)
1114 } else {
1115 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
1116 "OntoEnv is closed",
1117 ))
1118 }
1119 }
1120
1121 pub fn store_path(&self) -> PyResult<Option<String>> {
1122 let inner = self.inner.clone();
1123 let guard = inner.lock().unwrap();
1124 if let Some(env) = guard.as_ref() {
1125 match env.store_path() {
1126 Some(path) => {
1127 let dir = path.parent().unwrap_or(path);
1128 Ok(Some(dir.to_string_lossy().to_string()))
1129 }
1130 None => Ok(None), }
1132 } else {
1133 Ok(None)
1134 }
1135 }
1136
1137 pub fn close(&mut self, py: Python<'_>) -> PyResult<()> {
1142 py.allow_threads(|| {
1143 let inner = self.inner.clone();
1144 let mut guard = inner.lock().unwrap();
1145 if let Some(env) = guard.as_mut() {
1146 env.save_to_directory().map_err(anyhow_to_pyerr)?;
1147 env.flush().map_err(anyhow_to_pyerr)?;
1148 }
1149 *guard = None;
1150 Ok(())
1151 })
1152 }
1153
1154 pub fn flush(&mut self, py: Python<'_>) -> PyResult<()> {
1155 py.allow_threads(|| {
1156 let inner = self.inner.clone();
1157 let mut guard = inner.lock().unwrap();
1158 if let Some(env) = guard.as_mut() {
1159 env.flush().map_err(anyhow_to_pyerr)
1160 } else {
1161 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
1162 "OntoEnv is closed",
1163 ))
1164 }
1165 })
1166 }
1167}
1168
1169#[pymodule]
1170fn _native(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
1171 ::ontoenv::api::init_logging();
1173 let _ = env_logger::try_init();
1175
1176 m.add_class::<OntoEnv>()?;
1177 m.add_class::<PyOntology>()?;
1178 m.add_function(wrap_pyfunction!(run_cli, m)?)?;
1179 m.add("version", env!("CARGO_PKG_VERSION"))?;
1181 Ok(())
1182}