Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions .github/actions/respro-build-qc/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
name: respro build QC
description: |
Install ResistanceProfiler from a GitHub release tag (no conda, no autobump
of a pinned version) and run the build quality-control step against a
converted database's output artifacts. Used by both the autobump workflows
and the PR-triggered build-check workflow so every build is validated
against the same respro version.

inputs:
source-name:
description: Source database name (e.g. stanford-hiv). Passed to respro as the project name.
required: true
output-dir:
description: Directory containing rules.tsv / metadata.json / formula-rules.tsv.
required: true
work-dir:
description: Working directory for GenBank fetch + built .db + logs.
required: false
default: /tmp/respro_qc
respro-version:
description: |
ResistanceProfiler version to build from. Defaults to "latest", which
resolves the newest tag on the the-foxlab/ResistanceProfiler repository
at run time. Set to a concrete tag (e.g. "v0.1.3") to pin a specific
release.
required: false
default: latest

outputs:
qc_failed:
description: '"true" when the respro build QC reported a hard failure, otherwise "false".'
value: ${{ steps.qc_summary.outputs.qc_failed }}
summary:
description: 'Multi-line markdown summary of the build QC, suitable for PR bodies.'
value: ${{ steps.qc_summary.outputs.block }}

runs:
using: composite

steps:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Resolve latest respro release tag
if: inputs.respro-version == 'latest'
id: respro_tag
shell: bash
run: |
set -euo pipefail
# Fetch the newest non-web tag from the ResistanceProfiler repo.
# Tags are prefixed with "v" (e.g. v0.1.3); web releases use "web-v".
tag=$(curl -fsSL \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/the-foxlab/ResistanceProfiler/tags" \
| python -c '
import json, sys
tags = json.load(sys.stdin)
for t in tags:
name = t.get("name", "")
if name.startswith("v") and not name.startswith("web-"):
print(name)
break
else:
sys.exit("No non-web respro release tag found")
')
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
echo "Resolved latest respro release tag: ${tag}"

- name: Determine respro version
id: respro_version
shell: bash
run: |
if [ "${{ inputs.respro-version }}" = "latest" ]; then
echo "version=${{ steps.respro_tag.outputs.tag }}" >> "$GITHUB_OUTPUT"
else
echo "version=${{ inputs.respro-version }}" >> "$GITHUB_OUTPUT"
fi

- name: Install respro from release tag
shell: bash
run: |
set -euo pipefail
python -m pip install --upgrade pip
# Build from the tagged source tarball. The runner has a C compiler
# so the mappy native dependency builds from source without conda.
version="${{ steps.respro_version.outputs.version }}"
echo "Installing ResistanceProfiler ${version} from source"
python -m pip install \
"respro @ git+https://github.com/the-foxlab/ResistanceProfiler.git@${version}"
respro --version

- name: respro build QC
id: respro_qc
shell: bash
continue-on-error: true
run: |
python scripts/respro_build_qc.py \
--source-name "${{ inputs.source-name }}" \
--output-dir "${{ inputs.output-dir }}" \
--work-dir "${{ inputs.work-dir }}"
echo "qc_exit=$?" >> "$GITHUB_OUTPUT"

- name: Read respro QC summary
id: qc_summary
shell: bash
run: |
summary_path="${{ inputs.work-dir }}/qc_summary.md"
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "block<<${EOF}" >> "$GITHUB_OUTPUT"
qc_failed="false"
if [ ! -f "$summary_path" ]; then
# The QC script exited before writing a summary (infrastructure
# error: crash, missing respro, import failure). Emit a synthetic
# failure block so the PR comment still renders and the workflow
# fails cleanly via qc_failed=true.
echo "## respro build QC" >> "$GITHUB_OUTPUT"
echo "" >> "$GITHUB_OUTPUT"
echo "**Source:** \`${{ inputs.source-name }}\`" >> "$GITHUB_OUTPUT"
echo "**Status:** ⚠️ **\`respro init\` FAILED** — the QC step did not produce a summary (infrastructure error before QC ran)." >> "$GITHUB_OUTPUT"
echo "**Exit code:** \`unknown\`" >> "$GITHUB_OUTPUT"
qc_failed="true"
else
cat "$summary_path" >> "$GITHUB_OUTPUT"
if [ -f "${{ inputs.work-dir }}/qc_summary.json" ]; then
status=$(python -c "import json;print(json.load(open('${{ inputs.work-dir }}/qc_summary.json'))['status'])" 2>/dev/null || echo "")
if [ "$status" = "failed" ]; then qc_failed="true"; fi
fi
fi
echo "${EOF}" >> "$GITHUB_OUTPUT"
echo "qc_failed=${qc_failed}" >> "$GITHUB_OUTPUT"
17 changes: 17 additions & 0 deletions .github/workflows/herpesdrg-autobump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ jobs:
run: |
python scripts/build_metadata_manifest.py

- name: respro build QC
id: qc
if: steps.update_check.outputs.should_update == 'true'
uses: ./.github/actions/respro-build-qc
with:
source-name: herpesdrg
output-dir: databases/herpesdrg/output
work-dir: /tmp/respro_qc

- name: No update detected
if: steps.update_check.outputs.should_update != 'true'
run: |
Expand All @@ -122,6 +131,8 @@ jobs:
- Source commit SHA: ${{ steps.source.outputs.source_commit_sha }}
- Source URL: ${{ steps.source.outputs.source_url }}

${{ steps.qc.outputs.summary }}

This PR was generated by the autobump workflow.
labels: autobump
add-paths: |
Expand All @@ -130,3 +141,9 @@ jobs:
databases/herpesdrg/output/metadata.json
databases/herpesdrg/output/non-migrated-rules.txt
databases/manifest.json

- name: Mark workflow failed on respro build error
if: steps.update_check.outputs.should_update == 'true' && steps.qc.outputs.qc_failed == 'true'
run: |
echo "respro build QC failed for herpesdrg; see the PR body and the 'respro build QC' step log."
exit 1
17 changes: 17 additions & 0 deletions .github/workflows/hsv-daehne-jaki-autobump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ jobs:
run: |
python scripts/build_metadata_manifest.py

- name: respro build QC
id: qc
if: steps.update_check.outputs.should_update == 'true'
uses: ./.github/actions/respro-build-qc
with:
source-name: hsv_daehne_jaki
output-dir: databases/hsv_daehne_jaki/output
work-dir: /tmp/respro_qc

- name: No update detected
if: steps.update_check.outputs.should_update != 'true'
run: |
Expand All @@ -139,6 +148,8 @@ jobs:
- Zenodo updated timestamp: ${{ steps.zenodo.outputs.updated }}
- Source file: ${{ steps.zenodo.outputs.source_url }}

${{ steps.qc.outputs.summary }}

This PR was generated by the autobump workflow.
labels: autobump
add-paths: |
Expand All @@ -147,3 +158,9 @@ jobs:
databases/hsv_daehne_jaki/output/metadata.json
databases/hsv_daehne_jaki/output/non-migrated-rules.txt
databases/manifest.json

- name: Mark workflow failed on respro build error
if: steps.update_check.outputs.should_update == 'true' && steps.qc.outputs.qc_failed == 'true'
run: |
echo "respro build QC failed for hsv_daehne_jaki; see the PR body and the 'respro build QC' step log."
exit 1
97 changes: 97 additions & 0 deletions .github/workflows/pr-build-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: PR database build check

on:
pull_request:
paths:
- "databases/**"
- "scripts/respro_build_qc.py"
- ".github/actions/respro-build-qc/**"
- ".github/workflows/pr-build-check.yml"

permissions:
contents: read
pull-requests: write

jobs:
detect-changed-databases:
runs-on: ubuntu-latest
outputs:
herpesdrg: ${{ steps.filter.outputs.herpesdrg }}
hsv_daehne_jaki: ${{ steps.filter.outputs.hsv_daehne_jaki }}
stanford-hiv: ${{ steps.filter.outputs.stanford-hiv }}
any: ${{ steps.filter.outputs.any }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Detect changed databases
id: filter
uses: dorny/paths-filter@v3
with:
# `any` covers shared infrastructure (QC script, composite action,
# this workflow) so a change there triggers a full build check.
filters: |
herpesdrg:
- 'databases/herpesdrg/**'
hsv_daehne_jaki:
- 'databases/hsv_daehne_jaki/**'
stanford-hiv:
- 'databases/stanford-hiv/**'
any:
- 'databases/herpesdrg/**'
- 'databases/hsv_daehne_jaki/**'
- 'databases/stanford-hiv/**'
- 'scripts/respro_build_qc.py'
- '.github/actions/respro-build-qc/**'
- '.github/workflows/pr-build-check.yml'

build-check:
needs: detect-changed-databases
if: needs.detect-changed-databases.outputs.any == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# One job per database; the `if` guard on each step skips databases
# that were not touched by the PR.
database:
- name: herpesdrg
dir: databases/herpesdrg/output
changed: ${{ needs.detect-changed-databases.outputs.herpesdrg }}
- name: hsv_daehne_jaki
dir: databases/hsv_daehne_jaki/output
changed: ${{ needs.detect-changed-databases.outputs.hsv_daehne_jaki }}
- name: stanford-hiv
dir: databases/stanford-hiv/output
changed: ${{ needs.detect-changed-databases.outputs.stanford-hiv }}

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Run respro build QC
if: matrix.database.changed == 'true'
id: qc
uses: ./.github/actions/respro-build-qc
with:
source-name: ${{ matrix.database.name }}
output-dir: ${{ matrix.database.dir }}
work-dir: /tmp/respro_qc_${{ matrix.database.name }}

- name: Comment QC summary on PR
if: matrix.database.changed == 'true' && always()
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
### respro build QC — `${{ matrix.database.name }}`

${{ steps.qc.outputs.summary }}

_Failed builds must be fixed before merge._

- name: Fail on respro build error
if: matrix.database.changed == 'true' && steps.qc.outputs.qc_failed == 'true'
run: |
echo "respro build QC failed for ${{ matrix.database.name }}; see the PR comment and the build logs."
exit 1
19 changes: 18 additions & 1 deletion .github/workflows/stanford-hiv-autobump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@ jobs:
run: |
python scripts/build_metadata_manifest.py

- name: respro build QC
id: qc
if: steps.update_check.outputs.should_update == 'true'
uses: ./.github/actions/respro-build-qc
with:
source-name: stanford-hiv
output-dir: databases/stanford-hiv/output
work-dir: /tmp/respro_qc

- name: No update detected
if: steps.update_check.outputs.should_update != 'true'
run: |
Expand All @@ -142,11 +151,19 @@ jobs:
- Source version: ${{ steps.source.outputs.source_version }}
- Source URL: ${{ steps.source.outputs.source_url }}

${{ steps.qc.outputs.summary }}

This PR was generated by the autobump workflow.
labels: autobump
add-paths: |
databases/stanford-hiv/output/rules.tsv
databases/stanford-hiv/output/formula-rules.tsv
databases/stanford-hiv/output/metadata.json
databases/stanford-hiv/output/non-migrated-rules.txt
databases/manifest.json
databases/manifest.json

- name: Mark workflow failed on respro build error
if: steps.update_check.outputs.should_update == 'true' && steps.qc.outputs.qc_failed == 'true'
run: |
echo "respro build QC failed for stanford-hiv; see the PR body and the 'respro build QC' step log."
exit 1
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ __pycache__/

.pytest_cache/
.mypy_cache/

charmd/
.act/

.DS_Store
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ databases/
manifest.json # generated discovery file, see below

<source_name>/
example/
example.fasta
scripts/
convert.py
requirements.txt
Expand All @@ -45,6 +47,13 @@ It's built from all `databases/*/output/metadata.json` files, and each entry con
name, relative paths to `metadata.json`, `rules.tsv`, and `formula-rules.tsv` (empty when absent),
and the embedded metadata content.

The manifest's top-level `respro_version` field is a PEP 440 version specifier (e.g. `">=0.1.3"`)
stating the minimum ResPro release required to build the listed databases. ResPro clients compare
their own installed version against this specifier and prompt the user to upgrade when too old,
so breaking changes to the TSV contract or metadata schema can be gated on a known ResPro release.
Bump this value in `scripts/build_metadata_manifest.py` (`RESPRO_VERSION_REQUIREMENT`) whenever a
newer ResPro feature becomes a hard requirement.

This is the primariy file that is used to display supported databases via the ResPro command:

```bash
Expand Down
10 changes: 4 additions & 6 deletions databases/herpesdrg/output/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,16 @@
},
{
"name": "drug_interpretation",
"method": "by_phenotype",
"thresholds": {
"resistant": 1,
"intermediate": 1
}
"method": "by_phenotype"
},
{
"name": "drug_interpretation",
"method": "by_fold_ic50",
"thresholds": {
"susceptible": 0.0,
"low-level resistance": 2.0,
"intermediate": 5.0,
"resistant": 15.0
"high-level resistance": 15.0
}
},
{
Expand Down
Loading
Loading