Skip to content

Call build_base_model in MSBuilder.build_metabolic_model - #159

Open
jplfaria wants to merge 1 commit into
ModelSEED:devfrom
jplfaria:fix/build-metabolic-model-base-build
Open

jplfaria wants to merge 1 commit into
ModelSEED:devfrom
jplfaria:fix/build-metabolic-model-base-build

Conversation

@jplfaria

@jplfaria jplfaria commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Why this matters in our workflow

MSBuilder.build_metabolic_model cannot build any model on dev. The example notebook
examples/Model Reconstruction/build_metabolic_model.ipynb and the API docs (docs/source/core/msbuilder_api.rst)
both use it. We call build() and build_base_model directly, so our own builds are not affected. We found this
while reviewing dev's builder.

What happens

The builder refactor (f2de5f2, Sep 2025) renamed the old
build(model_or_id, index, allow_all_non_grp_reactions, annotate_with_rast, biomass_classic, biomass_gc, ...) to
build_base_model. It added a new build(model_or_id, gapfill_media, index, ...), which runs ATP correction and
gap-fill and returns a 4-tuple.
build_metabolic_model
still calls build() with the old positional arguments:

model = builder.build(model_id, index, allow_all_non_grp_reactions, annotate_with_rast, classic_biomass)

So every argument lands one slot off: index goes in as gapfill_media, allow_all_non_grp_reactions as index,
and so on. The base model then gets compartment index False (reactions named like rxn05294_cFalse), and
build_biomass raises TypeError: can only concatenate str (not "bool") to str. If that step passed, build() would
run ATP correction and gap-fill with "0" as the medium and return a tuple, which build_metabolic_model would then
pass to gapfill_model as a model.

How to reproduce

The reproduction is a three-gene genome built on the public GramNeg template (ModelSEEDTemplates v7.0):

import json
from modelseedpy import MSBuilder, MSGenome
from modelseedpy.core.msgenome import MSFeature
from modelseedpy.core.mstemplate import MSTemplateBuilder

template = MSTemplateBuilder.from_dict(json.load(open("GramNegModelTemplateV7.json"))).build()
genome = MSGenome()
features = []
for i, function in enumerate(["Pyruvate kinase (EC 2.7.1.40)", "Enolase (EC 4.2.1.11)",
                              "Phosphoglycerate kinase (EC 2.7.2.3)"]):
    f = MSFeature(f"g{i}", "M"); f.add_ontology_term("RAST", function); features.append(f)
genome.add_features(features)
MSBuilder.build_metabolic_model("toy", genome, template=template, annotate_with_rast=False, gapfill_model=False)

dev:

  File ".../modelseedpy/core/mstemplate.py", line 737, in build_biomass
    if "rxn13783_c" + index in model.reactions:
TypeError: can only concatenate str (not "bool") to str

On this branch, the call returns a model with 48 reactions.

The fix

The fix calls build_base_model with the same arguments. They are already in build_base_model's order, which is the
pre-refactor build() signature, so this restores the behaviour from before f2de5f2. gapfill_model then gap-fills
as before.

         builder = MSBuilder(genome, template)
-        model = builder.build(
+        model = builder.build_base_model(
             model_id,

Not changed here: build_from_annotaton_ontology. It has the same stale self.build(...) call
(L791),
but fixing that call alone would not make it work. Right after it:

  • it calls rxn.genes(), but cobra's Reaction.genes is a frozenset, so this raises TypeError: 'frozenset' object is not callable;
  • it calls anno_ont.get_feature(...), but AnnotationOntology has no such method;
  • it indexes gene_term_hash with cobra genes, but the keys are annotation-ontology features.

That needs its own change and a decision on how the function should work.

No package code calls build_metabolic_model. The example notebook calls it with keywords, and they work after the fix.

Tests

tests/core/test_msbuilder.py::test_build_metabolic_model_builds_base_model monkeypatches build_base_model to
record its arguments. It checks that build_metabolic_model passes (model_id, index, allow_all_non_grp_reactions, annotate_with_rast, classic_biomass) and returns the model.

Against dev's code, the stub's empty model reaches add_atpm inside build():

E       KeyError: 'cpd00001_c0'
E           AttributeError: DictList has no attribute or entry cpd00001_c0
1 failed

With the fix, it passes. Full suite (Python 3.10, cobra 0.32.1, CPLEX 22.1.1): tests/core goes from 56 to 57
passed, and tests/ from 58 to 59. black 22.8.0 and the other pre-commit hooks pass.

Real-model check

We ran this on a genome-scale E. coli K-12 genome (RAST annotations, GramNeg template v7.1):

  • With gapfill_model=False, the result (1,828 reactions) is identical to
    build_base_model("Keio", "0", False, False, False) in reaction ids, bounds, GPRs and equations.
  • With the default gapfill_model=True on minimal glucose, dev raises the same TypeError. This branch returns
    1,834 reactions, and the model grows on glucose (0.7685). That growth comes from gapfill_model's simple gap-fill,
    without ATP tests, as before the refactor.

Related

🤖 Generated with Claude Code

The builder refactor (f2de5f2) renamed the old build() to
build_base_model() and added a new build(model_or_id, gapfill_media, ...)
that runs ATP correction and gapfilling and returns a tuple.
build_metabolic_model kept calling build() with the old positional
arguments, so index went in as gapfill_media, allow_all_non_grp_reactions
as index, and so on; the call fails (e.g. compartment ids like "cFalse",
then a TypeError in build_biomass).

Call build_base_model with the same arguments, which is what this function
did before the refactor; gapfill_model then gapfills as before.

Test: build_metabolic_model passes its arguments to build_base_model and
returns the model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant