Skip to content
Draft
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
12 changes: 11 additions & 1 deletion src/job_scripts/reconstruct.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ def merge_ws_metadata(ws, obj_path, new_meta):
ws.update_metadata({"objects": [[obj_path, merged]]})


def _set_biomass_objective(model) -> None:
"""Set the model objective to bio1 if present, otherwise fall back to the first
biomass-like reaction. Silently skips if no biomass reaction is found."""
if "bio1" in {r.id for r in model.reactions}:
model.objective = "bio1"
return
biomass_rxns = [r for r in model.reactions if "bio" in r.id.lower()]
if biomass_rxns:
model.objective = biomass_rxns[0].id

def main():
parser = argparse.ArgumentParser(description="Model reconstruction job")
parser.add_argument("--job-id", required=True)
Expand Down Expand Up @@ -346,7 +356,7 @@ def main():
ws = get_storage_service(args.token)

import cobra.io
mdlutl.model.objective = "bio1"
_set_biomass_objective(mdlutl.model)
cobra_json = json.dumps(cobra.io.model_to_dict(mdlutl.model))

if not hasattr(mdlutl.model, 'get_data'):
Expand Down
17 changes: 12 additions & 5 deletions src/modelseed_api/jobs/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,16 @@ def _fix_gapfilling_metadata(ws_data: dict, media_workspace_ref: str | None) ->
gd[new_id] = gd.pop(old_id)


def _set_biomass_objective(model) -> None:
"""Set the model objective to bio1 if present, otherwise fall back to the first
biomass-like reaction. Silently skips if no biomass reaction is found."""
if "bio1" in {r.id for r in model.reactions}:
model.objective = "bio1"
return
biomass_rxns = [r for r in model.reactions if "bio" in r.id.lower()]
if biomass_rxns:
model.objective = biomass_rxns[0].id

@app.task(bind=True, name="modelseed.reconstruct")
def reconstruct(
self,
Expand Down Expand Up @@ -731,7 +741,7 @@ def reconstruct(
# Save cobra JSON BEFORE CobraModelConverter (which loses exchange
# bounds and breaks FBA). cobra.io roundtrip is lossless.
import cobra.io
mdlutl.model.objective = "bio1"
_set_biomass_objective(mdlutl.model)
cobra_json = json.dumps(cobra.io.model_to_dict(mdlutl.model))

if not hasattr(mdlutl.model, 'get_data'):
Expand Down Expand Up @@ -1262,10 +1272,7 @@ def _reconstruct_one_genome(
if do_fva:
# Configure model objective for FVA; both runs use the same model
# but with different media bounds applied (handled by media-pkg).
try:
mdlutl.model.objective = "bio1"
except Exception:
pass
_set_biomass_objective(mdlutl.model)
try:
fva_rich = compute_fva_classes(mdlutl.model, media_or_setup="rich")
fva_min = compute_fva_classes(mdlutl.model, media_or_setup="minimal")
Expand Down
49 changes: 49 additions & 0 deletions tests/unit/test_set_biomass_objective.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Unit tests for _set_biomass_objective helper."""
import pytest

pytestmark = pytest.mark.unit


def _make_mock_model(reaction_ids):
"""Build a minimal mock cobra Model with the given reaction IDs."""
import unittest.mock as mock

reactions = []
for rid in reaction_ids:
r = mock.MagicMock()
r.id = rid
r.__str__ = lambda self: self.id
reactions.append(r)

model = mock.MagicMock()
model.reactions = reactions
model.objective = None
return model


class TestSetBiomassObjective:
def test_bio1_present_sets_bio1(self):
from modelseed_api.jobs.tasks import _set_biomass_objective
model = _make_mock_model(["rxn00001", "bio1", "rxn00002"])
_set_biomass_objective(model)
assert model.objective == "bio1"

def test_no_bio1_falls_back_to_biomass_reaction(self):
from modelseed_api.jobs.tasks import _set_biomass_objective
model = _make_mock_model(["rxn00001", "bio2", "rxn00002"])
_set_biomass_objective(model)
assert model.objective == "bio2"

def test_no_biomass_reaction_skips_silently(self):
from modelseed_api.jobs.tasks import _set_biomass_objective
model = _make_mock_model(["rxn00001", "rxn00002"])
original = model.objective
_set_biomass_objective(model)
assert model.objective == original

def test_empty_model_skips_silently(self):
from modelseed_api.jobs.tasks import _set_biomass_objective
model = _make_mock_model([])
original = model.objective
_set_biomass_objective(model)
assert model.objective == original