Skip to content

fix(ci): repair archs4 + ELM live-data test failures#252

Merged
lauraluebbert merged 17 commits into
scverse:devfrom
Elarwei001:fix/dev-ci-repair
Jun 26, 2026
Merged

fix(ci): repair archs4 + ELM live-data test failures#252
lauraluebbert merged 17 commits into
scverse:devfrom
Elarwei001:fix/dev-ci-repair

Conversation

@Elarwei001

@Elarwei001 Elarwei001 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Scoped to the ARCHS4 + ELM CI-stability fixes only. The opentargets parts of this PR have been moved to #256 per maintainer preference for one-module-per-PR review.

ARCHS4 — graceful missing-color column + deterministic order

Bug: ARCHS4 intermittently omits the color column from its tissue-expression CSV response. The previous tissue_exp_df.drop(["color"], axis=1) raises KeyError: ['color'] not found in axis when this happens, so gget archs4 -w tissue ... crashes for users on those requests.

Fix: drop(columns=["color"], errors="ignore") — drops if present, no-op if absent. The color column was never used by gget archs4 (only ARCHS4's own plotting needs it).

Secondary improvement: sort key changed from "median" to ["median", "id"] (descending median, ascending id) so equal-median tissues no longer flip order between requests. Test fixtures refreshed for the deterministic ordering.

New regression tests (network-free, mocked):

  • TestArchs4MissingColor.test_tissue_missing_color_does_not_crash — main regression guard for the errors="ignore" fix
  • TestArchs4MissingColor.test_tissue_with_color_still_dropped — paired companion confirming the column still gets dropped when present

ELM — retry transient setup download

gget_setup(module="elm") downloads ELM database files from elm.eu.org. Cold-cache or transient network blips occasionally fail the download and break test collection (the call happens at module-import time, before any test runs). Wrap in a 3-attempt retry with a 30s sleep between attempts so transient failures don't kill the whole opentargets/ELM suite.

Out of scope (moved or punted)

Test plan

  • python -m pytest tests/test_archs4.py -v locally — all pass (including the new TestArchs4MissingColor cases)
  • python -m pytest tests/test_elm.py -v locally — passes (ELM setup either succeeds first try or after a retry)
  • CI green on this PR

Elarwei001 and others added 5 commits June 25, 2026 13:50
…0 fix)

OpenTargets changed the Drug 'synonyms' and 'tradeNames' fields from
[String!]! to the object type [DrugLabelAndSource!]!, which now requires
a sub-selection. The bare-scalar selection caused every drug query to
fail with HTTP 400.

Request '{ label }' for both fields and flatten the response objects
back to a list of label strings so downstream output stays
backward-compatible (a list of strings).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…ev-drift)

ARCHS4's tissue-expression CSV intermittently omits the 'color' column,
which made `gget archs4 --which tissue` crash with
`KeyError: "['color'] not found in axis"`. The 'color' column is only used
for plotting upstream and is dropped (never used) by gget, so a missing
column should not be fatal.

Use `drop(columns=["color"], errors="ignore")` so the request degrades
gracefully when the column is absent. Adds network-free regression tests
covering both the present-color and missing-color responses.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
OpenTargets retired the `target.expressions` field (it now returns an empty
list for every gene), so `gget opentargets -r expression` returned nothing.
Baseline expression data moved to the paginated `target.baselineExpression`
field with a new per-biosample data model.

- Repoint the expression query to `baselineExpression(page:{index:0,size:250})
  { rows {...} }` and update rows_path to ["baselineExpression","rows"].
- Output columns change accordingly (per-biosample summary stats: median/min/
  q1/q3/max/unit + tissueBiosample/celltypeBiosample ids + datasource/datatype),
  because the upstream data model changed and the old shape no longer exists.
- Remove the two now-invalid live exact-match fixtures and replace them with
  network-free mocked tests; update docs (example, resource table, updates.md).

Verified live: http_json with the new query returns 1409 rows in ~0.6s and the
parsing pipeline yields the documented columns.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…t (data drifts across releases)

OpenTargets is a live database re-released regularly; several opentargets tests
pinned exact current values (disease ids/scores, result hashes, interaction
partner ids, genotypes) that legitimately change every release, so they failed
on unrelated PRs even though gget returns correct current data.

Replace the exact-value/hash assertions for test_opentargets, _diseases,
_depmap, _depmap_filter, _interactions, _interactions_no_limit and
_pharmacogenetics with structural/invariant assertions (expected columns
present, numeric dtypes, value-format patterns — ontology-curie disease/tissue
ids, ENSG interaction partners, ACH DepMap ids, score in [0,1], nucleotide
genotypes — and the depmap filter invariant). The fixture entries are marked
`code_defined`; the structural methods live in tests/test_opentargets.py.

These stay meaningful (they break on wrong columns, malformed ids, non-numeric
scores, broken filtering, or empty-where-guaranteed) without pinning drifting
data. Verified live against current OpenTargets data.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@codecov-commenter

codecov-commenter commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 7.98%. Comparing base (5cf607f) to head (d640bff).
⚠️ Report is 8 commits behind head on dev.

Files with missing lines Patch % Lines
gget/gget_archs4.py 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #252       +/-   ##
==========================================
- Coverage   56.14%   7.98%   -48.17%     
==========================================
  Files          29      29               
  Lines        9244    9244               
==========================================
- Hits         5190     738     -4452     
- Misses       4054    8506     +4452     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Elarwei001

Elarwei001 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

currently opentarget change the format of its resource="drugs" response from "string" to a "DrugLabelAndSource" object. the response example looks like this:

`query: { drug(chemblId: "CHEMBL1201631") { name synonyms { label source } tradeNames { label source } } }

HTTP 200
name: INSULIN HUMAN

synonyms (first 8, now WITH source):
{'label': 'Biosulin r', 'source': 'ChEMBL'}
{'label': 'Capsulin', 'source': 'ChEMBL'}
{'label': 'Exubera (inhaled insulin human)', 'source': 'ChEMBL'}
{'label': 'High molecular weight insulin human', 'source': 'ChEMBL'}
{'label': 'Human insulin', 'source': 'ChEMBL'}
{'label': 'Humulin', 'source': 'ChEMBL'}
{'label': 'Insulin', 'source': 'ChEMBL'}
{'label': 'Insulin (human)', 'source': 'ChEMBL'}

tradeNames (first 8, now WITH source):
{'label': 'Actrapid', 'source': 'ChEMBL'}
{'label': 'Afrezza', 'source': 'ChEMBL'}
{'label': 'Exubera', 'source': 'ChEMBL'}
{'label': 'Humulin br', 'source': 'ChEMBL'}
{'label': 'Humulin r', 'source': 'ChEMBL'}
{'label': 'Inpremzia', 'source': 'ChEMBL'}
{'label': 'Ins humulin r', 'source': 'ChEMBL'}
{'label': 'Insuman', 'source': 'ChEMBL'}

-> Each entry now shows {'label': ..., 'source': ...}. So source was
always available; the earlier output omitted it only because the query
selected { label } (and gget intentionally keeps just label to
preserve its historical plain-string output).`

so, will need to change the decoding structure to capture current response labels.
image

test code:

#!/usr/bin/env python3
"""
Show that DrugLabelAndSource really has BOTH `label` and `source` —
we only saw `label` before because our query only selected `label`.
GraphQL returns exactly the fields you request, nothing more.

Read-only OpenTargets API call. No GitHub, no writes.
Run: /Users/elar/gget/.venv/bin/python /Users/elar/cc_output/gget/verify-opentargets-source.py
"""
import json

import requests

URL = "https://api.platform.opentargets.org/api/v4/graphql"
DRUG = "CHEMBL1201631"

# Note: now we ALSO request `source` in the sub-selection.
q = f'{{ drug(chemblId: "{DRUG}") {{ name synonyms {{ label source }} tradeNames {{ label source }} }} }}'
print("query:", q, "\n")

r = requests.post(URL, json={"query": q}, timeout=60)
print("HTTP", r.status_code)
drug = (r.json().get("data") or {}).get("drug") or {}
print("name:", drug.get("name"), "\n")

print("synonyms (first 8, now WITH source):")
for item in (drug.get("synonyms") or [])[:8]:
    print("   ", item)

print("\ntradeNames (first 8, now WITH source):")
for item in (drug.get("tradeNames") or [])[:8]:
    print("   ", item)

print(
    "\n-> Each entry now shows {'label': ..., 'source': ...}. So `source` was"
    "\n   always available; the earlier output omitted it only because the query"
    "\n   selected `{ label }` (and gget intentionally keeps just `label` to"
    "\n   preserve its historical plain-string output)."
)

@Elarwei001

Copy link
Copy Markdown
Contributor Author

try the GENE's associate diseases, the result may change (which means opentarget's response won't be stable).

for example, if compare with the previous test case in dev, current value is update both on ID and Score:
image

test code:

#!/usr/bin/env python3
"""
Measure the ACTUAL drift of `gget opentargets -r diseases` for IL13
(ENSG00000169194, the gene used in test_opentargets), so we can design a
RIGOROUS test (known-ID-set + score tolerance), not a weak one.

Compares the live OpenTargets top-2 associated diseases against the OLD
fixture values that PR #250 deleted.

Read-only OpenTargets API. No GitHub, no writes.
Run: /Users/elar/gget/.venv/bin/python /Users/elar/cc_output/gget/verify-opentargets-diseases-drift.py
"""
import json

import requests

URL = "https://api.platform.opentargets.org/api/v4/graphql"
GENE = "ENSG00000169194"  # IL13

# What the OLD fixture asserted (the 171 lines PR #250 removed), top 2:
OLD = [
    {"score": 0.7297489019498119, "id": "EFO_0000274", "name": "atopic eczema"},
    {"score": 0.6642728577751653, "id": "MONDO_0004979", "name": "asthma"},
]

q = f"""
{{ target(ensemblId: "{GENE}") {{
     associatedDiseases(page: {{index: 0, size: 2}}) {{
       count
       rows {{ score disease {{ id name }} }}
     }}
}} }}
"""

r = requests.post(URL, json={"query": q}, timeout=60)
print("HTTP", r.status_code)
body = r.json()
rows = (((body.get("data") or {}).get("target") or {}).get("associatedDiseases") or {}).get("rows")
if not rows:
    print(json.dumps(body, indent=2)[:800])
    raise SystemExit("no rows — inspect raw response above")

print(f"\n{'rank':<5}{'LIVE id':<16}{'LIVE name':<22}{'LIVE score':<14}"
      f"{'OLD id':<16}{'OLD score':<14}{'Δscore':<12}{'id changed?'}")
print("-" * 110)
for i, row in enumerate(rows):
    live_id = row["disease"]["id"]
    live_name = row["disease"]["name"]
    live_score = row["score"]
    old = OLD[i] if i < len(OLD) else {"id": "-", "score": float("nan"), "name": "-"}
    dscore = live_score - old["score"]
    rel = abs(dscore) / old["score"] if old["score"] else float("nan")
    idchg = "SAME" if live_id == old["id"] else f"{old['id']} -> {live_id}"
    print(f"{i:<5}{live_id:<16}{live_name:<22}{live_score:<14.6f}"
          f"{old['id']:<16}{old['score']:<14.6f}{dscore:<+12.6f}{idchg}")
    print(f"     (relative score drift this row: {rel*100:.3f}%)")

print("\nUse this to decide: (a) the acceptable ID SET per disease (did EFO<->MONDO"
      "\nflip, or is the name stable while id changed?), and (b) a score TOLERANCE"
      "\nthat survives normal release drift but still catches wrong-data. Run on a"
      "\nfew genes / a few days to see the real drift magnitude before fixing the test.")

Elarwei001 and others added 3 commits June 26, 2026 22:20
…ct-snapshot tests (scverse#249)

Sort tissue rows by [median desc, id asc] so output is reproducible when medians tie
(ARCHS4 returns tied rows in varying order). Revert the live tissue tests to exact
assert_equal snapshots (re-sorted to the deterministic order); keep the network-free
color regression tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Strip back the opentargets-related changes so this PR is focused on
the archs4 + ELM CI-stability fixes only. The opentargets work
(synonyms HTTP 400 fix, fixture refresh, expression skip) is being
handled in a separate PR (scverse#256), per maintainer preference for
one-module-per-PR review.

Reverted to origin/dev:
- gget/gget_opentargets.py
- tests/test_opentargets.py
- tests/fixtures/test_opentargets.json

Trimmed updates.md:
- Removed the opentargets bullet (lives in scverse#256)
- Added an archs4 bullet explaining the color-column + deterministic-
  sort fix (user-visible behavior change, was missing here)

Remaining scope:
- gget_archs4.py: graceful handling of missing color column,
  deterministic median-then-id sort
- tests/test_archs4.py: TestArchs4MissingColor regression test
- tests/fixtures/test_archs4.json: refreshed for the deterministic sort
- tests/test_elm.py: retry ELM setup on transient download failure

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@lauraluebbert lauraluebbert changed the title fix(ci): repair dev live-data test failures fix(ci): repair archs4 + ELM live-data test failures Jun 26, 2026
test_tissue_with_color_still_dropped tested the "happy path" that both
the old and the new code already handle the same way (column present →
column dropped from output). It can't catch any plausible regression
of the actual fix (which is the errors="ignore" kwarg, exercised by
the sibling test_tissue_missing_color_does_not_crash).

Removing it tightens the test suite without weakening the regression
guard around the actual bug. _CSV_WITH_COLOR class attribute removed
along with it (no other references).
@lauraluebbert lauraluebbert marked this pull request as ready for review June 26, 2026 18:58
@lauraluebbert lauraluebbert merged commit 919dab8 into scverse:dev Jun 26, 2026
1 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants