from __future__ import absolute_import
import json
from warnings import warn
from .. import Model, Metabolite, Reaction, Gene
from six import iteritems, string_types
# Detect numpy types to replace them.
try:
from numpy import float_, bool_
except ImportError:
class float_:
pass
class bool_:
pass
_REQUIRED_REACTION_ATTRIBUTES = {"id", "name", "metabolites", "lower_bound",
"upper_bound", "gene_reaction_rule"}
_OPTIONAL_REACTION_ATTRIBUTES = {
"objective_coefficient": 0,
"variable_kind": "continuous",
"subsystem": "",
"notes": {},
"annotation": {},
}
_REQUIRED_METABOLITE_ATTRIBUTES = {"id", "name", "compartment"}
_OPTIONAL_METABOLITE_ATTRIBUTES = {
"charge": None,
"formula": None,
"_bound": 0,
"_constraint_sense": "E",
"notes": {},
"annotation": {},
}
_REQUIRED_GENE_ATTRIBUTES = {"id", "name"}
_OPTIONAL_GENE_ATTRIBUTES = {
"notes": {},
"annotation": {},
}
_OPTIONAL_MODEL_ATTRIBUTES = {
"name": None,
# "description": None, should not actually be included
"compartments": {},
"notes": {},
"annotation": {},
}
def _fix_type(value):
"""convert possible types to str, float, and bool"""
# Because numpy floats can not be pickled to json
if isinstance(value, string_types):
return str(value)
if isinstance(value, float_):
return float(value)
if isinstance(value, bool_):
return bool(value)
# handle legacy Formula type
if value.__class__.__name__ == "Formula":
return str(value)
if value is None:
return ''
return value
def _from_dict(obj):
"""build a model from a dict"""
if 'reactions' not in obj:
raise Exception('JSON object has no reactions attribute. Cannot load.')
model = Model()
# add metabolites
new_metabolites = []
for metabolite in obj['metabolites']:
new_metabolite = Metabolite()
for k, v in iteritems(metabolite):
setattr(new_metabolite, k, v)
new_metabolites.append(new_metabolite)
model.add_metabolites(new_metabolites)
# add genes
for gene in obj['genes']:
new_gene = Gene(gene["id"])
for k, v in iteritems(gene):
setattr(new_gene, k, _fix_type(v))
model.genes.append(new_gene)
# add reactions
new_reactions = []
for reaction in obj['reactions']:
new_reaction = Reaction()
for k, v in iteritems(reaction):
if k == 'reversibility' or k == "reaction":
continue
elif k == 'metabolites':
new_reaction.add_metabolites(
{model.metabolites.get_by_id(str(met)): coeff
for met, coeff in iteritems(v)})
else:
setattr(new_reaction, k, _fix_type(v))
new_reactions.append(new_reaction)
model.add_reactions(new_reactions)
for k, v in iteritems(obj):
if k in {'id', 'name', 'notes', 'compartments', 'annotation'}:
setattr(model, k, v)
return model
def _update_optional(cobra_object, new_dict, optional_attribute_dict):
"""update new_dict with optional attributes from cobra_object"""
for key, default_value in iteritems(optional_attribute_dict):
value = getattr(cobra_object, key)
if value is not None and value != default_value:
new_dict[key] = _fix_type(value)
def _to_dict(model):
"""convert the model to a dict"""
new_reactions = []
new_metabolites = []
new_genes = []
for reaction in model.reactions:
new_reaction = {key: _fix_type(getattr(reaction, key))
for key in _REQUIRED_REACTION_ATTRIBUTES
if key != "metabolites"}
_update_optional(reaction, new_reaction, _OPTIONAL_REACTION_ATTRIBUTES)
# set metabolites
mets = {str(met): coeff for met, coeff
in iteritems(reaction._metabolites)}
new_reaction['metabolites'] = mets
new_reactions.append(new_reaction)
for metabolite in model.metabolites:
new_metabolite = {key: _fix_type(getattr(metabolite, key))
for key in _REQUIRED_METABOLITE_ATTRIBUTES}
_update_optional(metabolite, new_metabolite,
_OPTIONAL_METABOLITE_ATTRIBUTES)
new_metabolites.append(new_metabolite)
for gene in model.genes:
new_gene = {key: str(getattr(gene, key))
for key in _REQUIRED_GENE_ATTRIBUTES}
_update_optional(gene, new_gene, _OPTIONAL_GENE_ATTRIBUTES)
new_genes.append(new_gene)
obj = {'reactions': new_reactions,
'metabolites': new_metabolites,
'genes': new_genes,
'id': model.id,
}
_update_optional(model, obj, _OPTIONAL_MODEL_ATTRIBUTES)
# add in the JSON version
obj["version"] = 1
return obj
[docs]def to_json(model):
"""Save the cobra model as a json string"""
return json.dumps(_to_dict(model), allow_nan=False)
[docs]def from_json(jsons):
"""Load cobra model from a json string"""
return _from_dict(json.loads(jsons))
[docs]def load_json_model(file_name):
"""Load a cobra model stored as a json file
file_name : str or file-like object
"""
# open the file
should_close = False
if isinstance(file_name, string_types):
file_name = open(file_name, 'r')
should_close = True
model = _from_dict(json.load(file_name))
if should_close:
file_name.close()
return model
[docs]def save_json_model(model, file_name, pretty=False):
"""Save the cobra model as a json file.
model : :class:`~cobra.core.Model.Model` object
file_name : str or file-like object
"""
# open the file
should_close = False
if isinstance(file_name, string_types):
file_name = open(file_name, 'w')
should_close = True
if pretty:
dump_opts = {"indent": 4, "separators": (",", ": "), "sort_keys": True}
else:
dump_opts = {}
json.dump(_to_dict(model), file_name, allow_nan=False, **dump_opts)
if should_close:
file_name.close()
json_schema = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "COBRA",
"description": "JSON representation of COBRA model",
"type": "object",
"properties": {
"id": {"type": "string"},
"name": {"type": "string"},
"description": {"type": "string"},
"version": {
"type": "integer",
"default": 1,
},
"reactions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "string"},
"name": {"type": "string"},
"metabolites": {
"type": "object",
"patternProperties": {
".*": {"type": "number"},
}
},
"gene_reaction_rule": {"type": "string"},
"lower_bound": {"type": "number"},
"upper_bound": {"type": "number"},
"objective_coefficient": {
"type": "number",
"default": 0,
},
"variable_kind": {
"type": "string",
"pattern": "integer|continuous",
"default": "continuous"
},
"subsystem": {"type": "string"},
"notes": {"type": "object"},
"annotation": {"type": "object"},
},
"required": ["id", "name", "metabolites", "lower_bound",
"upper_bound", "gene_reaction_rule"],
"additionalProperties": False,
}
},
"metabolites": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "string"},
"name": {"type": "string"},
"compartment": {
"type": "string",
"pattern": "[a-z]{1,2}"
},
"charge": {"type": "integer"},
"formula": {"type": "string"},
"_bound": {
"type": "number",
"default": 0
},
"_constraint_sense": {
"type": "string",
"default": "E",
"pattern": "E|L|G",
},
"notes": {"type": "object"},
"annotation": {"type": "object"},
},
"required": ["id", "name", "compartment"],
"additionalProperties": False,
}
},
"genes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "string"},
"name": {"type": "string"},
"notes": {"type": "object"},
"annotation": {"type": "object"},
},
"required": ["id", "name"],
"additionalProperties": False,
}
},
"compartments": {
"type": "object",
"patternProperties": {
"[a-z]{1,2}": {"type": "string"}
}
},
"notes": {"type": "object"},
"annotation": {"type": "object"},
},
"required": ["id", "reactions", "metabolites", "genes"],
"additionalProperties": False,
}