Skip to content

Commit c318515

Browse files
committed
build: move to ontoenv 0.6.0
Pin 0.6.0-a9 -> ^0.6.0, the first stable 0.6 release. A caret range rather than the exact pin the alphas needed: 0.6.0 is a published release with a migration guide, so patch fixes are wanted, not feared. 0.6.0 closes the two upstream defects this branch had been working around, and both workarounds go with it. Deleting .ontoenv/catalog.pending by hand is gone. That existed because a merely unresolvable owl:imports left the interrupted-mutation marker behind, which made a single Library.load(ontology_graph=Brick) poison a persistent cache permanently -- routine here, not exceptional, since Brick declares imports that 404 even online. In 0.6.0 a tolerated missing import commits cleanly and leaves no marker, so reaching that handler now means an actually interrupted write. Verified: load Brick into a persistent cache, close, and the marker is absent; opens #2 and #3 both succeed with the ontology intact. _connect_recovering therefore stops removing a file ontoenv owns -- which 0.6's docs now explicitly warn against -- and calls OntoEnv.recover(path, graph_store=...) instead, the supported rebuild. It is taken automatically rather than surfaced, because the documented answer is unreachable for our callers: recovering a custom store requires passing that store, and ours does not exist until a BuildingMOTIF has been constructed, which is the thing that just failed. Recovery rescans every stored graph, so it warns; ontoenv only clears the marker once the replacement index is published, so a failed recovery still raises. Shape-collection dependency collection stops swallowing every exception. 0.6.0 raises UnresolvedImportError (a LookupError) for every unresolved owl:imports target -- confirmed 6 of 6 on the Brick closure, where earlier builds left some as bare ValueError -- so the expected case can be caught precisely and a storage error or malformed IRI now propagates instead of being logged as a missing dependency and skipped. The exception is re-exported from ontology_environment so ontoenv stays behind that seam. No API of ours changed. get_closure/get_union still return a ViewGraph that does not subclass rdflib.Graph, so the copy in ensure_and_get_closure stays; closure_names' benchmark is re-measured on 0.6.0 (list_closure free, copy_closure ~4.2s, get_closure ~2.4s over Brick's 15-graph, 155k-triple closure) and its conclusion is unchanged. No new deprecation warnings: the create_or_use_cached and init_from_store transitions were done on a8.
1 parent d0084de commit c318515

4 files changed

Lines changed: 71 additions & 54 deletions

File tree

buildingmotif/dataclasses/shape_collection.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515

1616
from buildingmotif import get_building_motif
1717
from buildingmotif.namespaces import BMOTIF, OWL, SH
18-
from buildingmotif.ontology_environment import OntologyImportsNotFound
18+
from buildingmotif.ontology_environment import (
19+
OntologyImportsNotFound,
20+
UnresolvedImportError,
21+
)
1922
from buildingmotif.utils import Triple, copy_graph, get_template_parts_from_shape
2023

2124
if TYPE_CHECKING:
@@ -239,8 +242,14 @@ def infer_templates(self, library: "Library") -> None:
239242
dependency_graphs[str(dependency)] = bm.ontology_environment.graph_copy(
240243
str(dependency)
241244
)
242-
except Exception as e:
243-
logging.warning(
245+
except UnresolvedImportError as e:
246+
# Only an import ontoenv knows it could not resolve is expected
247+
# here and skippable -- template inference simply proceeds
248+
# without that dependency's shapes. Anything else (a storage
249+
# error, a malformed IRI) is a real failure and propagates:
250+
# ontoenv >=0.6 types this case precisely so that catching it
251+
# no longer swallows those too.
252+
logger.warning(
244253
f"An ontology could not resolve a dependency on {dependency} ({e}). Check this is loaded into BuildingMOTIF"
245254
)
246255
continue

buildingmotif/ontology_environment.py

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,23 @@
33
from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Tuple, Union
44

55
import rdflib
6-
from ontoenv import CatalogRecoveryError, OntoEnv
6+
from ontoenv import CatalogRecoveryError, OntoEnv, UnresolvedImportError
77

88
from buildingmotif.database.graph_connection import _is_uuid
99

1010
if TYPE_CHECKING:
1111
from buildingmotif.database.graph_connection import GraphConnection
1212

13+
# ``UnresolvedImportError`` is re-exported so callers can catch an
14+
# unresolvable ``owl:imports`` without importing ontoenv themselves -- this
15+
# module is the one place BuildingMOTIF talks to it.
16+
__all__ = [
17+
"BuildingMOTIFGraphStore",
18+
"OntologyEnvironment",
19+
"OntologyImportsNotFound",
20+
"UnresolvedImportError",
21+
]
22+
1323

1424
class OntologyImportsNotFound(Exception):
1525
"""Raised when one or more owl:imports cannot be resolved."""
@@ -43,7 +53,7 @@ def __init__(
4353
else None
4454
)
4555

46-
# ontoenv >=0.6.0a8 deprecated the `init_from_store` flag in favour of
56+
# ontoenv >=0.6 deprecated the `init_from_store` flag in favour of
4757
# explicit lifecycle entry points. The two cases are not the same call:
4858
#
4959
# - persistent: `connect` *is* "create it or reuse the saved index",
@@ -64,44 +74,42 @@ def __init__(
6474
def _connect_recovering(
6575
path: str, store: Optional["BuildingMOTIFGraphStore"], options: Dict[str, Any]
6676
) -> OntoEnv:
67-
"""``OntoEnv.connect``, clearing a stale interrupted-mutation marker.
68-
69-
**This is a workaround for an ontoenv defect; delete it once ontoenv
70-
stops leaving the marker for tolerable failures.**
77+
"""``OntoEnv.connect``, rebuilding the catalog after an interrupted write.
7178
7279
ontoenv writes ``.ontoenv/catalog.pending`` when it begins a batched
73-
mutation and removes it only when the batch finishes cleanly
74-
(``BatchScope::run``). An *unresolvable* ``owl:imports`` makes the batch
75-
end in error, so the marker survives -- and every later open of that
76-
cache raises ``CatalogRecoveryError``.
77-
78-
That is routine here rather than exceptional: Brick declares eight
79-
imports that do not resolve offline (several 404 even online), so a
80-
single ``Library.from_ontology(Brick)`` permanently poisons a
81-
persistent cache. An ontology with no imports leaves no marker, which
82-
is how this was isolated. ontoenv exposes no recovery call, so the only
83-
way through is to remove the file.
84-
85-
The cost of doing so: after a genuine crash mid-write the marker is a
86-
real signal, and this clears it. That is the trade being made -- one
87-
warned retry, and if the retry also fails the error propagates.
80+
mutation and removes it when the batch commits; finding it at open time
81+
means a process died mid-write, and ontoenv raises
82+
``CatalogRecoveryError`` rather than trust an index that may not
83+
describe every stored graph.
84+
85+
Under ontoenv <0.6 that was a routine condition rather than an
86+
exceptional one -- a merely *unresolvable* ``owl:imports`` also left the
87+
marker, so one ``Library.load(ontology_graph=Brick)`` permanently
88+
poisoned a persistent cache -- and with no recovery call available the
89+
only way through was to delete the marker by hand. 0.6 fixed both ends:
90+
a tolerated missing import now commits cleanly and leaves no marker, and
91+
``OntoEnv.recover`` rebuilds the catalog properly.
92+
93+
So reaching this handler now means a genuinely interrupted write, and
94+
recovery is the supported answer to one. It is taken automatically
95+
because the alternative is a dead end: recovering a custom store
96+
requires passing that store, and ours does not exist until a
97+
``BuildingMOTIF`` has been constructed -- which is what failed. It is
98+
logged at warning level because it rescans every stored graph and is
99+
therefore much slower than a normal open. ontoenv verifies the rebuild
100+
and only clears the marker once the replacement index is published, so
101+
a failed recovery leaves the marker in place and raises.
88102
"""
89103
try:
90104
return OntoEnv.connect(path, graph_store=store, **options)
91105
except CatalogRecoveryError:
92-
marker = Path(path) / ".ontoenv" / "catalog.pending"
93-
if not marker.exists():
94-
raise
95106
logging.getLogger(__name__).warning(
96-
"Clearing stale ontoenv recovery marker at %s. This is normally "
97-
"left behind by an unresolvable owl:imports rather than by an "
98-
"actual interrupted write; if this repeats, the ontology cache "
99-
"at %s may genuinely be damaged and can be deleted.",
100-
marker,
107+
"Rebuilding the ontology catalog at %s after an interrupted "
108+
"write. This rescans every stored ontology and may take a "
109+
"while.",
101110
path,
102111
)
103-
marker.unlink()
104-
return OntoEnv.connect(path, graph_store=store, **options)
112+
return OntoEnv.recover(path, graph_store=store, **options)
105113

106114
def close(self) -> None:
107115
self.env.close()
@@ -144,20 +152,20 @@ def closure_names(self, ontology: str, recursion_depth: int = -1) -> list[str]:
144152
Callers that only need to know *what is in* the closure should use this
145153
rather than discarding :py:meth:`closure_copy`'s first return value.
146154
Measured on the Brick 1.4 closure (15 graphs, ~155k triples), three
147-
reps, on ontoenv 0.6.0a9:
155+
reps, on ontoenv 0.6.0:
148156
149157
- ``list_closure`` -- 0.000s, 0.000s, 0.000s
150-
- ``copy_closure`` -- 3.972s, 3.530s, 3.623s (materializes every time)
151-
- ``get_closure`` -- 2.053s, 2.077s, 2.059s (read-only view)
158+
- ``copy_closure`` -- 4.601s, 4.081s, 3.849s (materializes every time)
159+
- ``get_closure`` -- 2.435s, 2.418s, 2.479s (read-only view)
152160
153161
All three report the same 15 names, so for a name lookup this is free
154162
where the alternatives cost seconds.
155163
156-
The ``get_closure`` shape changed between releases and the numbers are
157-
worth keeping for that reason: on a8 it read 8.846s, 0.000s, 0.000s --
158-
one expensive eager index build, then cached. On a9 it is a flat ~2.05s
159-
per call. Cheaper on first use, but no longer free on repeat, so
160-
"bind once and query many times" is not the win it was on a8.
164+
The ``get_closure`` shape changed across the 0.6 alphas and the numbers
165+
are worth keeping for that reason: on 0.6.0a8 it read 8.846s, 0.000s,
166+
0.000s -- one expensive eager index build, then cached. Since a9 it is
167+
a flat ~2.4s per call. Cheaper on first use, but no longer free on
168+
repeat, so "bind once and query many times" is not the win it was.
161169
"""
162170
return list(self.env.list_closure(ontology, recursion_depth=recursion_depth))
163171

@@ -206,7 +214,7 @@ def ontology_names(self) -> list[str]:
206214
def knows(self, ontology: str) -> bool:
207215
"""Whether ontoenv can resolve ``ontology``.
208216
209-
ontoenv >=0.6.0a8 implements the container protocol, so this is a
217+
ontoenv >=0.6 implements the container protocol, so this is a
210218
direct lookup rather than building the full name list to test one
211219
membership. It is also *broader* than ``ontology in ontology_names()``:
212220
``in`` resolves aliases and source URLs as well as canonical names, so
@@ -225,7 +233,7 @@ def ensure_and_get_closure(
225233
"""Ensure graph is registered in ontoenv, then return its import closure.
226234
227235
The returned graph is materialized and mutable. ontoenv's read-only
228-
``get_closure`` view would avoid the copy, but as of 0.6.0a9 a
236+
``get_closure`` view would avoid the copy, but as of 0.6.0 a
229237
``ViewGraph`` deliberately **does not subclass rdflib.Graph**, so it
230238
cannot be handed back through this signature.
231239

poetry.lock

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ werkzeug="^2.3.7"
4141
types-jsonschema = "^4.21.0.20240311"
4242
matplotlib = "^3.9.2"
4343
pandas = "^2.2.3"
44-
ontoenv = "0.6.0-a9"
44+
ontoenv = "^0.6.0"
4545

4646
[tool.poetry.group.dev.dependencies]
4747
black = "^22.3.0"

0 commit comments

Comments
 (0)