Skip to content

feat: async execution support for Metabolism - #616

Merged
sg-s merged 11 commits into
mainfrom
metabolism-async-execution
Aug 29, 2026
Merged

feat: async execution support for Metabolism#616
sg-s merged 11 commits into
mainfrom
metabolism-async-execution

Conversation

@sg-s

@sg-s sg-s commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds start()/wait()/watch() support to Metabolism (via AsyncExecutableMixin and NotebookWatchMixin) for large ligand batches
  • Replaces the hard 250-ligand cap on run() with a workflow-routing threshold (METABOLISM_WORKFLOW_LIGAND_THRESHOLD = 30) matching platform preflight routing; run() now rejects batches at or above the threshold and directs callers to start()
  • Updates docs and notebook to describe sync vs. async usage

Builds on #615 (already merged).

Merge-ready status

Auto-updated — cycle 5 (final), last updated: 2026-08-28T23:40:00Z

Check Status
Branch vs main ✅ synced at 56f0a4c
Head bd0b8a7
CI ✅ green (required checks; unrelated pre-existing level-1-tests (3.13, prod) Pocket Finder platform-state failure ignored, not required)
Copilot ✅ reviewed bd0b8a7 — 1 comment, addressed & resolved
Review threads 0 human/Bugbot, 0 unresolved Copilot

Merge-ready.

Recent activity

  • Fixed Protein.sync(lazy=True) regression: it was skipping id resolution whenever remote_path was already set, not just when id was already set, which broke SystemPrep.sync_inputs() (CI failure test_sysprep_lv2[backend_only]).
  • Fixed a pytest-markdown-docs failure: the new "Removing unsupported ligands" snippet in ligands.md used a plain fence and got executed against a placeholder path — marked {.python notest} like sibling examples.
  • Quieted a SonarCloud path-injection hotspot on stamp_prepared_protein_pdb's write with a # NOSONAR comment, then fixed its placement (ruff had split the call across lines, moving the comment off the flagged line) — Quality Gate now passes.
  • level-1-tests (3.13, prod) fails on an unrelated, pre-existing "Pocket finder tool not registered" platform-state issue; not a required check, not blocking.
  • Copilot found a real bug: Metabolism.start(quote=True) silently ran the real execution instead of quoting, since _start_impl discarded approve_amount. Fixed by forwarding it into _make_payload, which now matches the Execution base contract and raises when Metabolism (which has no billing/quote path) is asked to quote. Also fixed "1 ligands" → "1 ligand" pluralization.
  • Copilot flagged the pocket_finder tool_version pin bump ("1""2") as undocumented scope creep — confirmed intentional; replied and resolved.

Test plan

  • uv run pytest tests/test_metabolism_local.py tests/test_metabolism_unit.py --env local — 25 passed
  • uv run coverage run -m pytest --env local — 1148 passed, 32 skipped, 4 xpassed (full suite, after fix)

Add start/wait/watch support for large ligand batches, replacing the
hard 250-ligand cap on run() with a workflow-routing threshold at 30
ligands (matching platform preflight routing).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
sg-s added 2 commits August 28, 2026 14:09
Prepared proteins already on UFA were re-uploaded via biotite on
sync(lazy=True), which dropped REMARK 99 and overwrote the stamped file.
Skip upload when remote_path is set, and re-stamp after PDB rewrites.
sg-s added 2 commits August 28, 2026 18:35
Protein.show() was forcing a PDB state dump, which fails for mmCIF
structures with residue names longer than 3 characters. Fall back to
CIF and teach Mol* HTML to load mmcif by path suffix.
Protein.sync(lazy=True) was skipping id resolution whenever remote_path
was already set (not just when id was already set), so
SystemPrep.sync_inputs() got a protein with id=None after a lazy sync
and raised. Lazy now only short-circuits on an existing id; the
upload-skip for an already-set remote_path stays separate.

Also quiets a SonarCloud path-injection hotspot on
stamp_prepared_protein_pdb's write, matching the existing NOSONAR
convention for this kind of caller-supplied local path.
The new "Removing unsupported ligands" snippet in ligands.md used a
plain python fence, so pytest-markdown-docs ran it and failed on the
placeholder path/to/ligands.csv. Mark it {.python notest} like the
other placeholder-path examples in this repo (pocketfinder.md,
patent.md).
SonarCloud's path-injection annotation targets the write_bytes call's
start line; the ruff-formatted multi-line call put the NOSONAR comment
on the closing-paren line instead, so the suppression didn't take.
Keep the call on one line so the comment lands where Sonar looks.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the Drug Discovery CLI SDK to support asynchronous execution for Metabolism (with a workflow-routing threshold), while also improving structure handling (Prepared Protein stamp preservation and mmCIF support) and tightening related tooling/docs/tests.

Changes:

  • Add async start() support for Metabolism, and gate run() to only allow batches smaller than METABOLISM_WORKFLOW_LIGAND_THRESHOLD (30).
  • Preserve the Prepared Protein REMARK 99 DO_PREPARED stamp through Protein.to_pdb() rewrites and avoid overwriting stamped remote objects during Protein.register()/sync().
  • Add mmCIF writing/fallback paths for visualization (Protein.to_cif(), _dump_state() choosing PDB vs CIF) and update Mol* HTML rendering to choose the correct format.

Reviewed changes

Copilot reviewed 28 out of 29 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
zensical.toml Adds docs nav entry for the Prepared Protein stamp reference page.
tests/test_protein.py Adds coverage for _dump_state() PDB vs CIF behavior and to_cif().
tests/test_prepared_protein_stamp.py Adds unit tests for stamp detection/preservation and sync/register behaviors with remote-only proteins.
tests/test_pocket_finder.py Verifies PocketFinder payload omits null protein IDs and supports file-path-only proteins from DTOs.
tests/test_molstar_html.py Adds tests for Mol* structure-format mapping and CIF handling in rendered HTML.
tests/test_metabolism_unit.py Updates unit tests for new threshold behavior and default execution naming/payload.
tests/test_metabolism_local.py Adds mock-server integration tests for Metabolism.start() and async polling behavior.
tests/test_ligand_set.py Adds tests for LigandSet.remove_unsupported() and improved sync error messaging.
tests/mock_server/server.py Tunes mock execution duration for metabolism to support async local tests.
tests/mock_server/routers/tools.py Adds async DTO support and result injection for metabolism executions.
src/viz/molstar_html.py Adds _molstar_structure_format() and uses it so Mol* loads PDB vs mmCIF correctly.
src/utils/constants.py Introduces PREPARED_PROTEIN_STAMP_LINE and replaces metabolism cap with workflow threshold constant.
src/platform/constants.py Bumps pocket-finder tool version.
src/drug_discovery/structures/protein.py Adds PDB-compat detection, to_cif(), stamp preservation in to_pdb(), and upload-skipping behavior when remote_path is already set.
src/drug_discovery/structures/prepared_protein_stamp.py New helper module for detecting/stamping REMARK 99 DO_PREPARED.
src/drug_discovery/structures/ligand.py Adds LigandSet.remove_unsupported() and improves sync error guidance.
src/drug_discovery/pocket_finder.py Uses _protein_tool_input() so payload omits null IDs; DTO rehydration accepts file-path-only proteins.
src/drug_discovery/metabolism.py Adds async execution support, naming, and threshold gating for sync run().
src/drug_discovery/constants.py Adds a dedicated CIF dump path constant.
src/drug_discovery/class-design.md Updates design notes to reflect Metabolism’s dual-mode execution.
docs/notebooks/clean/metabolism.ipynb Updates notebook narrative/examples for sync vs async Metabolism usage.
docs/dd/tools/proteinprep.md Documents Prepared Protein stamp implications and preservation guidance.
docs/dd/tools/metabolism.md Adds guidance for large-batch async execution.
docs/dd/ref/prepared_protein_stamp.md New reference page for stamp helpers.
docs/dd/ref/metabolism.md Updates reference text for threshold and async path.
docs/dd/how-to/proteins.md Documents to_cif() and notes show() CIF fallback.
docs/dd/how-to/ligands.md Documents remove_unsupported() and updates Metabolism guidance for large batches.
CONTEXT.md Updates project context to reflect workflow threshold and async usage for Metabolism.
.gitignore Ignores untitled notebooks.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/drug_discovery/metabolism.py Outdated
Comment thread src/drug_discovery/metabolism.py Outdated
Comment thread src/drug_discovery/metabolism.py Outdated
Metabolism._start_impl discarded approve_amount instead of forwarding
it, so start(quote=True) silently ran the real (billed) execution
instead of quoting. Metabolism has no quote/billing path by design
(see CONTEXT.md), so _make_payload and _start_impl now raise a clear
ValueError when approve_amount is not None, matching the Execution
base class's _make_payload(*, approve_amount, sync) contract that
Docking/Admet already follow.

Also fixes "Site of Metabolism for 1 ligands" -> "1 ligand".

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 29 changed files in this pull request and generated no new comments.

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

tests/test_protein.py:38

  • The tmp_path fixture is accepted but never used in this test, which can trigger unused-argument linting and makes the intent unclear. If you don't need it, drop it; if you want the fixture for future expansion, prefix it with _ to mark it intentionally unused.
def test_dump_state_writes_cif_when_resnames_exceed_pdb_limit(tmp_path: Path) -> None:

tests/test_metabolism_local.py:213

  • The timeout loop tracks elapsed += poll_interval, which doesn’t include time spent in job.sync() and can exceed timeout_seconds under slow responses. Using time.monotonic() (or job.wait(timeout=..., poll_interval=...)) makes the timeout behavior accurate and less flaky.
    elapsed = 0.0
    while elapsed < timeout_seconds:
        job.sync()
        if job.status in TERMINAL_STATES:
            break

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 30 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

tests/test_protein.py:38

  • The tmp_path fixture is declared but never used in this test, which adds noise and may trigger unused-argument linting depending on the repo’s ruff settings. Remove the parameter (or use it to isolate the dump path if that was the intent).
def test_dump_state_writes_cif_when_resnames_exceed_pdb_limit(tmp_path: Path) -> None:

Comment thread src/platform/constants.py
@sg-s sg-s added the merge-ready All merge-ready checks passed label Aug 28, 2026
@sg-s
sg-s merged commit 35c4b8c into main Aug 29, 2026
13 of 14 checks passed
@sg-s
sg-s deleted the metabolism-async-execution branch August 29, 2026 00:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-ready All merge-ready checks passed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants