Skip to content

Commit aa59acf

Browse files
committed
Adding documentation
1 parent 0e7f385 commit aa59acf

File tree

4 files changed

+215
-53
lines changed

4 files changed

+215
-53
lines changed

README.md

Lines changed: 83 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -129,38 +129,58 @@ If GraphViz is installed, `ontoenv dep-graph` will output a PDF graph representa
129129

130130
#### Usage
131131

132+
Here is a basic example of how to use the `pyontoenv` Python library. This example will:
133+
1. Create a temporary directory.
134+
2. Write two simple ontologies to files in that directory, where one imports the other.
135+
3. Configure and initialize `ontoenv` to use this directory.
136+
4. Compute the dependency closure of one ontology to demonstrate that `ontoenv` correctly resolves and includes the imported ontology.
137+
132138
```python
139+
import tempfile
140+
from pathlib import Path
133141
from ontoenv import Config, OntoEnv
134142
from rdflib import Graph
135143

136-
# create config object. This assumes you have a 'brick' folder locally storing some ontologies
137-
cfg = Config(search_directories=["brick"], strict=False, offline=True)
138-
# can also create an 'empty' config object if there are no local ontologies
139-
# cfg = Config(strict=False, offline=True)
140-
141-
# make the environment
142-
env = OntoEnv(config=cfg)
143-
144-
# compute closure for a given ontology and insert it into a graph
145-
g = Graph()
146-
env.get_closure("https://brickschema.org/schema/1.4/Brick", destination_graph=g)
147-
148-
# import all dependencies from a graph
149-
brick = Graph()
150-
brick.parse("brick/Brick.ttl", format="turtle")
151-
env.import_dependencies(brick)
152-
153-
# get a graph by IRI
154-
rec = env.get_graph("https://w3id.org/rec")
155-
156-
# add an ontology to a graph by IRI
157-
env.import_graph(brick, "https://w3id.org/rec")
144+
# create a temporary directory to store our ontology files
145+
with tempfile.TemporaryDirectory() as temp_dir:
146+
root = Path(temp_dir)
147+
# create a dummy ontology file for ontology A
148+
ontology_a_content = """
149+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
150+
@prefix : <http://example.com/ontology_a#> .
151+
<http://example.com/ontology_a> a owl:Ontology .
152+
"""
153+
(root / "ontology_a.ttl").write_text(ontology_a_content)
158154

159-
# get an rdflib.Dataset (https://rdflib.readthedocs.io/en/stable/apidocs/rdflib.html#rdflib.Dataset)
160-
ds = env.to_rdflib_dataset()
161-
for graphname in ds.graphs():
162-
graph = ds.graph(graphname)
163-
print(f"Graph {graphname} has {len(graph)} triples")
155+
# create a dummy ontology file for ontology B which imports A
156+
ontology_b_content = """
157+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
158+
@prefix : <http://example.com/ontology_b#> .
159+
<http://example.com/ontology_b> a owl:Ontology ;
160+
owl:imports <http://example.com/ontology_a> .
161+
"""
162+
(root / "ontology_b.ttl").write_text(ontology_b_content)
163+
164+
# create config object. We use temporary=True so it doesn't create a .ontoenv dir
165+
cfg = Config(search_directories=[str(root)], strict=False, offline=True, temporary=True)
166+
# make the environment
167+
env = OntoEnv(config=cfg)
168+
169+
# list the ontologies found
170+
print("Ontologies found:", env.get_ontology_names())
171+
172+
# compute closure for ontology B and insert it into a graph
173+
g = Graph()
174+
env.get_closure("http://example.com/ontology_b", destination_graph=g)
175+
# The closure should contain triples from both A and B.
176+
# Each ontology has 1 triple, so the union should have 2.
177+
print(f"Closure of ontology_b has {len(g)} triples")
178+
assert len(g) == 2
179+
180+
# get just the graph for ontology A
181+
g_a = env.get_graph("http://example.com/ontology_a")
182+
print(f"Graph of ontology_a has {len(g_a)} triples")
183+
assert len(g_a) == 1
164184
```
165185

166186
## Rust Library
@@ -171,16 +191,19 @@ for graphname in ds.graphs():
171191

172192
Here is a basic example of how to use the `ontoenv` Rust library. This example will:
173193
1. Create a temporary directory.
174-
2. Write a simple ontology to a file in that directory.
194+
2. Write two simple ontologies to files in that directory, where one imports the other.
175195
3. Configure and initialize `ontoenv` to use this directory.
176-
4. Verify that the ontology has been loaded correctly.
196+
4. Compute the dependency closure of one ontology to demonstrate that `ontoenv` correctly resolves and includes the imported ontology.
177197

178198
```rust
179199
use ontoenv::config::Config;
180-
use ontoenv::api::OntoEnv;
200+
use ontoenv::api::{OntoEnv, ResolveTarget};
201+
use ontoenv::ToUriString;
202+
use oxigraph::model::NamedNode;
181203
use std::path::PathBuf;
182204
use std::fs;
183205
use std::io::Write;
206+
use std::collections::HashSet;
184207

185208
# fn main() -> anyhow::Result<()> {
186209
// Set up a temporary directory for the example
@@ -191,14 +214,23 @@ if test_dir.exists() {
191214
fs::create_dir_all(&test_dir)?;
192215
let root = test_dir.canonicalize()?;
193216

194-
// Create a dummy ontology file
195-
let ontology_path = root.join("my_ontology.ttl");
196-
let mut file = fs::File::create(&ontology_path)?;
197-
writeln!(file, r#"
217+
// Create a dummy ontology file for ontology A
218+
let ontology_a_path = root.join("ontology_a.ttl");
219+
let mut file_a = fs::File::create(&ontology_a_path)?;
220+
writeln!(file_a, r#"
198221
@prefix owl: <http://www.w3.org/2002/07/owl#> .
199-
@prefix : <http://example.com/my_ontology#> .
222+
@prefix : <http://example.com/ontology_a#> .
223+
<http://example.com/ontology_a> a owl:Ontology .
224+
"#)?;
200225

201-
<http://example.com/my_ontology> a owl:Ontology .
226+
// Create a dummy ontology file for ontology B which imports A
227+
let ontology_b_path = root.join("ontology_b.ttl");
228+
let mut file_b = fs::File::create(&ontology_b_path)?;
229+
writeln!(file_b, r#"
230+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
231+
@prefix : <http://example.com/ontology_b#> .
232+
<http://example.com/ontology_b> a owl:Ontology ;
233+
owl:imports <http://example.com/ontology_a> .
202234
"#)?;
203235

204236
// Configure ontoenv
@@ -212,11 +244,22 @@ let config = Config::builder()
212244
let mut env = OntoEnv::init(config, false)?;
213245
env.update()?;
214246

215-
// Check that our ontology was loaded
247+
// Check that our ontologies were loaded
216248
let ontologies = env.ontologies();
217-
assert_eq!(ontologies.len(), 1);
218-
let ont_name = ontologies.keys().next().unwrap().name();
219-
assert_eq!(ont_name.as_str(), "http://example.com/my_ontology");
249+
assert_eq!(ontologies.len(), 2);
250+
251+
// Get the dependency closure for ontology B
252+
let ont_b_name = NamedNode::new("http://example.com/ontology_b")?;
253+
let ont_b_id = env.resolve(ResolveTarget::Graph(ont_b_name)).unwrap();
254+
let closure = env.get_dependency_closure(&ont_b_id)?;
255+
256+
// The closure should contain both ontology A and B
257+
assert_eq!(closure.len(), 2);
258+
let closure_names: HashSet<String> = closure.iter().map(|id| id.to_uri_string()).collect();
259+
println!("Closure contains: {:?}", closure_names);
260+
assert!(closure_names.contains("http://example.com/ontology_a"));
261+
assert!(closure_names.contains("http://example.com/ontology_b"));
262+
220263

221264
// Clean up
222265
fs::remove_dir_all(&test_dir)?;

cli/src/main.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use ontoenv::api::{OntoEnv, ResolveTarget};
55
use ontoenv::config::Config;
66
use ontoenv::ontology::{GraphIdentifier, OntologyLocation};
77
use ontoenv::util::write_dataset_to_file;
8+
use ontoenv::ToUriString;
89
use oxigraph::model::NamedNode;
910
use std::env::current_dir;
1011
use std::path::PathBuf;
@@ -335,14 +336,14 @@ fn main() -> Result<()> {
335336
ontologies.sort_by(|a, b| a.name().cmp(&b.name()));
336337
ontologies.dedup_by(|a, b| a.name() == b.name());
337338
for ont in ontologies {
338-
println!("{}", ont.name().as_str());
339+
println!("{}", ont.to_uri_string());
339340
}
340341
}
341342
ListCommands::Missing => {
342343
let mut missing_imports = env.missing_imports();
343344
missing_imports.sort();
344345
for import in missing_imports {
345-
println!("{}", import);
346+
println!("{}", import.to_uri_string());
346347
}
347348
}
348349
}
@@ -385,9 +386,9 @@ fn main() -> Result<()> {
385386
for ont in ontologies {
386387
let iri = NamedNode::new(ont).map_err(|e| anyhow::anyhow!(e.to_string()))?;
387388
let dependents = env.get_dependents(&iri)?;
388-
println!("Dependents of {iri}: ");
389+
println!("Dependents of {}: ", iri.to_uri_string());
389390
for dep in dependents {
390-
println!("{dep}");
391+
println!("{}", dep.to_uri_string());
391392
}
392393
}
393394
}

lib/src/lib.rs

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,93 @@
1-
#[doc = include_str!("../../README.md")]
1+
//! `ontoenv` is an environment manager for ontologies. It can be used as a Rust library to manage local and remote RDF ontologies and their dependencies.
2+
//!
3+
//! It recursively discovers and resolves `owl:imports` statements, and provides an API for querying the dependency graph and retrieving a unified "imports closure" of an ontology.
4+
//!
5+
//! The environment is backed by an `Oxigraph` store.
6+
//!
7+
//! # Usage
8+
//!
9+
//! Here is a basic example of how to use the `ontoenv` Rust library. This example will:
10+
//! 1. Create a temporary directory.
11+
//! 2. Write two simple ontologies to files in that directory, where one imports the other.
12+
//! 3. Configure and initialize `ontoenv` to use this directory.
13+
//! 4. Compute the dependency closure of one ontology to demonstrate that `ontoenv` correctly resolves and includes the imported ontology.
14+
//!
15+
//! ```rust
16+
//! use ontoenv::config::Config;
17+
//! use ontoenv::ToUriString;
18+
//! use ontoenv::api::{OntoEnv, ResolveTarget};
19+
//! use oxigraph::model::NamedNode;
20+
//! use std::path::PathBuf;
21+
//! use std::fs;
22+
//! use std::io::Write;
23+
//! use std::collections::HashSet;
24+
//!
25+
//! # fn main() -> anyhow::Result<()> {
26+
//! // Set up a temporary directory for the example
27+
//! let test_dir = PathBuf::from("target/doc_test_temp_readme");
28+
//! if test_dir.exists() {
29+
//! fs::remove_dir_all(&test_dir)?;
30+
//! }
31+
//! fs::create_dir_all(&test_dir)?;
32+
//! let root = test_dir.canonicalize()?;
33+
//!
34+
//! // Create a dummy ontology file for ontology A
35+
//! let ontology_a_path = root.join("ontology_a.ttl");
36+
//! let mut file_a = fs::File::create(&ontology_a_path)?;
37+
//! writeln!(file_a, r#"
38+
//! @prefix owl: <http://www.w3.org/2002/07/owl#> .
39+
//! @prefix : <http://example.com/ontology_a#> .
40+
//! <http://example.com/ontology_a> a owl:Ontology .
41+
//! "#)?;
42+
//!
43+
//! // Create a dummy ontology file for ontology B which imports A
44+
//! let ontology_b_path = root.join("ontology_b.ttl");
45+
//! let mut file_b = fs::File::create(&ontology_b_path)?;
46+
//! writeln!(file_b, r#"
47+
//! @prefix owl: <http://www.w3.org/2002/07/owl#> .
48+
//! @prefix : <http://example.com/ontology_b#> .
49+
//! <http://example.com/ontology_b> a owl:Ontology ;
50+
//! owl:imports <http://example.com/ontology_a> .
51+
//! "#)?;
52+
//!
53+
//! // Configure ontoenv
54+
//! let config = Config::builder()
55+
//! .root(root.clone())
56+
//! .locations(vec![root.clone()])
57+
//! .temporary(true) // Use a temporary environment
58+
//! .build()?;
59+
//!
60+
//! // Initialize the environment
61+
//! let mut env = OntoEnv::init(config, false)?;
62+
//! env.update()?;
63+
//!
64+
//! // Check that our ontologies were loaded
65+
//! let ontologies = env.ontologies();
66+
//! assert_eq!(ontologies.len(), 2);
67+
//!
68+
//! // Get the dependency closure for ontology B
69+
//! let ont_b_name = NamedNode::new("http://example.com/ontology_b")?;
70+
//! let ont_b_id = env.resolve(ResolveTarget::Graph(ont_b_name)).unwrap();
71+
//! let closure_ids = env.get_dependency_closure(&ont_b_id)?;
72+
//!
73+
//! // The closure should contain both ontology A and B
74+
//! assert_eq!(closure_ids.len(), 2);
75+
//! let closure_names: HashSet<String> = closure_ids.iter().map(|id| id.to_uri_string()).collect();
76+
//! assert!(closure_names.contains("http://example.com/ontology_a"));
77+
//! assert!(closure_names.contains("http://example.com/ontology_b"));
78+
//!
79+
//! // We can also get the union graph of the closure
80+
//! let union_graph_result = env.get_union_graph(&closure_ids, Some(false), Some(false))?;
81+
//! // Each ontology has 1 triple, so the union should have 2.
82+
//! // the 'ontology_a' declaration gets removed by default so that the closure
83+
//! // only has one ontology declaration.
84+
//! assert_eq!(union_graph_result.dataset.len(), 2);
85+
//!
86+
//! // Clean up
87+
//! fs::remove_dir_all(&test_dir)?;
88+
//! # Ok(())
89+
//! # }
90+
//! ```
291
392
extern crate derive_builder;
493

@@ -21,6 +110,35 @@ use oxigraph::model::NamedNode;
21110
use pretty_bytes::converter::convert as pretty_bytes;
22111
use std::fmt::{self, Display};
23112

113+
pub trait ToUriString {
114+
fn to_uri_string(&self) -> String;
115+
}
116+
117+
impl ToUriString for NamedNode {
118+
fn to_uri_string(&self) -> String {
119+
self.as_str().to_string()
120+
}
121+
}
122+
123+
impl ToUriString for &NamedNode {
124+
fn to_uri_string(&self) -> String {
125+
self.as_str().to_string()
126+
}
127+
}
128+
129+
impl ToUriString for GraphIdentifier {
130+
fn to_uri_string(&self) -> String {
131+
self.name().as_str().to_string()
132+
}
133+
}
134+
135+
impl ToUriString for &GraphIdentifier {
136+
fn to_uri_string(&self) -> String {
137+
self.name().as_str().to_string()
138+
}
139+
}
140+
141+
24142
pub struct FailedImport {
25143
ontology: GraphIdentifier,
26144
error: String,
@@ -37,7 +155,8 @@ impl Display for FailedImport {
37155
write!(
38156
f,
39157
"Failed to import ontology {}: {}",
40-
self.ontology, self.error
158+
self.ontology.to_uri_string(),
159+
self.error
41160
)
42161
}
43162
}
@@ -84,7 +203,7 @@ impl std::fmt::Display for EnvironmentStatus {
84203
if !self.missing_imports.is_empty() {
85204
write!(f, "\n\nMissing Imports:")?;
86205
for import in &self.missing_imports {
87-
write!(f, "\n - {}", import)?;
206+
write!(f, "\n - {}", import.to_uri_string())?;
88207
}
89208
}
90209
Ok(())

python/src/lib.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
#[doc = include_str!("../../README.md")]
12
use ::ontoenv::api::{OntoEnv as OntoEnvRs, ResolveTarget};
23
use ::ontoenv::config;
34
use ::ontoenv::consts::{IMPORTS, ONTOLOGY, TYPE};
5+
use ::ontoenv::ToUriString;
46
use ::ontoenv::ontology::OntologyLocation;
57
use ::ontoenv::transform;
68
use anyhow::Error;
@@ -321,7 +323,7 @@ impl OntoEnv {
321323
let closure = env
322324
.get_dependency_closure(ont.id())
323325
.map_err(anyhow_to_pyerr)?;
324-
let names: Vec<String> = closure.iter().map(|ont| ont.name().to_string()).collect();
326+
let names: Vec<String> = closure.iter().map(|ont| ont.to_uri_string()).collect();
325327
Ok(names)
326328
}
327329

@@ -462,10 +464,7 @@ impl OntoEnv {
462464
let inner = self.inner.clone();
463465
let env = inner.lock().unwrap();
464466
let dependents = env.get_dependents(&iri).map_err(anyhow_to_pyerr)?;
465-
let names: Vec<String> = dependents
466-
.iter()
467-
.map(|ont| ont.name().to_string())
468-
.collect();
467+
let names: Vec<String> = dependents.iter().map(|ont| ont.to_uri_string()).collect();
469468
Ok(names)
470469
}
471470

@@ -515,7 +514,7 @@ impl OntoEnv {
515514
let names: Vec<String> = env
516515
.ontologies()
517516
.keys()
518-
.map(|k| k.name().to_string())
517+
.map(|k| k.to_uri_string())
519518
.collect();
520519
Ok(names)
521520
}

0 commit comments

Comments
 (0)