ontoenv is a lightweight environment manager for RDF ontologies and their imports. It helps you:
- Discover ontologies locally and on the web
- Resolve and materialize
owl:importsclosures - Query and export graphs
Components:
| Install | Docs | |
|---|---|---|
| CLI | cargo install --locked ontoenv-cli or pip install ontoenv |
This README |
| Python library | pip install ontoenv |
Python API |
| Rust library | cargo add ontoenv |
docs.rs |
# Install the CLI
cargo install --locked ontoenv-cli
# Initialize an environment in a directory containing ontology files
ontoenv init ./ontologies
# Compute the imports closure of an ontology and write it to a file
ontoenv closure http://example.org/ont/MyOntology result.ttlOr from Python:
from ontoenv import OntoEnv
from rdflib import Graph
env = OntoEnv(search_directories=["./ontologies"], strict=False, temporary=True)
g = Graph()
env.get_closure("http://example.org/ont/MyOntology", destination_graph=g)
print(f"Closure has {len(g)} triples")ontoenv looks for patterns like this in local ontology files:
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix : <urn:my_ontology/> .
<urn:my_ontology> a owl:Ontology ;
owl:imports <https://brickschema.org/schema/1.4/Brick>,
<http://qudt.org/2.1/vocab/quantitykind> .When initialized, ontoenv searches configured directories for ontology declarations, identifies their owl:imports, and recursively pulls in dependencies. The central operation is computing the imports closure: the set of all ontologies transitively required by a given root, optionally merged into a single flat graph.
Implementation: Built in Rust using oxigraph as the internal RDF store and petgraph for dependency graph traversal. Python bindings are generated via PyO3. Persistent on-disk state uses a compact binary format (RDF5D) at .ontoenv/store.r5tu with single-writer/shared-reader locking.
Design goals:
- Lightweight — usable from a Python library or CLI without a heavyweight GUI
- Configurable — control which files are local vs. remote, which IRIs to include/exclude
- Fast — quickly refresh a workspace after local file changes
Ontologies fetched from a URL often declare a different (usually versioned) ontology IRI inside the file. ontoenv remembers that relationship: when an ontology is added, the source URL is recorded and, if its declared name differs, an alias is created from the normalized URL to the canonical ontology IRI. Future owl:imports referencing the versionless URL reuse the already-downloaded ontology instead of re-fetching it.
cargo install --locked ontoenv-cli— from crates.iocargo install --path cli --locked— from a local checkoutpip install ontoenv— alongside Python bindings- Download a binary from the Releases tab
| Path | Purpose |
|---|---|
.ontoenv/ |
Environment directory |
.ontoenv/store.r5tu |
RDF5D persistent store |
.ontoenv/store.lock |
File lock (single writer, shared readers) |
Set ONTOENV_DIR to override the environment location. Logging is controlled via ONTOENV_LOG or RUST_LOG.
Initialize an ontoenv workspace. Must be run once per environment; all other commands auto-discover the nearest .ontoenv/ by walking up from the current directory.
Usage: ontoenv init [OPTIONS] [LOCATION]...
Arguments:
[LOCATION]... Directories to search for ontologies. If omitted, no
directories are scanned; add ontologies manually or
rerun init with explicit paths.
Options:
--overwrite Overwrite an existing environment
-r, --require-ontology-names Raise an error if multiple ontologies share the same name
-s, --strict Raise an error if an import is not found
-o, --offline Do not fetch ontologies from the web
-p, --policy <POLICY> Resolution policy: 'default', 'latest', or 'version' [default: default]
-i, --includes <INCLUDES>... Glob patterns to include [default: *.ttl *.xml *.n3]
-e, --excludes <EXCLUDES>... Glob patterns to exclude
--include-ontology, --io <REGEX> Regex patterns of ontology IRIs to include
--exclude-ontology, --eo <REGEX> Regex patterns of ontology IRIs to exclude
-h, --help Print help
Examples:
ontoenv init . # scan current directory
ontoenv init ./ontologies ./models # scan multiple directories
ontoenv init # empty environment, add ontologies manually
ontoenv init --overwrite --offline ./ontologies # rebuild from scratch, offlineOffline mode is particularly useful when you want to limit which ontologies are loaded: download the ones you want, then enable --offline to prevent any further network access.
Refresh the environment based on file timestamps and configuration.
ontoenv update # refresh only changed/added files
ontoenv update --all # rebuild the in-memory view from all sourcesTo change search paths or flags, re-run ontoenv init.
Compute and write the imports closure (union of a graph and its transitive imports).
ontoenv closure http://example.org/ont/MyOntology # writes output.ttl
ontoenv closure http://example.org/ont/MyOntology result.ttl # writes result.ttl
ontoenv closure http://example.org/ont/MyOntology --no-rewrite-sh-prefixes
ontoenv closure http://example.org/ont/MyOntology --keep-owl-importsBy default, SHACL sh:prefixes references are rewritten onto the root node and owl:imports triples are removed from the output.
Retrieve a single ontology graph from the environment.
ontoenv get http://example.org/ont/MyOntology # Turtle to STDOUT
ontoenv get http://example.org/ont/MyOntology --format jsonld # JSON-LD to STDOUT
ontoenv get http://example.org/ont/MyOntology --output my.ttl # write to file
# Disambiguate when multiple sources share the same IRI
ontoenv get http://example.org/ont/MyOntology --location ./ontologies/MyOntology-1.4.ttlSupported formats: turtle (default), ntriples, rdfxml, jsonld.
| Command | Description |
|---|---|
ontoenv status [--json] |
Human-friendly environment status |
ontoenv dump |
Show ontologies, imports, sizes, and metadata |
ontoenv list ontologies [--json] |
List ontology names in the environment |
ontoenv list missing [--json] |
List imports not found in the environment |
ontoenv dep-graph |
Export a Graphviz import dependency graph (PDF); requires Graphviz |
ontoenv why <IRI>... [--json] |
Show which ontologies import the given IRI, as path(s) |
pip install ontoenv # Python 3.11+; prebuilt wheels for common platformsBuilding from source requires a Rust toolchain (MSRV 1.70).
import tempfile
from pathlib import Path
from ontoenv import OntoEnv
from rdflib import Graph
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
(root / "ontology_a.ttl").write_text("""
@prefix owl: <http://www.w3.org/2002/07/owl#> .
<http://example.com/ontology_a> a owl:Ontology .
""")
(root / "ontology_b.ttl").write_text("""
@prefix owl: <http://www.w3.org/2002/07/owl#> .
<http://example.com/ontology_b> a owl:Ontology ;
owl:imports <http://example.com/ontology_a> .
""")
env = OntoEnv(search_directories=[str(root)], strict=False, offline=True, temporary=True)
print("Ontologies found:", env.get_ontology_names())
g = Graph()
env.get_closure("http://example.com/ontology_b", destination_graph=g)
print(f"Closure of ontology_b has {len(g)} triples") # → 2OntoEnv(
path=None,
recreate=False,
create_or_use_cached=False,
read_only=False,
search_directories=None, # pass ["."] to scan immediately; None skips discovery
require_ontology_names=False,
strict=False,
offline=False,
use_cached_ontologies=False,
resolution_policy="default",
root=".",
includes=None, # gitignore-style globs; bare dirs match everything under them
excludes=None,
include_ontologies=None, # regex filters on ontology IRIs; excludes run after includes
exclude_ontologies=None,
temporary=False, # in-memory only, no .ontoenv/
remote_cache_ttl_secs=None, # max age before re-fetching a remote ontology (default: 86400)
)Environment modes:
temporary=True— in-memory only, no.ontoenv/recreate=True— explicitly create (or overwrite) atpathcreate_or_use_cached=True— bootstrap a new environment if none is found, otherwise reuse existing- Default — walk up from
path(orroot) to find an existing.ontoenv/; raiseFileNotFoundErrorif not found
| Method | Root identified by | Mutates input? | Returns |
|---|---|---|---|
get_closure(name, ...) |
IRI string | No | (Graph, list[str]) |
import_graph(destination_graph, name, ...) |
IRI string | Yes (required) | None |
import_dependencies(graph, ...) |
owl:imports in caller's graph |
Yes | list[str] |
get_dependencies(graph, ...) |
owl:imports in caller's graph |
No | (Graph, list[str]) |
list_closure(name, ...) |
IRI string | — | list[str] (IRIs only) |
get_closure(uri, destination_graph=None, rewrite_sh_prefixes=True, remove_owl_imports=True, recursion_depth=-1)
Compute the full closure by IRI. sh:prefixes blocks are consolidated onto uri and owl:imports removed by default. Returns (merged_graph, closure_iris). Use when you have an IRI and want a self-contained graph for reasoning, exchange, or export.
import_dependencies(graph, fetch_missing=False)
Mutates the provided graph in-place. Reads its owl:imports statements, resolves each one transitively, merges all closure triples into the same graph, removes owl:imports, and rewrites sh:prefixes onto the graph's root. Returns list[str] of imported IRIs.
get_dependencies(graph, graph_name=None, fetch_missing=False)
Same closure as import_dependencies but never modifies the original graph. Returns a new graph. Without graph_name, each ontology retains its own owl:Ontology declaration. With graph_name, all declarations are collapsed onto that single IRI and sh:prefixes are rewritten onto it. Returns (deps_graph, list[str]).
| Method | Description |
|---|---|
update(all=False) |
Refresh discovered ontologies |
add(location, fetch_imports=True) -> str |
Add ontology by file path, URL, or rdflib.Graph; returns IRI |
add_no_imports(location) -> str |
Same as add, but skips import traversal |
get_graph(name) -> Graph |
Retrieve a single ontology (no closure expansion) |
get_ontology(name) |
Inspect metadata: imports list, version, namespace map, last-updated |
get_importers(name) -> list[str] |
Reverse dependency lookup |
get_namespaces(name, include_closure=False) |
Aggregated prefix-to-IRI mappings |
to_rdflib_dataset() -> rdflib.Dataset |
Export full environment as a named-graph Dataset |
store_path() -> str | None |
Path to .ontoenv/, or None for temporary environments |
close() |
Persist (if applicable) and release resources |
[dependencies]
ontoenv = "0.5"Requires Rust 1.70+.
use ontoenv::config::Config;
use ontoenv::api::{OntoEnv, ResolveTarget};
use ontoenv::ToUriString;
use oxigraph::model::NamedNode;
use std::path::PathBuf;
use std::fs;
use std::io::Write;
use std::collections::HashSet;
# fn main() -> anyhow::Result<()> {
let test_dir = PathBuf::from("target/doc_test_temp_readme");
if test_dir.exists() { fs::remove_dir_all(&test_dir)?; }
fs::create_dir_all(&test_dir)?;
let root = test_dir.canonicalize()?;
let mut file_a = fs::File::create(root.join("ontology_a.ttl"))?;
writeln!(file_a, r#"
@prefix owl: <http://www.w3.org/2002/07/owl#> .
<http://example.com/ontology_a> a owl:Ontology .
"#)?;
let mut file_b = fs::File::create(root.join("ontology_b.ttl"))?;
writeln!(file_b, r#"
@prefix owl: <http://www.w3.org/2002/07/owl#> .
<http://example.com/ontology_b> a owl:Ontology ;
owl:imports <http://example.com/ontology_a> .
"#)?;
let config = Config::builder()
.root(root.clone())
.locations(vec![root.clone()])
.temporary(true)
.build()?;
let mut env = OntoEnv::init(config, false)?;
env.update()?;
let ont_b_name = NamedNode::new("http://example.com/ontology_b")?;
let ont_b_id = env.resolve(ResolveTarget::Graph(ont_b_name)).unwrap();
let closure_ids = env.get_closure(&ont_b_id, -1)?;
assert_eq!(closure_ids.len(), 2);
fs::remove_dir_all(&test_dir)?;
# Ok(())
# }| Method | Description |
|---|---|
OntoEnv::init(config, overwrite) |
Create (or overwrite) an environment |
OntoEnv::load_from_directory(root, read_only) |
Load an existing environment |
find_ontoenv_root() / find_ontoenv_root_from(path) |
Locate the nearest .ontoenv/ by walking up |
env.update_all(all) |
Refresh discovered ontologies |
env.add(location, Overwrite, RefreshStrategy) |
Add by file/URL |
env.add_no_imports(location, Overwrite, RefreshStrategy) |
Add without import traversal |
env.add_from_bytes(location, bytes, format, Overwrite, RefreshStrategy) |
Add from in-memory RDF bytes |
env.get_graph(id) |
Retrieve a single ontology |
env.get_union_graph(ids) |
Merge multiple ontology graphs |
env.get_closure(id, recursion_depth) |
Compute transitive imports closure |
env.save_to_directory() / env.flush() |
Persist to .ontoenv/store.r5tu |
| Enum | Variants | Meaning |
|---|---|---|
Overwrite |
Allow, Preserve |
Replace existing graphs or keep original |
RefreshStrategy |
Force, UseCache |
Bypass or reuse cached ontologies |
CacheMode |
Enabled, Disabled |
Mirrored in Python as use_cached_ontologies |
bool values still convert via Into for backward compatibility.
Prerequisites: Rust 1.70+, Python 3.11+ (for Python bindings)
cargo build --workspace --release # all crates (lib/, cli/, rdf5d/)
cargo test --workspace # all Rust tests
./test # Rust + Python test suitescd python
uv run --group dev maturin develop # editable dev install
uv run python -m unittest discover -s tests # run Python tests
uv run --group dev maturin build --release # wheels → python/target/wheels/cd pyontoenv-shim
uv build --wheel
uv run python -m unittest discover -s tests
uv run python scripts/test_pyontoenv.py # end-to-end install sanity checkThis exists because I have both ontoenv and pyontoenv on PyPI — a mistake from when I restarted this project and forgot I already had the other package name.
./version <new-version> # syncs all Cargo.toml files and Python pyproject.toml