Staying in sync¶
OntoEnv has a saved view of the world, and the world moves. Files get edited, remote ontologies get republished, and — if you supplied your own graph store — something else may write into that store directly.
There are two reconciliation operations, and they solve genuinely different problems.
|
|
|
|---|---|---|
Reconciles |
Ontology sources: files and URLs |
Graphs changed directly in a custom store |
Reads |
The filesystem and the network |
Your |
Follows |
Yes |
No |
Relevant when |
Always |
Only with |
Both exist because they answer different questions. “Has site.ttl changed
on disk, and if so what does it import now?” is not the same question as “did
another process write into my database while I wasn’t looking?” Collapsing
them into one call would mean every restart either checks the network or
scans your whole backend.
Refreshing sources¶
env.update() # changed files + expired remotes
env.update(force=True) # every known source, regardless
env.update("https://example.org/site.ttl") # one source
env.update("https://example.org/site.ttl", force=True)
update() checks the configured search directories for new, changed, and
removed files, and re-fetches remote ontologies whose cached copies have
outlived the TTL. It follows owl:imports throughout, so dependencies stay
current along with the ontologies that pulled them in.
With a location argument it updates that one source and replaces its stored graph, again following imports.
Whether update touches the network at all depends on the environment’s
offline and cache settings. See Work offline and control caching.
Why connect doesn’t do this for you¶
Because reading files and fetching URLs is expensive, and startup is exactly when you least want to pay for it.
A service that restarts under load would otherwise re-parse every ontology on
every restart. Keeping update() explicit means the cost is scheduled by
you — at deploy time, on a timer, from an admin endpoint — rather than
imposed by the library.
Reconciling a custom store¶
This section only matters if you passed graph_store=.
Changes made through env update your store and OntoEnv’s catalog
together; nothing extra is needed. Changes made directly to the store are
different, because OntoEnv did not see them happen.
How much OntoEnv can do about that depends entirely on what your store can tell it:
The store reports per-graph revisions (graph_revisions()). OntoEnv
rereads only the graphs that actually changed. This is the good case, and the
reason that optional method is worth implementing.
The store reports that it changed, but not which graphs
(store_state() only). OntoEnv raises rather than guessing, and asks you to
request sync="full" explicitly.
The store reports nothing. OntoEnv trusts its saved catalog until you ask for a refresh.
That middle case is a deliberate design choice. sync="auto" will never
silently turn a normal restart into a full scan of your database — if the only
correct action is expensive, you get to decide when to pay for it.
report = env.refresh_from_store() # incremental
report = env.refresh_from_store(graphs=[iri, ...]) # exactly these
report = env.refresh_from_store(full=True) # forget and rebuild
print(report.added, report.changed, report.removed)
Targeted refreshes are exact¶
graphs= is an exact set of backend graph IDs. OntoEnv does not expand it —
if you name a root, you get the root, not its imports. To refresh a root along
with the closure already recorded for it, compose the two calls:
report = env.refresh_from_store(graphs=env.list_closure(root))
That works when a known closure was rewritten together. But if the external
edit introduced a new imported graph, that graph is not in the old closure
yet, so a targeted refresh will not see it. An incremental refresh finds it
when the store reports per-graph changes; otherwise full=True is the
answer.
A targeted refresh also says nothing about graphs it did not look at. The report lists those as still pending, and a later connection will still detect that the store and the catalog have drifted.
Synchronization at connect time¶
sync= controls the same machinery during connect:
"auto"(default)Create or index on first use, reuse the saved catalog on restart, incorporate external changes when the store can identify them. Never reads ontology sources.
"full"Reread every graph in the store. For known out-of-band changes the store cannot identify.
"catalog"Use the saved catalog without reading graph contents at all. For controlled deployments where another part of the system guarantees the saved view is correct.
None of these fetch ontology sources. After connecting, call update() when
files and URLs should be refreshed too.
When commits are interrupted¶
A mutation writes graphs, then publishes the updated catalog. If a process
dies between those steps, a catalog.pending marker remains and the next
open raises CatalogRecoveryError rather than trusting a catalog that might
not describe every graph.
Normal completed mutations remove the marker as part of their commit —
including best-effort ones. In non-strict mode,
import_dependencies(..., fetch_missing=True) and
get_dependencies(..., fetch_missing=True) may skip imports they cannot
reach, but the partial result they did commit is a complete, valid commit.
So a recovery marker always means an interrupted or failed write, never merely
a missing import.