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
15 changes: 8 additions & 7 deletions src/raven_toolbox/io/yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,8 +621,10 @@ def _collapse_singleton_annotations(entries) -> None:
Matches writeYAMLmodel.m's MIRIAM handling: every annotation entry
collapses to a scalar when it carries a single value, except
``ec-code``/``smiles`` — RAVEN's own writer marks exactly those two
``forceList`` — which stay a list even for one value, since cobra and
geckopy read them as ``list[str]``. Normalises in place.
``forceList``, which are always a list, even for one value, since cobra and
geckopy read them as ``list[str]``. cobra stores a single identifier as a
bare string, so a string under one of those keys is wrapped in a list.
Normalises in place.
"""
for entry in entries:
if not isinstance(entry, dict):
Expand All @@ -631,11 +633,10 @@ def _collapse_singleton_annotations(entries) -> None:
if not isinstance(annotation, dict):
continue
for key, value in annotation.items():
if (
key not in _LIST_ONLY_ANNOTATION_KEYS
and isinstance(value, list)
and len(value) == 1
):
if key in _LIST_ONLY_ANNOTATION_KEYS:
if isinstance(value, str):
annotation[key] = [value]
elif isinstance(value, list) and len(value) == 1:
annotation[key] = value[0]


Expand Down
26 changes: 26 additions & 0 deletions tests/test_io_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,32 @@ def test_needs_quote_leaves_longer_words_plain(value):
assert not _needs_quote(value)


def test_single_ec_code_and_smiles_are_written_as_lists(tmp_path):
# cobra stores a one-identifier annotation as a bare string; RAVEN's writer forces
# ec-code and smiles to a list even for one value, and other keys stay scalars.
m = cobra.Model("m")
a = cobra.Metabolite("a_c", compartment="c", name="a")
b = cobra.Metabolite("b_c", compartment="c", name="b")
a.annotation = {"smiles": "C1=NC2", "kegg.compound": "C00002"}
r = cobra.Reaction("R1")
m.add_reactions([r])
r.add_metabolites({a: -1, b: 1})
r.annotation = {"ec-code": "1.1.1.1", "kegg.reaction": "R00001"}

out = tmp_path / "m.yml"
write_yaml_model(m, out)
lines = out.read_text().splitlines()

assert lines[lines.index(" - ec-code:") + 1] == " - 1.1.1.1"
assert lines[lines.index(" - smiles:") + 1] == " - C1=NC2"
assert " - kegg.compound: C00002" in lines
assert " - kegg.reaction: R00001" in lines

back = read_yaml_model(out)
assert back.reactions.R1.annotation["ec-code"] == ["1.1.1.1"]
assert back.metabolites.a_c.annotation["smiles"] == ["C1=NC2"]


def _edge_model():
m = cobra.Model("edge")
a = cobra.Metabolite("A_c", name="A", compartment="c")
Expand Down
Loading