Concepts¶
Ontologies are identified by IRI, not by location¶
An RDF ontology declares its own name:
<https://example.org/site> a owl:Ontology .
That IRI is the ontology’s identity. It is not a URL you are promised to be able to fetch, and it need not resemble the path of the file containing it. Two copies of the same ontology in different directories declare the same IRI; a file renamed on disk still declares the same IRI.
Imports are written in the same currency:
<https://example.org/site> owl:imports <https://example.org/sensors> .
This says what is needed, not where it is. Something has to close that gap, and that something is what OntoEnv is.
An environment is a name-to-location index¶
An environment is a directory — .ontoenv/ by default — holding two things:
The graphs themselves, in a compact binary format (rdf5d) that can be memory-mapped and read without parsing RDF text.
A catalog describing them: for each ontology, its canonical IRI, where it
came from, any aliases, its namespace prefixes, and its owl:imports
targets.
The catalog is the interesting half. Because it records the import relationships explicitly, OntoEnv can answer “what does this ontology need?” by walking a graph in memory, rather than by re-parsing files. It is also small — it holds facts about ontologies, not their triples — which is why reopening an environment takes about the same time whether it holds a thousand triples or a million.
Discovery: how ontologies get in¶
Three routes:
init <dir>/search_directories=— walk a directory, parse every file matching the include filters, record the IRI each one declares.add <path-or-url>— register one ontology, then follow itsowl:importsand register those too, recursively.update()— revisit sources already known to the environment and re-read the ones that changed.
Remote ontologies are fetched over HTTP and cached on disk with a TTL, so the second run of a program does not re-download anything.
Closures¶
The transitive closure of an ontology is that ontology plus everything it imports, plus everything those import, and so on. It is the set of graphs you need in order to interpret the first one.
OntoEnv does not hand you a raw concatenation of that set. A closure is flattened:
Resolved ``owl:imports`` statements are removed. They have already been followed. Leaving them in invites a downstream consumer to follow them again — probably over the network, probably to a different version.
Ontology declarations are collapsed onto the root. A merged graph with twelve
owl:Ontologysubjects is ambiguous about what it is. The result declares one ontology: the root you asked for.SHACL prefix declarations are consolidated onto the root, so
sh:prefixesstill resolves in the merged graph.Duplicate triples appear once.
The result is a single self-contained graph. That is usually what you want when handing a closure to a reasoner, a validator, or a colleague.
When you want the other thing — exactly the graphs you named, untouched —
that is a union: ontoenv union / env.get_union(...). Unions do not
strip imports, do not collapse declarations, and do not de-duplicate across
graphs.
Aliases and canonical IRIs¶
Real ontologies get published at more than one IRI: with and without a version
suffix, over http and https, at a vanity domain and a permanent one.
An alias maps an extra IRI onto an ontology already in the environment.
An alias may only point at a canonical IRI, never at another alias — so resolution is always a single hop and chains cannot form. Aliases resolve transparently everywhere an IRI is accepted.
Resolution policy¶
When two files declare the same ontology IRI, something has to choose. The resolution policy decides:
defaultPrefer the first-registered definition.
latestPrefer the most recently updated source.
versionPrefer the highest
owl:versionIRI/ version property.
ontoenv doctor reports duplicate IRIs so you can decide whether the
duplication is intentional at all.
Strict mode¶
By default an unresolvable import is a warning: OntoEnv assembles what it can and tells you what is missing. In strict mode it is an error.
Non-strict is the right default for exploration — a closure missing one obscure vocabulary is usually still useful. Strict is the right setting for CI, where silently incomplete output is worse than a failure.
Persistent and temporary environments¶
A persistent environment writes to .ontoenv/ and reopens quickly because
of its saved catalog. A temporary one (--temporary /
OntoEnv(temporary=True)) keeps graphs and catalog in memory, leaves
nothing behind, and starts from scratch every time. The API is otherwise
identical.
To experiment with an existing environment without changing it, create an
explicit snapshot instead: env.temporary_snapshot() in Python (or
env.new_temporary() in Rust). A snapshot copies the current catalog and
graph content into a separate in-memory environment; later changes are
independent in both directions. A root supplied to
OntoEnv(temporary=True) is only a configuration base for source paths; it
does not select or load a saved environment.
Persistent environments allow one writer at a time. Any number of readers can open the same environment read-only.
See also
Views and copies for what a closure gives you in Python, and Opening an environment for the ways to open an environment.