1use ::ontoenv::api::{OntoEnv as OntoEnvRs, ResolveTarget};
2use ::ontoenv::config;
3use ::ontoenv::consts::{IMPORTS, ONTOLOGY, TYPE};
4use ::ontoenv::ToUriString;
5use ::ontoenv::ontology::{Ontology as OntologyRs, OntologyLocation};
6use ::ontoenv::transform;
7use anyhow::Error;
8use oxigraph::model::{BlankNode, Literal, NamedNode, SubjectRef, Term};
9use pyo3::{
10 prelude::*,
11 types::{IntoPyDict, PyString, PyTuple},
12};
13use std::borrow::Borrow;
14use std::collections::{HashMap, HashSet};
15use std::path::PathBuf;
16use std::sync::{Arc, Mutex, Once};
17
18fn anyhow_to_pyerr(e: Error) -> PyErr {
19 PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string())
20}
21
22static INIT: Once = Once::new();
23
24#[allow(dead_code)]
25struct MyTerm(Term);
26impl From<Result<Bound<'_, PyAny>, pyo3::PyErr>> for MyTerm {
27 fn from(s: Result<Bound<'_, PyAny>, pyo3::PyErr>) -> Self {
28 let s = s.unwrap();
29 let typestr = s.get_type().name().unwrap();
30 let typestr = typestr.to_string();
31 let data_type: Option<NamedNode> = match s.getattr("datatype") {
32 Ok(dt) => {
33 if dt.is_none() {
34 None
35 } else {
36 Some(NamedNode::new(dt.to_string()).unwrap())
37 }
38 }
39 Err(_) => None,
40 };
41 let lang: Option<String> = match s.getattr("language") {
42 Ok(l) => {
43 if l.is_none() {
44 None
45 } else {
46 Some(l.to_string())
47 }
48 }
49 Err(_) => None,
50 };
51 let n: Term = match typestr.borrow() {
52 "URIRef" => Term::NamedNode(NamedNode::new(s.to_string()).unwrap()),
53 "Literal" => match (data_type, lang) {
54 (Some(dt), None) => Term::Literal(Literal::new_typed_literal(s.to_string(), dt)),
55 (None, Some(l)) => {
56 Term::Literal(Literal::new_language_tagged_literal(s.to_string(), l).unwrap())
57 }
58 (_, _) => Term::Literal(Literal::new_simple_literal(s.to_string())),
59 },
60 "BNode" => Term::BlankNode(BlankNode::new(s.to_string()).unwrap()),
61 _ => Term::NamedNode(NamedNode::new(s.to_string()).unwrap()),
62 };
63 MyTerm(n)
64 }
65}
66
67fn term_to_python<'a>(
68 py: Python,
69 rdflib: &Bound<'a, PyModule>,
70 node: Term,
71) -> PyResult<Bound<'a, PyAny>> {
72 let dtype: Option<String> = match &node {
73 Term::Literal(lit) => {
74 let mut s = lit.datatype().to_string();
75 s.remove(0);
76 s.remove(s.len() - 1);
77 Some(s)
78 }
79 _ => None,
80 };
81 let lang: Option<&str> = match &node {
82 Term::Literal(lit) => lit.language(),
83 _ => None,
84 };
85
86 let res: Bound<'_, PyAny> = match &node {
87 Term::NamedNode(uri) => {
88 let mut uri = uri.to_string();
89 uri.remove(0);
90 uri.remove(uri.len() - 1);
91 rdflib.getattr("URIRef")?.call1((uri,))?
92 }
93 Term::Literal(literal) => {
94 match (dtype, lang) {
95 (_, Some(lang)) => {
97 rdflib
98 .getattr("Literal")?
99 .call1((literal.value(), lang, py.None()))?
100 }
101 (Some(dtype), None) => {
102 rdflib
103 .getattr("Literal")?
104 .call1((literal.value(), py.None(), dtype))?
105 }
106 (None, None) => rdflib.getattr("Literal")?.call1((literal.value(),))?,
107 }
108 }
109 Term::BlankNode(id) => rdflib
110 .getattr("BNode")?
111 .call1((id.clone().into_string(),))?,
112 Term::Triple(_) => {
113 return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
114 "Triples are not supported",
115 ))
116 }
117 };
118 Ok(res)
119}
120
121#[pyclass]
122#[derive(Clone)]
123struct Config {
124 cfg: config::Config,
125}
126
127#[pymethods]
128impl Config {
129 #[new]
130 #[pyo3(signature = (search_directories=None, require_ontology_names=false, strict=false, offline=false, resolution_policy="default".to_owned(), root=".".to_owned(), includes=None, excludes=None, temporary=false, no_search=false))]
131 fn new(
132 search_directories: Option<Vec<String>>,
133 require_ontology_names: bool,
134 strict: bool,
135 offline: bool,
136 resolution_policy: String,
137 root: String,
138 includes: Option<Vec<String>>,
139 excludes: Option<Vec<String>>,
140 temporary: bool,
141 no_search: bool,
142 ) -> PyResult<Self> {
143 let mut builder = config::Config::builder()
144 .root(root.into())
145 .require_ontology_names(require_ontology_names)
146 .strict(strict)
147 .offline(offline)
148 .resolution_policy(resolution_policy)
149 .temporary(temporary)
150 .no_search(no_search);
151
152 if let Some(dirs) = search_directories {
153 let paths = dirs.into_iter().map(PathBuf::from).collect();
154 builder = builder.locations(paths);
155 }
156
157 if let Some(includes) = includes {
158 builder = builder.includes(includes);
159 }
160
161 if let Some(excludes) = excludes {
162 builder = builder.excludes(excludes);
163 }
164
165 let cfg = builder
166 .build()
167 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
168
169 Ok(Config { cfg })
170 }
171}
172
173
174#[pyclass(name = "Ontology")]
175#[derive(Clone)]
176struct PyOntology {
177 inner: OntologyRs,
178}
179
180#[pymethods]
181impl 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
235#[pyclass]
236struct OntoEnv {
237 inner: Arc<Mutex<Option<OntoEnvRs>>>,
238}
239
240#[pymethods]
241impl OntoEnv {
242 #[new]
243 #[pyo3(signature = (config=None, path=None, recreate=false, read_only=false))]
244 fn new(
245 _py: Python,
246 config: Option<Config>,
247 path: Option<PathBuf>,
248 recreate: bool,
249 read_only: bool,
250 ) -> PyResult<Self> {
251 INIT.call_once(|| {
254 env_logger::init();
255 });
256
257 let env = if let Some(c) = config {
258 let config_path = path.unwrap_or_else(|| PathBuf::from("."));
259 if c.cfg.temporary {
261 OntoEnvRs::init(c.cfg, recreate).map_err(anyhow_to_pyerr)
262 } else if !recreate && config_path.join(".ontoenv").exists() {
263 OntoEnvRs::load_from_directory(config_path, read_only).map_err(anyhow_to_pyerr)
265 } else {
266 OntoEnvRs::init(c.cfg, recreate).map_err(anyhow_to_pyerr)
268 }
269 } else if let Some(p) = path {
270 if !recreate {
271 if let Some(root) = ::ontoenv::api::find_ontoenv_root_from(&p) {
272 OntoEnvRs::load_from_directory(root, read_only).map_err(anyhow_to_pyerr)
273 } else {
274 let cfg = config::Config::default(p).map_err(|e| {
275 PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string())
276 })?;
277 OntoEnvRs::init(cfg, false).map_err(anyhow_to_pyerr)
278 }
279 } else {
280 let cfg = config::Config::default(p).map_err(|e| {
281 PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string())
282 })?;
283 OntoEnvRs::init(cfg, true).map_err(anyhow_to_pyerr)
284 }
285 } else {
286 OntoEnvRs::new_offline().map_err(anyhow_to_pyerr)
287 }?;
288
289 let inner = Arc::new(Mutex::new(Some(env)));
290
291 Ok(OntoEnv {
292 inner: inner.clone(),
293 })
294 }
295
296 fn update(&self) -> PyResult<()> {
297 let inner = self.inner.clone();
298 let mut guard = inner.lock().unwrap();
299 if let Some(env) = guard.as_mut() {
300 env.update().map_err(anyhow_to_pyerr)?;
301 env.save_to_directory().map_err(anyhow_to_pyerr)
302 } else {
303 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
304 "OntoEnv is closed",
305 ))
306 }
307 }
308
309 fn __repr__(&self) -> PyResult<String> {
316 let inner = self.inner.clone();
317 let guard = inner.lock().unwrap();
318 if let Some(env) = guard.as_ref() {
319 let stats = env.stats().map_err(anyhow_to_pyerr)?;
320 Ok(format!(
321 "<OntoEnv: {} ontologies, {} graphs, {} triples>",
322 stats.num_ontologies, stats.num_graphs, stats.num_triples,
323 ))
324 } else {
325 Ok("<OntoEnv: closed>".to_string())
326 }
327 }
328
329 fn import_graph(
332 &self,
333 py: Python,
334 destination_graph: &Bound<'_, PyAny>,
335 uri: &str,
336 ) -> PyResult<()> {
337 let inner = self.inner.clone();
338 let mut guard = inner.lock().unwrap();
339 let env = guard.as_mut().ok_or_else(|| {
340 PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
341 })?;
342 let rdflib = py.import("rdflib")?;
343 let iri = NamedNode::new(uri)
344 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
345 let graphid = env
346 .resolve(ResolveTarget::Graph(iri.clone()))
347 .ok_or_else(|| {
348 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
349 "Failed to resolve graph for URI: {uri}"
350 ))
351 })?;
352 let mut graph = env.get_graph(&graphid).map_err(anyhow_to_pyerr)?;
353
354 let uriref_constructor = rdflib.getattr("URIRef")?;
355 let type_uri = uriref_constructor.call1((TYPE.as_str(),))?;
356 let ontology_uri = uriref_constructor.call1((ONTOLOGY.as_str(),))?;
357 let kwargs = [("predicate", type_uri), ("object", ontology_uri)].into_py_dict(py)?;
358 let result = destination_graph.call_method("value", (), Some(&kwargs))?;
359 if !result.is_none() {
360 let ontology = NamedNode::new(result.extract::<String>()?)
361 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
362 let base_ontology: SubjectRef = SubjectRef::NamedNode(ontology.as_ref());
363
364 transform::rewrite_sh_prefixes_graph(&mut graph, base_ontology);
365 transform::remove_ontology_declarations_graph(&mut graph, base_ontology);
366 }
367 transform::remove_owl_imports_graph(&mut graph, Some(&[iri.as_ref()]));
369
370 Python::with_gil(|_py| {
371 for triple in graph.into_iter() {
372 let s: Term = triple.subject.into();
373 let p: Term = triple.predicate.into();
374 let o: Term = triple.object.into();
375
376 let t = PyTuple::new(
377 py,
378 &[
379 term_to_python(py, &rdflib, s)?,
380 term_to_python(py, &rdflib, p)?,
381 term_to_python(py, &rdflib, o)?,
382 ],
383 )?;
384
385 destination_graph.getattr("add")?.call1((t,))?;
386 }
387 Ok::<(), PyErr>(())
388 })?;
389 Ok(())
390 }
391
392 #[pyo3(signature = (uri, recursion_depth = -1))]
394 fn list_closure(&self, _py: Python, uri: &str, recursion_depth: i32) -> PyResult<Vec<String>> {
395 let iri = NamedNode::new(uri)
396 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
397 let inner = self.inner.clone();
398 let mut guard = inner.lock().unwrap();
399 let env = guard.as_mut().ok_or_else(|| {
400 PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
401 })?;
402 let graphid = env
403 .resolve(ResolveTarget::Graph(iri.clone()))
404 .ok_or_else(|| {
405 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
406 "Failed to resolve graph for URI: {uri}"
407 ))
408 })?;
409 let ont = env.ontologies().get(&graphid).ok_or_else(|| {
410 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Ontology {iri} not found"))
411 })?;
412 let closure = env
413 .get_closure(ont.id(), recursion_depth)
414 .map_err(anyhow_to_pyerr)?;
415 let names: Vec<String> = closure.iter().map(|ont| ont.to_uri_string()).collect();
416 Ok(names)
417 }
418
419 #[pyo3(signature = (uri, destination_graph=None, rewrite_sh_prefixes=true, remove_owl_imports=true, recursion_depth=-1))]
423 fn get_closure<'a>(
424 &self,
425 py: Python<'a>,
426 uri: &str,
427 destination_graph: Option<&Bound<'a, PyAny>>,
428 rewrite_sh_prefixes: bool,
429 remove_owl_imports: bool,
430 recursion_depth: i32,
431 ) -> PyResult<(Bound<'a, PyAny>, Vec<String>)> {
432 let rdflib = py.import("rdflib")?;
433 let iri = NamedNode::new(uri)
434 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
435 let inner = self.inner.clone();
436 let mut guard = inner.lock().unwrap();
437 let env = guard.as_mut().ok_or_else(|| {
438 PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
439 })?;
440 let graphid = env
441 .resolve(ResolveTarget::Graph(iri.clone()))
442 .ok_or_else(|| {
443 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
444 "No graph with URI: {uri}"
445 ))
446 })?;
447 let ont = env.ontologies().get(&graphid).ok_or_else(|| {
448 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Ontology {iri} not found"))
449 })?;
450 let closure = env
451 .get_closure(ont.id(), recursion_depth)
452 .map_err(anyhow_to_pyerr)?;
453 let closure_names: Vec<String> = closure.iter().map(|ont| ont.to_uri_string()).collect();
454 let destination_graph = match destination_graph {
456 Some(g) => g.clone(),
457 None => rdflib.getattr("Graph")?.call0()?,
458 };
459 let union = env
460 .get_union_graph(
461 &closure,
462 Some(rewrite_sh_prefixes),
463 Some(remove_owl_imports),
464 )
465 .map_err(anyhow_to_pyerr)?;
466 for triple in union.dataset.into_iter() {
467 let s: Term = triple.subject.into();
468 let p: Term = triple.predicate.into();
469 let o: Term = triple.object.into();
470 let t = PyTuple::new(
471 py,
472 &[
473 term_to_python(py, &rdflib, s)?,
474 term_to_python(py, &rdflib, p)?,
475 term_to_python(py, &rdflib, o)?,
476 ],
477 )?;
478 destination_graph.getattr("add")?.call1((t,))?;
479 }
480
481 if remove_owl_imports {
483 for graphid in union.graph_ids {
484 let iri = term_to_python(py, &rdflib, Term::NamedNode(graphid.into()))?;
485 let pred = term_to_python(py, &rdflib, IMPORTS.into())?;
486 let remove_tuple = PyTuple::new(py, &[py.None(), pred.into(), iri.into()])?;
488 destination_graph
489 .getattr("remove")?
490 .call1((remove_tuple,))?;
491 }
492 }
493 Ok((destination_graph, closure_names))
494 }
495
496 #[pyo3(signature = (includes=None))]
498 fn dump(&self, _py: Python, includes: Option<String>) -> PyResult<()> {
499 let inner = self.inner.clone();
500 let guard = inner.lock().unwrap();
501 if let Some(env) = guard.as_ref() {
502 env.dump(includes.as_deref());
503 Ok(())
504 } else {
505 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
506 "OntoEnv is closed",
507 ))
508 }
509 }
510
511 #[pyo3(signature = (graph, recursion_depth=-1, fetch_missing=false))]
514 fn import_dependencies<'a>(
515 &self,
516 py: Python<'a>,
517 graph: &Bound<'a, PyAny>,
518 recursion_depth: i32,
519 fetch_missing: bool,
520 ) -> PyResult<Vec<String>> {
521 let rdflib = py.import("rdflib")?;
522 let py_imports_pred = term_to_python(py, &rdflib, Term::NamedNode(IMPORTS.into()))?;
523
524 let kwargs = [("predicate", py_imports_pred)].into_py_dict(py)?;
525 let objects_iter = graph.call_method("objects", (), Some(&kwargs))?;
526 let builtins = py.import("builtins")?;
527 let objects_list = builtins.getattr("list")?.call1((objects_iter,))?;
528 let imports: Vec<String> = objects_list.extract()?;
529
530 if imports.is_empty() {
531 return Ok(Vec::new());
532 }
533
534 let inner = self.inner.clone();
535 let mut guard = inner.lock().unwrap();
536 let env = guard.as_mut().ok_or_else(|| {
537 PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
538 })?;
539
540 let is_strict = env.is_strict();
541 let mut all_ontologies = HashSet::new();
542 let mut all_closure_names: Vec<String> = Vec::new();
543
544 for uri in &imports {
545 let iri = NamedNode::new(uri.as_str())
546 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
547
548 let mut graphid = env.resolve(ResolveTarget::Graph(iri.clone()));
549
550 if graphid.is_none() && fetch_missing {
551 let location =
552 OntologyLocation::from_str(uri.as_str()).map_err(anyhow_to_pyerr)?;
553 match env.add(location, false) {
554 Ok(new_id) => {
555 graphid = Some(new_id);
556 }
557 Err(e) => {
558 if is_strict {
559 return Err(anyhow_to_pyerr(e));
560 }
561 println!("Failed to fetch {uri}: {e}");
562 }
563 }
564 }
565
566 let graphid = match graphid {
567 Some(id) => id,
568 None => {
569 if is_strict {
570 return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
571 "Failed to resolve graph for URI: {}",
572 uri
573 )));
574 }
575 println!("could not find {uri:?}");
576 continue;
577 }
578 };
579
580 let ont = env.ontologies().get(&graphid).ok_or_else(|| {
581 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
582 "Ontology {} not found",
583 uri
584 ))
585 })?;
586
587 let closure = env
588 .get_closure(ont.id(), recursion_depth)
589 .map_err(anyhow_to_pyerr)?;
590 for c_ont in closure {
591 all_closure_names.push(c_ont.to_uri_string());
592 all_ontologies.insert(c_ont.clone());
593 }
594 }
595
596 if all_ontologies.is_empty() {
597 return Ok(Vec::new());
598 }
599
600 let union = env
601 .get_union_graph(&all_ontologies, Some(true), Some(true))
602 .map_err(anyhow_to_pyerr)?;
603
604 for triple in union.dataset.into_iter() {
605 let s: Term = triple.subject.into();
606 let p: Term = triple.predicate.into();
607 let o: Term = triple.object.into();
608 let t = PyTuple::new(
609 py,
610 &[
611 term_to_python(py, &rdflib, s)?,
612 term_to_python(py, &rdflib, p)?,
613 term_to_python(py, &rdflib, o)?,
614 ],
615 )?;
616 graph.getattr("add")?.call1((t,))?;
617 }
618
619 let py_imports_pred_for_remove = term_to_python(py, &rdflib, IMPORTS.into())?;
621 let remove_tuple =
622 PyTuple::new(py, &[py.None(), py_imports_pred_for_remove.into(), py.None()])?;
623 graph.getattr("remove")?.call1((remove_tuple,))?;
624
625 all_closure_names.sort();
626 all_closure_names.dedup();
627
628 Ok(all_closure_names)
629 }
630
631 #[pyo3(signature = (location, overwrite = false, fetch_imports = true))]
633 fn add(
634 &self,
635 location: &Bound<'_, PyAny>,
636 overwrite: bool,
637 fetch_imports: bool,
638 ) -> PyResult<String> {
639 let inner = self.inner.clone();
640 let mut guard = inner.lock().unwrap();
641 let env = guard.as_mut().ok_or_else(|| {
642 PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
643 })?;
644
645 let location =
646 OntologyLocation::from_str(&location.to_string()).map_err(anyhow_to_pyerr)?;
647 let graph_id = if fetch_imports {
648 env.add(location, overwrite)
649 } else {
650 env.add_no_imports(location, overwrite)
651 }
652 .map_err(anyhow_to_pyerr)?;
653 Ok(graph_id.to_uri_string())
654 }
655
656 #[pyo3(signature = (location, overwrite = false))]
658 fn add_no_imports(&self, location: &Bound<'_, PyAny>, overwrite: bool) -> PyResult<String> {
659 let inner = self.inner.clone();
660 let mut guard = inner.lock().unwrap();
661 let env = guard.as_mut().ok_or_else(|| {
662 PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
663 })?;
664 let location =
665 OntologyLocation::from_str(&location.to_string()).map_err(anyhow_to_pyerr)?;
666 let graph_id = env
667 .add_no_imports(location, overwrite)
668 .map_err(anyhow_to_pyerr)?;
669 Ok(graph_id.to_uri_string())
670 }
671
672
673 fn get_importers(&self, uri: &str) -> PyResult<Vec<String>> {
675 let iri = NamedNode::new(uri)
676 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
677 let inner = self.inner.clone();
678 let guard = inner.lock().unwrap();
679 let env = guard.as_ref().ok_or_else(|| {
680 PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
681 })?;
682 let importers = env.get_importers(&iri).map_err(anyhow_to_pyerr)?;
683 let names: Vec<String> = importers.iter().map(|ont| ont.to_uri_string()).collect();
684 Ok(names)
685 }
686
687 fn get_ontology(&self, uri: &str) -> PyResult<PyOntology> {
689 let iri = NamedNode::new(uri)
690 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
691 let inner = self.inner.clone();
692 let guard = inner.lock().unwrap();
693 let env = guard.as_ref().ok_or_else(|| {
694 PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
695 })?;
696 let graphid = env
697 .resolve(ResolveTarget::Graph(iri.clone()))
698 .ok_or_else(|| {
699 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
700 "Failed to resolve graph for URI: {uri}"
701 ))
702 })?;
703 let ont = env.get_ontology(&graphid).map_err(anyhow_to_pyerr)?;
704 Ok(PyOntology { inner: ont })
705 }
706
707 fn get_graph(&self, py: Python, uri: &Bound<'_, PyString>) -> PyResult<Py<PyAny>> {
709 let rdflib = py.import("rdflib")?;
710 let iri = NamedNode::new(uri.to_string())
711 .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
712 let graph = {
713 let inner = self.inner.clone();
714 let guard = inner.lock().unwrap();
715 let env = guard.as_ref().ok_or_else(|| {
716 PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
717 })?;
718 let graphid = env
719 .resolve(ResolveTarget::Graph(iri))
720 .ok_or_else(|| {
721 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
722 "Failed to resolve graph for URI: {uri}"
723 ))
724 })?;
725
726 env.get_graph(&graphid).map_err(anyhow_to_pyerr)?
727 };
728 let res = rdflib.getattr("Graph")?.call0()?;
729 for triple in graph.into_iter() {
730 let s: Term = triple.subject.into();
731 let p: Term = triple.predicate.into();
732 let o: Term = triple.object.into();
733
734 let t = PyTuple::new(
735 py,
736 &[
737 term_to_python(py, &rdflib, s)?,
738 term_to_python(py, &rdflib, p)?,
739 term_to_python(py, &rdflib, o)?,
740 ],
741 )?;
742
743 res.getattr("add")?.call1((t,))?;
744 }
745 Ok(res.into())
746 }
747
748 fn get_ontology_names(&self) -> PyResult<Vec<String>> {
750 let inner = self.inner.clone();
751 let guard = inner.lock().unwrap();
752 let env = guard.as_ref().ok_or_else(|| {
753 PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
754 })?;
755 let names: Vec<String> = env
756 .ontologies()
757 .keys()
758 .map(|k| k.to_uri_string())
759 .collect();
760 Ok(names)
761 }
762
763 fn to_rdflib_dataset(&self, py: Python) -> PyResult<Py<PyAny>> {
765 let inner = self.inner.clone();
767 let guard = inner.lock().unwrap();
768 let env = guard.as_ref().ok_or_else(|| {
769 PyErr::new::<pyo3::exceptions::PyValueError, _>("OntoEnv is closed")
770 })?;
771 let rdflib = py.import("rdflib")?;
772 let dataset = rdflib.getattr("Dataset")?;
773
774 let kwargs = [("store", "Oxigraph")].into_py_dict(py)?;
776 let store = dataset.call((), Some(&kwargs))?;
777 let path = env.store_path().unwrap();
778 store.getattr("open")?.call1((path,))?;
779 Ok(store.into())
780 }
781
782 fn is_offline(&self) -> PyResult<bool> {
784 let inner = self.inner.clone();
785 let guard = inner.lock().unwrap();
786 if let Some(env) = guard.as_ref() {
787 Ok(env.is_offline())
788 } else {
789 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
790 "OntoEnv is closed",
791 ))
792 }
793 }
794
795 fn set_offline(&mut self, offline: bool) -> PyResult<()> {
796 let inner = self.inner.clone();
797 let mut guard = inner.lock().unwrap();
798 if let Some(env) = guard.as_mut() {
799 env.set_offline(offline);
800 env.save_to_directory().map_err(anyhow_to_pyerr)
801 } else {
802 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
803 "OntoEnv is closed",
804 ))
805 }
806 }
807
808 fn is_strict(&self) -> PyResult<bool> {
809 let inner = self.inner.clone();
810 let guard = inner.lock().unwrap();
811 if let Some(env) = guard.as_ref() {
812 Ok(env.is_strict())
813 } else {
814 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
815 "OntoEnv is closed",
816 ))
817 }
818 }
819
820 fn set_strict(&mut self, strict: bool) -> PyResult<()> {
821 let inner = self.inner.clone();
822 let mut guard = inner.lock().unwrap();
823 if let Some(env) = guard.as_mut() {
824 env.set_strict(strict);
825 env.save_to_directory().map_err(anyhow_to_pyerr)
826 } else {
827 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
828 "OntoEnv is closed",
829 ))
830 }
831 }
832
833 fn requires_ontology_names(&self) -> PyResult<bool> {
834 let inner = self.inner.clone();
835 let guard = inner.lock().unwrap();
836 if let Some(env) = guard.as_ref() {
837 Ok(env.requires_ontology_names())
838 } else {
839 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
840 "OntoEnv is closed",
841 ))
842 }
843 }
844
845 fn set_require_ontology_names(&mut self, require: bool) -> PyResult<()> {
846 let inner = self.inner.clone();
847 let mut guard = inner.lock().unwrap();
848 if let Some(env) = guard.as_mut() {
849 env.set_require_ontology_names(require);
850 env.save_to_directory().map_err(anyhow_to_pyerr)
851 } else {
852 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
853 "OntoEnv is closed",
854 ))
855 }
856 }
857
858 fn no_search(&self) -> PyResult<bool> {
859 let inner = self.inner.clone();
860 let guard = inner.lock().unwrap();
861 if let Some(env) = guard.as_ref() {
862 Ok(env.no_search())
863 } else {
864 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
865 "OntoEnv is closed",
866 ))
867 }
868 }
869
870 fn set_no_search(&mut self, no_search: bool) -> PyResult<()> {
871 let inner = self.inner.clone();
872 let mut guard = inner.lock().unwrap();
873 if let Some(env) = guard.as_mut() {
874 env.set_no_search(no_search);
875 env.save_to_directory().map_err(anyhow_to_pyerr)
876 } else {
877 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
878 "OntoEnv is closed",
879 ))
880 }
881 }
882
883 fn resolution_policy(&self) -> PyResult<String> {
884 let inner = self.inner.clone();
885 let guard = inner.lock().unwrap();
886 if let Some(env) = guard.as_ref() {
887 Ok(env.resolution_policy().to_string())
888 } else {
889 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
890 "OntoEnv is closed",
891 ))
892 }
893 }
894
895 fn set_resolution_policy(&mut self, policy: String) -> PyResult<()> {
896 let inner = self.inner.clone();
897 let mut guard = inner.lock().unwrap();
898 if let Some(env) = guard.as_mut() {
899 env.set_resolution_policy(policy);
900 env.save_to_directory().map_err(anyhow_to_pyerr)
901 } else {
902 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
903 "OntoEnv is closed",
904 ))
905 }
906 }
907
908 pub fn store_path(&self) -> PyResult<Option<String>> {
909 let inner = self.inner.clone();
910 let guard = inner.lock().unwrap();
911 if let Some(env) = guard.as_ref() {
912 match env.store_path() {
913 Some(path) => Ok(Some(path.to_string_lossy().to_string())),
914 None => Ok(None), }
916 } else {
917 Ok(None)
918 }
919 }
920
921 pub fn close(&mut self, py: Python<'_>) -> PyResult<()> {
926 py.allow_threads(|| {
927 let inner = self.inner.clone();
928 let mut guard = inner.lock().unwrap();
929 if let Some(env) = guard.as_mut() {
930 env.save_to_directory().map_err(anyhow_to_pyerr)?;
931 env.flush().map_err(anyhow_to_pyerr)?;
932 }
933 *guard = None;
934 Ok(())
935 })
936 }
937
938 pub fn flush(&mut self, py: Python<'_>) -> PyResult<()> {
939 py.allow_threads(|| {
940 let inner = self.inner.clone();
941 let mut guard = inner.lock().unwrap();
942 if let Some(env) = guard.as_mut() {
943 env.flush().map_err(anyhow_to_pyerr)
944 } else {
945 Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
946 "OntoEnv is closed",
947 ))
948 }
949 })
950 }
951}
952
953#[pymodule]
954fn ontoenv(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
955 m.add_class::<Config>()?;
956 m.add_class::<OntoEnv>()?;
957 m.add_class::<PyOntology>()?;
958 m.add("version", env!("CARGO_PKG_VERSION"))?;
960 Ok(())
961}