Skip to content

Commit

Permalink
refactor: change ordering behaviour of dict-like model outputs
Browse files Browse the repository at this point in the history
  • Loading branch information
Midnighter committed Oct 28, 2017
1 parent 17b5b77 commit 5fe7a6e
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ matrix:
before_install:
- if [[ -n "${MB_PYTHON_VERSION}" ]]; then
(travis_retry git clone https://github.com/matthew-brett/multibuild.git && cd multibuild && git checkout 37040e31b1044468027bd86991c805006a92bf09);
TEST_DEPENDS="swiglpk optlang sympy decorator cython codecov coverage numpy scipy jsonschema six pytest pytest-cov pytest-benchmark tabulate";
TEST_DEPENDS="swiglpk optlang sympy decorator cython codecov coverage numpy scipy==0.19.1 jsonschema six pytest pytest-cov pytest-benchmark tabulate";
BUILD_DEPENDS="swiglpk optlang sympy cython numpy scipy";
source multibuild/common_utils.sh;
source multibuild/travis_steps.sh;
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ install:
conda install -q pip } else {
python -m pip install pip setuptools>=24.0 wheel --upgrade }
- if not defined CONDA %WITH_COMPILER% python -m pip install --upgrade pytest
- if defined CONDA conda install -q setuptools pytest numpy scipy
- if defined CONDA conda install -q setuptools pytest numpy scipy==0.19.1
- python -m pip install pytest-cov pytest-benchmark pandas swiglpk optlang python-libsbml decorator Cython jsonschema twine pypandoc
- "%WITH_COMPILER% python appveyor/build_glpk.py"

Expand Down
25 changes: 15 additions & 10 deletions cobra/io/dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ def _fix_type(value):
return bool(value)
if isinstance(value, set):
return list(value)
if isinstance(value, dict):
return OrderedDict((key, value[key]) for key in sorted(value))
# handle legacy Formula type
if value.__class__.__name__ == "Formula":
return str(value)
Expand Down Expand Up @@ -145,13 +147,16 @@ def reaction_from_dict(reaction, model):
return new_reaction


def model_to_dict(model):
def model_to_dict(model, sort=False):
"""Convert model to a dict.
Parameters
----------
model : cobra.Model
The model to reformulate as a dict
The model to reformulate as a dict.
sort : bool, optional
Whether to sort the metabolites, reactions, and genes or maintain the
order defined in the model.
Returns
-------
Expand All @@ -166,17 +171,17 @@ def model_to_dict(model):
cobra.io.model_from_dict
"""
obj = OrderedDict()
obj["reactions"] = sorted(
(reaction_to_dict(reaction) for reaction in model.reactions),
key=itemgetter("id"))
obj["metabolites"] = sorted(
(metabolite_to_dict(metabolite) for metabolite in model.metabolites),
key=itemgetter("id"))
obj["genes"] = sorted(
(gene_to_dict(gene) for gene in model.genes), key=itemgetter("id"))
obj["metabolites"] = list(map(metabolite_to_dict, model.metabolites))
obj["reactions"] = list(map(reaction_to_dict, model.reactions))
obj["genes"] = list(map(gene_to_dict, model.genes))
obj["id"] = model.id
_update_optional(model, obj, _OPTIONAL_MODEL_ATTRIBUTES,
_ORDERED_OPTIONAL_MODEL_KEYS)
if sort:
get_id = itemgetter("id")
obj["metabolites"].sort(key=get_id)
obj["reactions"].sort(key=get_id)
obj["genes"].sort(key=get_id)
return obj


Expand Down
14 changes: 10 additions & 4 deletions cobra/io/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
JSON_SPEC = "1"


def to_json(model, **kwargs):
def to_json(model, sort=False, **kwargs):
"""
Return the model as a JSON document.
Expand All @@ -25,6 +25,9 @@ def to_json(model, **kwargs):
----------
model : cobra.Model
The cobra model to represent.
sort : bool, optional
Whether to sort the metabolites, reactions, and genes or maintain the
order defined in the model.
Returns
-------
Expand All @@ -36,7 +39,7 @@ def to_json(model, **kwargs):
save_json_model : Write directly to a file.
json.dumps : Base function.
"""
obj = model_to_dict(model)
obj = model_to_dict(model, sort=sort)
obj[u"version"] = JSON_SPEC
return json.dumps(obj, allow_nan=False, **kwargs)

Expand All @@ -62,7 +65,7 @@ def from_json(document):
return model_from_dict(json.loads(document))


def save_json_model(model, filename, pretty=False, **kwargs):
def save_json_model(model, filename, sort=False, pretty=False, **kwargs):
"""
Write the cobra model to a file in JSON format.
Expand All @@ -75,6 +78,9 @@ def save_json_model(model, filename, pretty=False, **kwargs):
filename : str or file-like
File path or descriptor that the JSON representation should be
written to.
sort : bool, optional
Whether to sort the metabolites, reactions, and genes or maintain the
order defined in the model.
pretty : bool, optional
Whether to format the JSON more compactly (default) or in a more
verbose but easier to read fashion. Can be partially overwritten by the
Expand All @@ -85,7 +91,7 @@ def save_json_model(model, filename, pretty=False, **kwargs):
to_json : Return a string representation.
json.dump : Base function.
"""
obj = model_to_dict(model)
obj = model_to_dict(model, sort=sort)
obj[u"version"] = JSON_SPEC

if pretty:
Expand Down
14 changes: 10 additions & 4 deletions cobra/io/yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
YAML_SPEC = "1"


def to_yaml(model, **kwargs):
def to_yaml(model, sort=False, **kwargs):
"""
Return the model as a YAML document.
Expand All @@ -22,6 +22,9 @@ def to_yaml(model, **kwargs):
----------
model : cobra.Model
The cobra model to represent.
sort : bool, optional
Whether to sort the metabolites, reactions, and genes or maintain the
order defined in the model.
Returns
-------
Expand All @@ -33,7 +36,7 @@ def to_yaml(model, **kwargs):
save_yaml_model : Write directly to a file.
ruamel.yaml.dump : Base function.
"""
obj = model_to_dict(model)
obj = model_to_dict(model, sort=sort)
obj["version"] = YAML_SPEC
return yaml.dump(obj, Dumper=yaml.RoundTripDumper, **kwargs)

Expand All @@ -59,7 +62,7 @@ def from_yaml(document):
return model_from_dict(yaml.load(document, yaml.RoundTripLoader))


def save_yaml_model(model, filename, **kwargs):
def save_yaml_model(model, filename, sort=False, **kwargs):
"""
Write the cobra model to a file in YAML format.
Expand All @@ -72,13 +75,16 @@ def save_yaml_model(model, filename, **kwargs):
filename : str or file-like
File path or descriptor that the YAML representation should be
written to.
sort : bool, optional
Whether to sort the metabolites, reactions, and genes or maintain the
order defined in the model.
See Also
--------
to_yaml : Return a string representation.
ruamel.yaml.dump : Base function.
"""
obj = model_to_dict(model)
obj = model_to_dict(model, sort=sort)
obj["version"] = YAML_SPEC
if isinstance(filename, string_types):
with io.open(filename, "w") as file_handle:
Expand Down
10 changes: 6 additions & 4 deletions cobra/test/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,12 @@ def compare_models(cls, name, model1, model2):
@classmethod
def extra_comparisons(cls, name, model1, model2):
assert model1.compartments == model2.compartments
assert model1.metabolites[4].annotation == model2.metabolites[
4].annotation
assert model1.reactions[4].annotation == model2.reactions[4].annotation
assert model1.genes[5].annotation == model2.genes[5].annotation
assert dict(model1.metabolites[4].annotation) == dict(
model2.metabolites[4].annotation)
assert dict(model1.reactions[4].annotation) == dict(
model2.reactions[4].annotation)
assert dict(model1.genes[5].annotation) == dict(
model2.genes[5].annotation)
for attr in ("id", "name"):
assert getattr(model1.genes[0], attr) == getattr(model2.genes[0],
attr)
Expand Down
92 changes: 92 additions & 0 deletions cobra/test/test_io_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-

from __future__ import absolute_import

import logging
from operator import attrgetter
from os.path import join
from random import sample

import pytest

import cobra.io as cio
from cobra import DictList, Model

LOGGER = logging.getLogger(__name__)


@pytest.fixture(scope="module")
def tmp_path(tmpdir_factory):
return str(tmpdir_factory.mktemp("model_order"))


@pytest.fixture(scope="module")
def minimized_shuffle(small_model):
model = small_model.copy()
chosen = sample(list(set(model.reactions) - set(model.exchanges)), 10)
new = Model("minimized_shuffle")
new.add_reactions(chosen)
LOGGER.debug("'%s' has %d metabolites, %d reactions, and %d genes.",
new.id, new.metabolites, new.reactions, new.genes)
return new


@pytest.fixture(scope="module")
def minimized_sorted(minimized_shuffle):
model = minimized_shuffle.copy()
model.id = "minimized_sorted"
model.metabolites = DictList(
sorted(model.metabolites, key=attrgetter("id")))
model.genes = DictList(sorted(model.genes, key=attrgetter("id")))
model.reactions = DictList(sorted(model.reactions, key=attrgetter("id")))
return model


@pytest.fixture(scope="module")
def minimized_reverse(minimized_shuffle):
model = minimized_shuffle.copy()
model.id = "minimized_reverse"
model.metabolites = DictList(
sorted(model.metabolites, key=attrgetter("id"), reverse=True))
model.genes = DictList(
sorted(model.genes, key=attrgetter("id"), reverse=True))
model.reactions = DictList(
sorted(model.reactions, key=attrgetter("id"), reverse=True))
return model


@pytest.fixture(scope="module", params=[
"minimized_shuffle", "minimized_reverse", "minimized_sorted"])
def template(request, minimized_shuffle, minimized_reverse, minimized_sorted):
return locals()[request.param]


@pytest.fixture(scope="module", params=["metabolites", "reactions", "genes"])
def attribute(request):
return request.param


def get_ids(iterable):
return [x.id for x in iterable]


@pytest.mark.parametrize("read, write, ext", [
("read_sbml_model", "write_sbml_model", ".xml"),
pytest.mark.skip(("read_legacy_sbml", "write_legacy_sbml", ".xml"),
reason="Order for legacy SBML I/O is uninteresting."),
pytest.mark.skip(("load_matlab_model", "save_matlab_model", ".mat"),
reason="Order for Matlab model I/O is uninteresting."),
("load_json_model", "save_json_model", ".json"),
("load_yaml_model", "save_yaml_model", ".yml"),
])
def test_io_order(attribute, read, write, ext, template, tmp_path):
read = getattr(cio, read)
write = getattr(cio, write)
filename = join(tmp_path, "template" + ext)
write(template, filename)
model = read(filename)
model_elements = get_ids(getattr(model, attribute))
template_elements = get_ids(getattr(template, attribute))
assert len(model_elements) == len(template_elements)
assert set(model_elements) == set(template_elements)
assert model_elements == template_elements
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def build_extension(self, ext):
extras = {
'matlab': ["pymatbridge"],
'sbml': ["python-libsbml", "lxml"],
'array': ["scipy>=0.11.0"],
'array': ["scipy==0.19.1"],
'display': ["matplotlib", "palettable"]
}

Expand Down

0 comments on commit 5fe7a6e

Please sign in to comment.