From a775d4330f2b7c36724d6ecbabd380b8941d7bdd Mon Sep 17 00:00:00 2001 From: "marcin p. joachimiak" <4625870+realmarcin@users.noreply.github.com> Date: Tue, 21 Jul 2026 19:32:10 -0700 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20GO=20single=20source=20of=20truth?= =?UTF-8?q?=20=E2=80=94=20derive=20go.json=20+=20go.db=20from=20go.owl=20(?= =?UTF-8?q?fix=202,=20#604)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously GO had two independent downloads: go.json (fed the transform TSV) and go.owl (built go.db, the MF/BP/CC aspect map). A `kg download` straddling a GO release boundary could land them at different releases, silently miscategorizing MolecularActivity/CellularComponent terms as BiologicalProcess. The #604/#606 version-alignment gate *detected* this; fix 2 makes it *impossible* by collapsing GO to one source. - download.yaml: drop the standalone go.json download; go.owl is the only GO source (with a comment warning against re-adding go.json). - ONTOLOGIES_MAP["go"]: "go.json" → "go.owl". The transform now derives go.json from go.owl via ROBOT owl→json (the same path ChEBI already uses), so go.json and go.db share go.owl's release by construction. - ontology_utils._derived_json_is_stale(owl, json): regenerate a derived JSON when its OBO release differs from the source OWL's. Conservative — only when both stamps are readable and differ, so unstamped .owl inputs (metpo, upa, …) keep the prior "convert if missing" behavior. - ontology_utils._ensure_go_db: also rebuild go.db when its release drifts from go.owl (reuses new _go_db_release, reading `obo:go.owl owl:versionInfo`), so a refreshed go.owl realigns the aspect map too. Guarded to avoid spurious 30-min rebuilds when a stamp is unreadable. - assert_go_version_alignment: docstring updated to the single-source model; it remains the belt-and-braces guard against a leftover pre-fix-2 go.json. Tests: +8 (derived-json staleness, _go_db_release incl. decoy-subject/corrupt, _ensure_go_db keeps-aligned / rebuilds-on-drift). 29 pass. ruff + codespell clean. Note: the transform now runs a ROBOT owl→json conversion for GO on first run / after a GO refresh (steady-state re-runs skip it via the staleness guard), same as ChEBI. End-to-end verification requires a GO transform run in the pipeline. Co-Authored-By: Claude Opus 4.8 (1M context) --- download.yaml | 12 +-- .../ontologies/ontologies_transform.py | 15 ++- kg_microbe/utils/ontology_utils.py | 78 ++++++++++++-- tests/test_go_version_alignment.py | 101 ++++++++++++++++-- 4 files changed, 179 insertions(+), 27 deletions(-) diff --git a/download.yaml b/download.yaml index 0344c19c..129de70c 100644 --- a/download.yaml +++ b/download.yaml @@ -51,14 +51,10 @@ local_name: chebi.owl.gz # -# GO -# -- - url: http://purl.obolibrary.org/obo/go.json - local_name: go.json - -# -# GO +# GO — single source of truth (fix 2, #604): download only go.owl; the +# transform derives go.json from it (ROBOT owl→json) and go.db is built from +# it (semsql), so the aspect map and the transform output can never drift to +# different GO releases. Do NOT re-add a standalone go.json download. # - url: http://purl.obolibrary.org/obo/go.owl diff --git a/kg_microbe/transform_utils/ontologies/ontologies_transform.py b/kg_microbe/transform_utils/ontologies/ontologies_transform.py index 74d9b2f6..6e6faa9d 100644 --- a/kg_microbe/transform_utils/ontologies/ontologies_transform.py +++ b/kg_microbe/transform_utils/ontologies/ontologies_transform.py @@ -48,7 +48,7 @@ UNIPROT_PREFIX, XREF_COLUMN, ) -from kg_microbe.utils.ontology_utils import replace_category_ontology +from kg_microbe.utils.ontology_utils import _derived_json_is_stale, replace_category_ontology from kg_microbe.utils.pandas_utils import ( drop_duplicates, establish_transitive_relationship, @@ -73,7 +73,10 @@ "ncbitaxon": "ncbitaxon.owl.gz", "chebi": "chebi.owl.gz", "envo": "envo.json", - "go": "go.json", + # GO is single-source (fix 2, #604): the transform derives go.json from + # go.owl (ROBOT owl→json), the same OWL that go.db is built from — so the + # aspect map (go.db) and the transform output (go.json) share one release. + "go": "go.owl", ## "rhea": "rhea.json.gz", # Redundant to RheaMappingsTransform "ec": "ec.json", "upa": "upa.owl", @@ -202,7 +205,13 @@ def parse(self, name: str, data_file: Optional[Path], source: str) -> None: data_file = json_path elif data_file.suffix == ".owl": json_path = str(data_file).replace(".owl", ".json") - if not Path(json_path).is_file(): + # Regenerate the derived JSON when it's missing OR its OBO + # release no longer matches the source OWL. For single-source + # ontologies (fix 2 #604: GO derives go.json from go.owl) this + # keeps the transform output locked to the same release the OWL + # (and go.db, built from it) carry, so a refreshed go.owl can't + # leave a stale go.json behind. + if not Path(json_path).is_file() or _derived_json_is_stale(data_file, Path(json_path)): convert_to_json(str(self.input_base_dir), name) data_file = json_path elif data_file.suffix == ".obo": diff --git a/kg_microbe/utils/ontology_utils.py b/kg_microbe/utils/ontology_utils.py index 5b17015b..bdfce414 100644 --- a/kg_microbe/utils/ontology_utils.py +++ b/kg_microbe/utils/ontology_utils.py @@ -66,6 +66,23 @@ def _obo_release_from_head(path: Path, nbytes: int = 2_000_000) -> Optional[str] return m.group(1) if m else None +def _derived_json_is_stale(owl_path: Path, json_path: Path) -> bool: + """ + Return True when a derived OBO-JSON's release no longer matches its source OWL. + + Used by the ontologies transform to decide whether to re-run the ROBOT + ``owl→json`` conversion: a single-source ontology (e.g. GO, whose go.json + is derived from go.owl rather than downloaded) must regenerate the JSON + when the OWL is refreshed to a new release. Conservative — only reports + stale when *both* release stamps are readable and differ; an unstamped or + unreadable pair yields False so unstamped ``.owl`` inputs keep the prior + "convert only if missing" behavior. + """ + owl_release = _obo_release_from_head(owl_path) + json_release = _obo_release_from_head(json_path) + return bool(owl_release and json_release and owl_release != json_release) + + def _version_check_strict(env_var: str, strict: Optional[bool], default_strict: bool = True) -> bool: """ Resolve a version-gate's strictness: explicit arg wins, else the env var. @@ -87,15 +104,18 @@ def _version_check_strict(env_var: str, strict: Optional[bool], default_strict: def assert_go_version_alignment(strict: Optional[bool] = None) -> None: """ - Guard that GO's aspect-map source (go.owl→go.db) matches the transform's (go.json). - - The GO transform TSV is built from ``go.json`` while the MF/BP/CC aspect map - is read from ``go.db`` (built from ``go.owl``). If a ``kg download`` straddles - a GO release boundary the two diverge and MF/CC terms silently fall through - to the ``biological_process`` default. Compare the two releases and, on - mismatch, raise (``strict``) or warn loudly. No-op when either release stamp - can't be read (e.g. a source is absent), so a missed versionIRI never - false-alarms — only two readable-but-different stamps trip the gate. + Guard that GO's derived ``go.json`` matches its single source ``go.owl``. + + Since fix 2 (#604) GO is single-source: ``go.owl`` is the only download, and + ``go.json`` (transform output) and ``go.db`` (MF/BP/CC aspect map) are both + derived from it — the transform regenerates a stale go.json and + ``_ensure_go_db`` rebuilds a drifted go.db. This gate is the belt-and-braces + check that the derived go.json actually tracks go.owl: a leftover pre-fix-2 + go.json, or a conversion that didn't re-run, would make MF/CC terms silently + fall through to the ``biological_process`` default. Compare the two releases + and, on mismatch, raise (``strict``) or warn loudly. No-op when either + release stamp can't be read (e.g. a source is absent), so a missed versionIRI + never false-alarms — only two readable-but-different stamps trip the gate. ``strict`` defaults to fail-loud (raise). Since the verdict rests on a release-stamp heuristic, ``KG_GO_VERSION_CHECK=warn`` downgrades to a @@ -149,6 +169,31 @@ def _ncbitaxon_db_release(db_path: str) -> Optional[str]: return m.group(1) if m else None +def _go_db_release(db_path: str) -> Optional[str]: + """ + Return the GO release (YYYY-MM-DD) recorded in a SemSQL ``go.db``. + + SemSQL stamps the ontology node directly: ``obo:go.owl | owl:versionInfo | + 2026-05-19``. Target that subject exactly (rather than any entity carrying + a version-shaped value) and return None on any read error / missing stamp. + """ + try: + conn = sqlite3.connect(db_path) + try: + row = conn.execute( + "SELECT value FROM statements WHERE predicate = 'owl:versionInfo' " + "AND subject = 'obo:go.owl' AND value IS NOT NULL LIMIT 1" + ).fetchone() + finally: + conn.close() + except sqlite3.Error: + return None + if not row or not row[0]: + return None + m = re.search(r"(\d{4}-\d{2}-\d{2})", str(row[0])) + return m.group(1) if m else None + + def assert_ncbitaxon_version_alignment(db_path: str, strict: Optional[bool] = None) -> None: """ Guard that the NCBITaxon lookup DB matches the transform's OWL release. @@ -203,7 +248,20 @@ def _ensure_go_db(go_db_path: str) -> bool: from kg_microbe.transform_utils.constants import GO_SOURCE if os.path.exists(go_db_path) and os.path.getsize(go_db_path) >= _GO_DB_MIN_SIZE: - return True + # An existing, non-stub go.db is reused — unless it has drifted from the + # source OWL's release (single-source invariant, fix 2 #604): a refreshed + # go.owl must rebuild go.db, else the aspect map lags the transform output + # and MF/CC terms silently fall through to BiologicalProcess. Only rebuild + # when both release stamps are readable and differ (an unreadable stamp + # never forces a costly spurious rebuild). + owl_release = _obo_release_from_head(Path(GO_SOURCE)) if GO_SOURCE else None + db_release = _go_db_release(go_db_path) + if not (owl_release and db_release and owl_release != db_release): + return True + print( + f"Rebuilding {go_db_path}: release {db_release} drifted from " + f"go.owl {owl_release} (single-source realign)..." + ) if not (GO_SOURCE and Path(GO_SOURCE).exists()): print(f"Warning: cannot build {go_db_path} — GO OWL source {GO_SOURCE} is missing") return False diff --git a/tests/test_go_version_alignment.py b/tests/test_go_version_alignment.py index 79e48981..53de42c5 100644 --- a/tests/test_go_version_alignment.py +++ b/tests/test_go_version_alignment.py @@ -1,5 +1,6 @@ """Tests for the GO version-alignment gate and go.db build helper (#604).""" +import sqlite3 from pathlib import Path import pytest @@ -17,6 +18,22 @@ def _write_go_pair(tmp_path: Path, owl_date: str, json_date: str) -> Path: return tmp_path / "go.owl" +def _make_go_db(tmp_path: Path, release, name: str = "go.db") -> str: + """Write a minimal SemSQL-shaped go.db stamping obo:go.owl owl:versionInfo.""" + path = str(tmp_path / name) + conn = sqlite3.connect(path) + conn.execute("CREATE TABLE statements (subject TEXT, predicate TEXT, object TEXT, value TEXT)") + if release is not None: + conn.execute( + "INSERT INTO statements (subject, predicate, object, value) " + "VALUES ('obo:go.owl', 'owl:versionInfo', NULL, ?)", + (release,), + ) + conn.commit() + conn.close() + return path + + def test_release_parsed_from_owl_versioniri(tmp_path): """The YYYY-MM-DD release is read from an OWL versionIRI.""" (tmp_path / "go.owl").write_text(_OWL.format(d="2026-05-19"), encoding="utf-8") @@ -61,9 +78,7 @@ def test_mismatch_warns_when_not_strict(tmp_path, monkeypatch, capsys): def test_missing_source_is_a_noop(tmp_path, monkeypatch): """When a GO source can't be read, the gate is a no-op (can't judge).""" - monkeypatch.setattr( - "kg_microbe.transform_utils.constants.GO_SOURCE", tmp_path / "absent.owl" - ) + monkeypatch.setattr("kg_microbe.transform_utils.constants.GO_SOURCE", tmp_path / "absent.owl") ou.assert_go_version_alignment(strict=True) # must not raise @@ -117,8 +132,82 @@ def test_ensure_go_db_skips_build_when_valid(tmp_path, monkeypatch): def test_ensure_go_db_cannot_build_without_owl(tmp_path, monkeypatch, capsys): """A missing/empty go.db with no OWL source degrades gracefully (warn, False).""" - monkeypatch.setattr( - "kg_microbe.transform_utils.constants.GO_SOURCE", tmp_path / "absent.owl" - ) + monkeypatch.setattr("kg_microbe.transform_utils.constants.GO_SOURCE", tmp_path / "absent.owl") assert ou._ensure_go_db(str(tmp_path / "go.db")) is False assert "missing" in capsys.readouterr().out + + +# --- fix 2 (#604): GO single source of truth ------------------------------- + + +def test_derived_json_stale_when_releases_differ(tmp_path): + """A go.json whose release differs from go.owl is reported stale.""" + owl = _write_go_pair(tmp_path, "2026-05-19", "2026-04-01") + assert ou._derived_json_is_stale(owl, tmp_path / "go.json") is True + + +def test_derived_json_not_stale_when_aligned(tmp_path): + """Matching releases are not stale (no needless reconversion).""" + owl = _write_go_pair(tmp_path, "2026-05-19", "2026-05-19") + assert ou._derived_json_is_stale(owl, tmp_path / "go.json") is False + + +def test_derived_json_unstamped_is_not_stale(tmp_path): + """An unreadable/unstamped pair yields False (preserves convert-if-missing).""" + (tmp_path / "go.owl").write_text("no version here", encoding="utf-8") + assert ou._derived_json_is_stale(tmp_path / "go.owl", tmp_path / "missing.json") is False + + +def test_go_db_release_read_from_versioninfo(tmp_path): + """_go_db_release reads owl:versionInfo off the obo:go.owl subject.""" + assert ou._go_db_release(_make_go_db(tmp_path, "2026-05-19")) == "2026-05-19" + + +def test_go_db_release_ignores_decoy_subject(tmp_path): + """A version-shaped value on a non-ontology subject is ignored.""" + path = str(tmp_path / "go.db") + conn = sqlite3.connect(path) + conn.execute("CREATE TABLE statements (subject TEXT, predicate TEXT, object TEXT, value TEXT)") + conn.execute("INSERT INTO statements VALUES ('GO:0008150', 'owl:versionInfo', NULL, '1999-01-01')") + conn.execute("INSERT INTO statements VALUES ('obo:go.owl', 'owl:versionInfo', NULL, '2026-05-19')") + conn.commit() + conn.close() + assert ou._go_db_release(path) == "2026-05-19" + + +def test_go_db_release_none_when_absent_or_corrupt(tmp_path): + """Missing stamp, missing file, and a non-sqlite file all yield None.""" + assert ou._go_db_release(_make_go_db(tmp_path, None)) is None + assert ou._go_db_release(str(tmp_path / "nope.db")) is None + corrupt = tmp_path / "corrupt.db" + corrupt.write_text("not sqlite", encoding="utf-8") + assert ou._go_db_release(str(corrupt)) is None + + +def test_ensure_go_db_keeps_aligned_db(tmp_path, monkeypatch): + """A valid, release-aligned go.db is reused (no rebuild).""" + monkeypatch.setattr(ou, "_GO_DB_MIN_SIZE", 8) + monkeypatch.setattr( + "kg_microbe.transform_utils.constants.GO_SOURCE", + _write_go_pair(tmp_path, "2026-05-19", "2026-05-19"), + ) + db = _make_go_db(tmp_path, "2026-05-19") + assert ou._ensure_go_db(db) is True + + +def test_ensure_go_db_rebuilds_on_release_drift(tmp_path, monkeypatch, capsys): + """ + A valid-size but release-drifted go.db is NOT short-circuited as usable. + + With ``semsql`` forced off, the drift path falls through to a build attempt + and returns False — proving the stale db was rejected rather than reused. + """ + monkeypatch.setattr(ou, "_GO_DB_MIN_SIZE", 8) + monkeypatch.setattr( + "kg_microbe.transform_utils.constants.GO_SOURCE", + _write_go_pair(tmp_path, "2026-05-19", "2026-05-19"), + ) + monkeypatch.setattr(ou.shutil, "which", lambda _cmd: None) # no semsql → build can't run + db = _make_go_db(tmp_path, "2026-01-01") # drifted from go.owl (2026-05-19) + assert ou._ensure_go_db(db) is False + assert "drifted" in capsys.readouterr().out From ef4f899179854210b25c3fb1e182dfaaeeff37ec Mon Sep 17 00:00:00 2001 From: "marcin p. joachimiak" <4625870+realmarcin@users.noreply.github.com> Date: Tue, 21 Jul 2026 19:58:14 -0700 Subject: [PATCH 2/3] fix(go-single-source): regenerate stale go.json; harden go.db release read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review findings on #609: - HIGH (correctness): the stale-JSON regeneration was a no-op. convert_to_json (robot_utils.py:50) skips when the target already exists, so a stale go.json was never overwritten — a refreshed go.owl would leave the old go.json in place and the alignment gate would hard-abort the transform instead of it self-healing. Fix: unlink the stale go.json before reconverting in the .owl branch (mirrors _ensure_go_db's os.remove-then-rebuild). _derived_json_is_stale returns False for a missing file, so the unlink never hits a missing path. - LOW (robustness): _go_db_release hard-coded subject='obo:go.owl'. Broaden to also match 'obo:go' and the full-IRI form ('%/go.owl') so a different semsql/rdftab subject encoding doesn't silently return None (which would make _ensure_go_db reuse a drifted db). Still excludes GO: term subjects. Tests: +3 (stale go.json unlinked+reconverted via a stubbed transform; aligned go.json not reconverted; full-IRI db subject read). 22 pass. ruff + codespell clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../ontologies/ontologies_transform.py | 9 ++- kg_microbe/utils/ontology_utils.py | 12 +++- tests/test_go_version_alignment.py | 69 +++++++++++++++++++ 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/kg_microbe/transform_utils/ontologies/ontologies_transform.py b/kg_microbe/transform_utils/ontologies/ontologies_transform.py index 6e6faa9d..d4c6cfa0 100644 --- a/kg_microbe/transform_utils/ontologies/ontologies_transform.py +++ b/kg_microbe/transform_utils/ontologies/ontologies_transform.py @@ -210,8 +210,13 @@ def parse(self, name: str, data_file: Optional[Path], source: str) -> None: # ontologies (fix 2 #604: GO derives go.json from go.owl) this # keeps the transform output locked to the same release the OWL # (and go.db, built from it) carry, so a refreshed go.owl can't - # leave a stale go.json behind. - if not Path(json_path).is_file() or _derived_json_is_stale(data_file, Path(json_path)): + # leave a stale go.json behind. convert_to_json is a no-op when + # the target already exists, so a stale JSON must be removed + # first (mirrors _ensure_go_db's rebuild) — otherwise the + # reconversion silently keeps the old release. + if _derived_json_is_stale(data_file, Path(json_path)): + Path(json_path).unlink() + if not Path(json_path).is_file(): convert_to_json(str(self.input_base_dir), name) data_file = json_path elif data_file.suffix == ".obo": diff --git a/kg_microbe/utils/ontology_utils.py b/kg_microbe/utils/ontology_utils.py index bdfce414..0d9ae2d0 100644 --- a/kg_microbe/utils/ontology_utils.py +++ b/kg_microbe/utils/ontology_utils.py @@ -174,15 +174,21 @@ def _go_db_release(db_path: str) -> Optional[str]: Return the GO release (YYYY-MM-DD) recorded in a SemSQL ``go.db``. SemSQL stamps the ontology node directly: ``obo:go.owl | owl:versionInfo | - 2026-05-19``. Target that subject exactly (rather than any entity carrying - a version-shaped value) and return None on any read error / missing stamp. + 2026-05-19``. Match the GO ontology subject across the encodings different + semsql/rdftab builds emit — the CURIE ``obo:go.owl`` / ``obo:go`` or the + full IRI ``.../obo/go.owl`` — while still excluding GO *term* subjects + (``GO:...``) that might carry a version-shaped value. Returns None on any + read error / missing stamp; a None here makes ``_ensure_go_db`` conservatively + reuse the existing db rather than force a spurious rebuild. """ try: conn = sqlite3.connect(db_path) try: row = conn.execute( "SELECT value FROM statements WHERE predicate = 'owl:versionInfo' " - "AND subject = 'obo:go.owl' AND value IS NOT NULL LIMIT 1" + "AND value IS NOT NULL AND (" + "subject IN ('obo:go.owl', 'obo:go') OR subject LIKE '%/go.owl'" + ") LIMIT 1" ).fetchone() finally: conn.close() diff --git a/tests/test_go_version_alignment.py b/tests/test_go_version_alignment.py index 53de42c5..07353ec9 100644 --- a/tests/test_go_version_alignment.py +++ b/tests/test_go_version_alignment.py @@ -211,3 +211,72 @@ def test_ensure_go_db_rebuilds_on_release_drift(tmp_path, monkeypatch, capsys): db = _make_go_db(tmp_path, "2026-01-01") # drifted from go.owl (2026-05-19) assert ou._ensure_go_db(db) is False assert "drifted" in capsys.readouterr().out + + +def test_go_db_release_reads_full_iri_subject(tmp_path): + """The db-release read also matches the full-IRI ontology subject encoding.""" + path = str(tmp_path / "go.db") + conn = sqlite3.connect(path) + conn.execute("CREATE TABLE statements (subject TEXT, predicate TEXT, object TEXT, value TEXT)") + conn.execute( + "INSERT INTO statements VALUES ('http://purl.obolibrary.org/obo/go.owl', 'owl:versionInfo', NULL, '2026-05-19')" + ) + conn.commit() + conn.close() + assert ou._go_db_release(path) == "2026-05-19" + + +def _stub_go_transform(tmp_path, monkeypatch): + """ + Build an OntologiesTransform whose post-conversion steps are stubbed out. + + Returns (transform, calls) where calls['convert'] counts convert_to_json + invocations. convert_to_json is faked to write a fresh 2026-05-19 go.json + (what ROBOT would produce from the new go.owl). + """ + import kg_microbe.transform_utils.ontologies.ontologies_transform as ot + + calls = {"convert": 0} + + def fake_convert(path, ont): + calls["convert"] += 1 + (Path(path) / f"{ont}.json").write_text(_JSON.format(d="2026-05-19"), encoding="utf-8") + + monkeypatch.setattr(ot, "convert_to_json", fake_convert) + monkeypatch.setattr(ot, "transform", lambda **k: None) + t = ot.OntologiesTransform.__new__(ot.OntologiesTransform) + t.input_base_dir = tmp_path + t.output_dir = tmp_path + monkeypatch.setattr(t, "_sanitize_obograph_synonyms", lambda p: None, raising=False) + monkeypatch.setattr(t, "_drop_deprecated_terms", lambda p: None, raising=False) + monkeypatch.setattr(t, "post_process", lambda n: None, raising=False) + return t, calls + + +def test_stale_go_json_is_removed_and_reconverted(tmp_path, monkeypatch): + """ + A stale go.json is unlinked then regenerated — convert_to_json won't overwrite it. + + Regression for the review finding: without the unlink, convert_to_json is a + no-op on an existing (stale) file, so a refreshed go.owl would leave the old + go.json in place instead of self-healing. + """ + (tmp_path / "go.owl").write_text(_OWL.format(d="2026-05-19"), encoding="utf-8") + (tmp_path / "go.json").write_text(_JSON.format(d="2026-04-01"), encoding="utf-8") # stale + t, calls = _stub_go_transform(tmp_path, monkeypatch) + + t.parse("go", tmp_path / "go.owl", "go") + + assert calls["convert"] == 1 + assert ou._obo_release_from_head(tmp_path / "go.json") == "2026-05-19" # regenerated + + +def test_aligned_go_json_is_not_reconverted(tmp_path, monkeypatch): + """An already-aligned go.json is reused (no needless ROBOT reconversion).""" + (tmp_path / "go.owl").write_text(_OWL.format(d="2026-05-19"), encoding="utf-8") + (tmp_path / "go.json").write_text(_JSON.format(d="2026-05-19"), encoding="utf-8") # aligned + t, calls = _stub_go_transform(tmp_path, monkeypatch) + + t.parse("go", tmp_path / "go.owl", "go") + + assert calls["convert"] == 0 From 2d5b9edac9d7ee0e35f7fe3f8cbf9f67c3516ad2 Mon Sep 17 00:00:00 2001 From: "marcin p. joachimiak" <4625870+realmarcin@users.noreply.github.com> Date: Tue, 21 Jul 2026 19:59:28 -0700 Subject: [PATCH 3/3] =?UTF-8?q?test:=20rename=20module=20alias=20ot?= =?UTF-8?q?=E2=86=92tf=5Fmod=20(codespell=20false-positive)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/test_go_version_alignment.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_go_version_alignment.py b/tests/test_go_version_alignment.py index 07353ec9..d45b51de 100644 --- a/tests/test_go_version_alignment.py +++ b/tests/test_go_version_alignment.py @@ -234,7 +234,7 @@ def _stub_go_transform(tmp_path, monkeypatch): invocations. convert_to_json is faked to write a fresh 2026-05-19 go.json (what ROBOT would produce from the new go.owl). """ - import kg_microbe.transform_utils.ontologies.ontologies_transform as ot + from kg_microbe.transform_utils.ontologies import ontologies_transform as tf_mod calls = {"convert": 0} @@ -242,9 +242,9 @@ def fake_convert(path, ont): calls["convert"] += 1 (Path(path) / f"{ont}.json").write_text(_JSON.format(d="2026-05-19"), encoding="utf-8") - monkeypatch.setattr(ot, "convert_to_json", fake_convert) - monkeypatch.setattr(ot, "transform", lambda **k: None) - t = ot.OntologiesTransform.__new__(ot.OntologiesTransform) + monkeypatch.setattr(tf_mod, "convert_to_json", fake_convert) + monkeypatch.setattr(tf_mod, "transform", lambda **k: None) + t = tf_mod.OntologiesTransform.__new__(tf_mod.OntologiesTransform) t.input_base_dir = tmp_path t.output_dir = tmp_path monkeypatch.setattr(t, "_sanitize_obograph_synonyms", lambda p: None, raising=False)