diff --git a/modelseedpy/core/mstemplate.py b/modelseedpy/core/mstemplate.py index 03c485c1..78e22b5b 100755 --- a/modelseedpy/core/mstemplate.py +++ b/modelseedpy/core/mstemplate.py @@ -5,14 +5,11 @@ from cobra.core import Metabolite, Reaction from cobra.core.dictlist import DictList from cobra.util import format_long_string -from modelseedpy.core.msmodel import get_direction_from_constraints, \ - get_reaction_constraints_from_direction, get_cmp_token -from cobra.core.dictlist import DictList +from modelseedpy.core.msmodel import get_direction_from_constraints, get_reaction_constraints_from_direction, get_cmp_token #from cobrakbase.kbase_object_info import KBaseObjectInfo logger = logging.getLogger(__name__) - class AttrDict(dict): """ Base object to use for subobjects in KBase objects @@ -21,19 +18,16 @@ def __init__(self, *args, **kwargs): super(AttrDict, self).__init__(*args, **kwargs) self.__dict__ = self - class TemplateReactionType(Enum): CONDITIONAL = 'conditional' UNIVERSAL = 'universal' SPONTANEOUS = 'spontaneous' GAPFILLING = 'gapfilling' - class MSTemplateMetabolite: - def __init__(self, cpd_id, formula=None, name='', default_charge=None, - mass=None, delta_g=None, delta_g_error=None, is_cofactor=False, - abbreviation='', aliases=None): + def __init__(self, cpd_id, formula=None, name='', default_charge=None, mass=None, delta_g=None, + delta_g_error=None, is_cofactor=False, abbreviation='', aliases=None): self.id = cpd_id self.formula = formula self.name = name @@ -43,9 +37,7 @@ def __init__(self, cpd_id, formula=None, name='', default_charge=None, self.delta_g = delta_g self.delta_g_error = delta_g_error self.is_cofactor = is_cofactor - self.aliases = [] - if aliases: - self.aliases = aliases + self.aliases = aliases or [] self.species = set() self._template = None @@ -97,11 +89,10 @@ def _repr_html_(self): {species} """.format(id=self.id, name=format_long_string(self.name), - formula=self.formula, address='0x0%x' % id(self), + formula=self.formula, n_species=len(self.species), - species=format_long_string( - ', '.join(r.id for r in self.species), 200)) + species=format_long_string(', '.join(r.id for r in self.species), 200)) class MSTemplateSpecies(Metabolite): @@ -122,8 +113,7 @@ def to_metabolite(self, index='0'): :param index: compartment index :return: cobra.core.Metabolite """ - if index is None: - index = '' + index = index or '' cpd_id = f'{self.id}{index}' compartment = f'{self.compartment}{index}' name = f'{self.name}' @@ -143,9 +133,9 @@ def name(self): return '' @name.setter - def name(self, value): + def name(self, name): if self._template_compound: - self._template_compound.name = value + self._template_compound.name = name @property def formula(self): @@ -154,9 +144,9 @@ def formula(self): return '' @formula.setter - def formula(self, value): + def formula(self, formula): if self._template_compound: - self._template_compound.formula = value + self._template_compound.formula = formula @staticmethod def from_dict(d, template=None): @@ -183,8 +173,7 @@ class MSTemplateReaction(Reaction): def __init__(self, rxn_id: str, reference_id: str, name='', subsystem='', lower_bound=0.0, upper_bound=None, reaction_type=TemplateReactionType.CONDITIONAL, gapfill_direction='=', - base_cost=1000, reverse_penalty=1000, forward_penalty=1000, - status='OK', reference_reaction_id=None): + base_cost=1000, reverse_penalty=1000, forward_penalty=1000, status='OK'): """ :param rxn_id: @@ -209,18 +198,17 @@ def __init__(self, rxn_id: str, reference_id: str, name='', subsystem='', lower_ self.reverse_penalty = reverse_penalty self.forward_penalty = forward_penalty self.status = status - self.type = reaction_type.value if type(reaction_type) == TemplateReactionType else reaction_type - self.reference_reaction_id = reference_reaction_id # TODO: to be removed + self.type = reaction_type.value if isinstance(reaction_type, TemplateReactionType) else reaction_type self.complexes = DictList() self.templateReactionReagents = {} self._template = None @property def gene_reaction_rule(self): - return ' or '.join(map(lambda x: x.id, self.complexes)) + return ' or '.join([x.id for x in self.complexes]) @gene_reaction_rule.setter - def gene_reaction_rule(self, gpr): + def gene_reaction_rule(self, gpr): # !!! can this function be deleted? pass @property @@ -269,7 +257,6 @@ def from_dict(d, template): d['type'], d['GapfillDirection'], d['base_cost'], d['reverse_penalty'], d['forward_penalty'], d['status'] if 'status' in d else None, - d['reaction_ref'].split('/')[-1] ) reaction.add_metabolites(metabolites) reaction.add_complexes(complexes) @@ -280,12 +267,12 @@ def add_complexes(self, complex_list): @property def cstoichiometry(self): - return dict(((met.id, met.compartment), coefficient) for (met, coefficient) in self.metabolites.items()) + return {(met.id, met.compartment):coefficient for (met, coefficient) in self.metabolites.items()} - def remove_role(self, role_id): + def remove_role(self, role_id): # !!! can this function be deleted? pass - def remove_complex(self, complex_id): + def remove_complex(self, complex_id): # !!! can this function be deleted? pass def get_roles(self): @@ -295,34 +282,32 @@ def get_roles(self): """ roles = set() for cpx in self.complexes: - for role in cpx.roles: - roles.add(role) + roles.update(cpx.roles) return roles def get_complexes(self): return self.complexes def get_complex_roles(self): - res = {} + roles = {} for complexes in self.data['templatecomplex_refs']: complex_id = complexes.split('/')[-1] - res[complex_id] = set() + roles[complex_id] = set() if self._template: cpx = self._template.get_complex(complex_id) - if cpx: for complex_role in cpx['complexroles']: role_id = complex_role['templaterole_ref'].split('/')[-1] - res[complex_id].add(role_id) + roles[complex_id].add(role_id) else: - print('!!') - return res + print(f'The complex for ID {complex_id} does not exist.') + return roles def get_data(self): - template_reaction_reagents = list(map(lambda x: { - 'coefficient': x[1], - 'templatecompcompound_ref': '~/compcompounds/id/' + x[0].id - }, self.metabolites.items())) + template_reaction_reagents = [{ + 'coefficient': coef, + 'templatecompcompound_ref': '~/compcompounds/id/'+met.id + } for met, coef in self.metabolites.items()] return { 'id': self.id, 'name': self.name, @@ -365,8 +350,7 @@ def get_data(self): class NewModelTemplateRole: - def __init__(self, role_id, name, - features=None, source='', aliases=None): + def __init__(self, role_id, name, features=None, source='', aliases=None): """ :param role_id: @@ -378,8 +362,8 @@ def __init__(self, role_id, name, self.id = role_id self.name = name self.source = source - self.features = [] if features is None else features - self.aliases = [] if aliases is None else aliases + self.features = features or [] + self.aliases = aliases or [] self._complexes = set() self._template = None @@ -446,13 +430,10 @@ def __init__(self, complex_id, name, source='', reference='', confidence=0, temp @staticmethod def from_dict(d, template): protein_complex = NewModelTemplateComplex( - d['id'], d['name'], - d['source'], d['reference'], d['confidence'], - template - ) - for o in d['complexroles']: - role = template.roles.get_by_id(o['templaterole_ref'].split('/')[-1]) - protein_complex.add_role(role, o['triggering'] == 1, o['optional_role'] == 1) + d['id'], d['name'], d['source'], d['reference'], d['confidence'], template) + for cplx_role in d['complexroles']: + role = template.roles.get_by_id(cplx_role['templaterole_ref'].split('/')[-1]) + protein_complex.add_role(role, cplx_role['triggering'], cplx_role['optional_role']) return protein_complex def add_role(self, role: NewModelTemplateRole, triggering=True, optional=False): @@ -484,10 +465,8 @@ def get_data(self): } def __str__(self): - return " and ".join(map(lambda x: "{}{}{}".format( - x[0].id, - ":trig" if x[1][0] else "", - ":optional" if x[1][1] else ""), self.roles.items())) + return " and ".join( + ["{}{}{}".format(role[0].id, ":trig" if role[1][0] else "", ":optional" if role[1][1] else "") for role in self.roles.items()]) def __repr__(self): return "<%s %s at 0x%x>" % (self.__class__.__name__, self.id, id(self)) @@ -508,21 +487,18 @@ def _repr_html_(self): {complexes} """.format(id=self.id, name=format_long_string(self.name), - address='0x0%x' % id(self), - n_complexes=len(self.roles), - complexes=format_long_string( - ', '.join("{}:{}:{}:{}".format(r[0].id, r[0].name, r[1][0], r[1][1]) for r in - self.roles.items()), 200)) - + address='0x0%x' % id(self), n_complexes=len(self.roles), + complexes=format_long_string(', '.join("{}:{}:{}:{}".format( + r[0].id, r[0].name, r[1][0], r[1][1]) for r in self.roles.items()), 200)) class MSTemplateCompartment: - def __init__(self, compartment_id: str, name: str, ph: float, hierarchy=0, aliases=None): + def __init__(self, compartment_id: str, name: str, ph: float, hierarchy=0, aliases:list = None): self.id = compartment_id self.name = name self.ph = ph self.hierarchy = hierarchy - self.aliases = [] if aliases is None else list(aliases) + self.aliases = list(aliases) or [] self._template = None @staticmethod @@ -530,33 +506,21 @@ def from_dict(d): return MSTemplateCompartment(d['id'], d['name'], d['pH'], d['hierarchy'], d['aliases']) def get_data(self): - return { - 'id': self.id, - 'name': self.name, - 'pH': self.ph, - 'aliases': self.aliases, - 'hierarchy': self.hierarchy - } + return {'id': self.id, 'name': self.name, 'pH': self.ph, 'aliases': self.aliases, 'hierarchy': self.hierarchy} class MSTemplate: - def __init__(self, template_id, name='', domain='', template_type='', version=1, info=None, args=None): + def __init__(self, template_id, name='', domain='', template_type='', version=1, info=None, args=None): # !!! info and args are never used self.id = template_id self.name = name self.domain = domain self.template_type = template_type self.__VERSION__ = version self.biochemistry_ref = '' - self.compartments = DictList() - self.biomasses = DictList() - self.reactions = DictList() - self.compounds = DictList() - self.compcompounds = DictList() - self.roles = DictList() - self.complexes = DictList() - self.pathways = DictList() - self.subsystems = DictList() + self.compartments, self.biomasses, self.reactions = DictList(), DictList(), DictList() + self.compounds, self.pathways, self.subsystems = DictList(), DictList(), DictList() + self.roles, self.complexes, self.compcompounds = DictList(), DictList(), DictList() def add_compartments(self, compartments: list): """ @@ -564,10 +528,10 @@ def add_compartments(self, compartments: list): :param compartments: :return: """ - duplicates = list(filter(lambda x: x.id in self.compartments, compartments)) + duplicates = list(set(self.compartments).intersection(compartments)) if len(duplicates) > 0: - logger.error("unable to add compartments [%s] already present in the template", duplicates) - return None + logger.error(f"The duplicate compartments {duplicates} cannot be added to the template") + return None #!!! Should the non-duplicate compartments still be added? for x in compartments: x._template = self @@ -579,10 +543,10 @@ def add_roles(self, roles: list): :param roles: :return: """ - duplicates = list(filter(lambda x: x.id in self.roles, roles)) + duplicates = list(set(self.roles).intersection(roles)) if len(duplicates) > 0: - logger.error("unable to add roles [%s] already present in the template", duplicates) - return None + logger.error(f"The duplicate roles {duplicates} cannot be added to the template") + return None # !!! Should the non-duplicate compartments still be added? for x in roles: x._template = self @@ -594,24 +558,24 @@ def add_complexes(self, complexes: list): :param complexes: :return: """ - duplicates = list(filter(lambda x: x.id in self.complexes, complexes)) + duplicates = list(set(self.complexes).intersection(complexes)) if len(duplicates) > 0: - logger.error("unable to add comp compounds [%s] already present in the template", duplicates) - return None + logger.error(f"The duplicate complexes {duplicates} cannot be added to the template") + return None #!!! Should the non-duplicate compartments still be added? roles_to_add = [] - for x in complexes: - x._template = self + for complx in complexes: + complx._template = self roles_rep = {} - for role in x.roles: + for role in complx.roles: r = role if role.id not in self.roles: roles_to_add.append(role) else: r = self.roles.get_by_id(role.id) - roles_rep[r] = x.roles[role] - r._complexes.add(x) - x.roles = roles_rep + roles_rep[r] = complx.roles[role] + r._complexes.add(complx) + complx.roles = roles_rep self.roles += roles_to_add self.complexes += complexes @@ -622,13 +586,13 @@ def add_compounds(self, compounds: list): :param compounds: :return: """ - duplicates = list(filter(lambda x: x.id in self.compounds, compounds)) + duplicates = list(set(self.compounds).intersection(compounds)) if len(duplicates) > 0: - logger.error("unable to add compounds [%s] already present in the template", duplicates) - return None + logger.error(f"The duplicate compounds {duplicates} cannot be added to the template") + return None #!!! Should the non-duplicate compartments still be added? - for x in compounds: - x._template = self + for cpd in compounds: + cpd._template = self self.compounds += compounds def add_comp_compounds(self, comp_compounds: list): @@ -637,16 +601,16 @@ def add_comp_compounds(self, comp_compounds: list): :param comp_compounds: :return: """ - duplicates = list(filter(lambda x: x.id in self.compcompounds, comp_compounds)) + duplicates = list(set(self.compcompounds).intersection(comp_compounds)) if len(duplicates) > 0: - logger.error("unable to add comp compounds [%s] already present in the template", duplicates) - return None - - for x in comp_compounds: - x._template = self - if x.cpd_id in self.compounds: - x._template_compound = self.compounds.get_by_id(x.cpd_id) - x._template_compound.species.add(x) + logger.error(f"The duplicate comp compounds {duplicates} cannot be added to the template") + return None #!!! Should the non-duplicate compartments still be added? + + for comp_cpd in comp_compounds: + comp_cpd._template = self + if comp_cpd.cpd_id in self.compounds: + comp_cpd._template_compound = self.compounds.get_by_id(comp_cpd.cpd_id) + comp_cpd._template_compound.species.add(comp_cpd) self.compcompounds += comp_compounds def add_reactions(self, reaction_list: list): @@ -655,32 +619,31 @@ def add_reactions(self, reaction_list: list): :param reaction_list: :return: """ - duplicates = list(filter(lambda x: x.id in self.reactions, reaction_list)) + duplicates = list(set(self.reactions).intersection(reaction_list)) if len(duplicates) > 0: logger.error("unable to add reactions [%s] already present in the template", duplicates) - return None + return None # !!! Should the non-duplicate compartments still be added? - for x in reaction_list: + for rxn in reaction_list: metabolites_replace = {} complex_replace = set() - x._template = self - for comp_cpd, coefficient in x.metabolites.items(): + rxn._template = self + for comp_cpd, coefficient in rxn.metabolites.items(): if comp_cpd.id not in self.compcompounds: self.add_comp_compounds([comp_cpd]) metabolites_replace[self.compcompounds.get_by_id(comp_cpd.id)] = coefficient - for cpx in x.complexes: + for cpx in rxn.complexes: if cpx.id not in self.complexes: self.add_complexes([cpx]) complex_replace.add(self.complexes.get_by_id(cpx.id)) - x._metabolites = metabolites_replace - x.complexes = complex_replace - + rxn._metabolites = metabolites_replace + rxn.complexes = complex_replace self.reactions += reaction_list - def get_role_sources(self): + def get_role_sources(self): # !!! can this function be deleted? pass - def get_complex_sources(self): + def get_complex_sources(self): # !!! can this function be deleted? pass def get_complex_from_role(self, roles): @@ -690,24 +653,23 @@ def get_complex_from_role(self, roles): return None @staticmethod - def get_last_id_value(object_list, s): + def get_last_id_value(object_list, prefix): last_id = 0 - for o in object_list: - if o.id.startswith(s): - number_part = id[len(s):] + for obj in object_list: + if obj.id.startswith(prefix): + number_part = id[len(prefix):] if len(number_part) == 5: - if int(number_part) > last_id: - last_id = int(number_part) + last_id = max(last_id, int(number_part)) return last_id - def get_complex(self, id): - return self.complexes.get_by_id(id) + def get_complex(self, obj_id): + return self.complexes.get_by_id(obj_id) - def get_reaction(self, id): - return self.reactions.get_by_id(id) + def get_reaction(self, obj_id): + return self.reactions.get_by_id(obj_id) - def get_role(self, id): - return self.roles.get_by_id(id) + def get_role(self, obj_id): + return self.roles.get_by_id(obj_id) # def _to_object(self, key, data): # if key == 'compounds': @@ -748,12 +710,12 @@ def get_data(self): 'domain': self.domain, 'biochemistry_ref': self.biochemistry_ref, 'type': 'Test', - 'compartments': list(map(lambda x: x.get_data(), self.compartments)), - 'compcompounds': list(map(lambda x: x.get_data(), self.compcompounds)), - 'compounds': list(map(lambda x: x.get_data(), self.compounds)), - 'roles': list(map(lambda x: x.get_data(), self.roles)), - 'complexes': list(map(lambda x: x.get_data(), self.complexes)), - 'reactions': list(map(lambda x: x.get_data(), self.reactions)), + 'compartments': list(x.get_data() for x in self.compartments), + 'compcompounds': list(x.get_data() for x in self.compcompounds), + 'compounds': list(x.get_data() for x in self.compounds), + 'roles': list(x.get_data() for x in self.roles), + 'complexes': list(x.get_data() for x in self.complexes), + 'reactions': list(x.get_data() for x in self.reactions), 'biomasses': list(self.biomasses), 'pathways': [], 'subsystems': [], @@ -805,19 +767,14 @@ def _repr_html_(self): class MSTemplateBuilder: def __init__(self, template_id, name='', domain='', template_type='', version=1, info=None, - biochemistry=None, biomasses=None, pathways=None, subsystems=None): + biochemistry=None, biomasses=None, pathways=None, subsystems=None): # !!! biochemistry, biomasses, pathways, and subsystems are nevery used self.id = template_id self.version = version self.name = name self.domain = domain self.template_type = template_type - self.compartments = [] - self.biomasses = [] - self.roles = [] - self.complexes = [] - self.compounds = [] - self.compartment_compounds = [] - self.reactions = [] + self.compartments, self.biomasses, self.roles, self.complexes = [], [], [], [] + self.compounds, self.compartment_compounds, self.reactions = [], [], [] self.info = info self.biochemistry_ref = None @@ -830,7 +787,7 @@ def from_dict(d, info=None, args=None): :param args: :return: """ - builder = MSTemplateBuilder(d['id'], d['name'], d['domain'], d['type'], d['__VERSION__'], None) + builder = MSTemplateBuilder(d['id'], d['name'], d['domain'], d['type'], d['__VERSION__'], None) # !!! None here should probably be info, which is why info is a function argument builder.compartments = d['compartments'] builder.roles = d['roles'] builder.complexes = d['complexes'] @@ -843,60 +800,54 @@ def from_dict(d, info=None, args=None): @staticmethod def from_template(template): - b = MSTemplateBuilder() - for o in template.compartments: - b.compartments.append(copy.deepcopy(o)) + builder = MSTemplateBuilder() + for compartment in template.compartments: + builder.compartments.append(copy.deepcopy(compartment)) - return b + return builder - def with_compound_modelseed(self, seed_id, modelseed): + def with_compound_modelseed(self, seed_id, modelseed): # !!! can this function be deleted? pass def with_role(self, template_rxn, role_ids, auto_complex=False): # TODO: copy from template curation complex_roles = template_rxn.get_complex_roles() - role_match = {} - for o in role_ids: - role_match[o] = False + role_match = {role_id:False for role_id in role_ids} for complex_id in complex_roles: - for o in role_match: - if o in complex_roles[complex_id]: - role_match[o] = True + for role in role_match: + if role in complex_roles[complex_id]: + role_match[role] = True all_roles_present = True - for o in role_match: - all_roles_present &= role_match[o] + for role in role_match: + all_roles_present &= role_match[role] if all_roles_present: - logger.debug('ignore %s all present in atleast 1 complex', role_ids) + logger.debug(f'At least one complex does not express all one role of {role_ids}.') return None - complex_id = self.template.get_complex_from_role(role_ids) + complex_id = self.template.get_complex_from_roles(role_ids) if complex_id is None: - logger.warning('unable to find complex for %s', role_ids) + logger.warning(f'A corresponding complex for the roles {role_ids} cannot be found.') if auto_complex: - role_names = set() - for role_id in role_ids: - role = self.template.get_role(role_id) - role_names.add(role['name']) - logger.warning('build complex for %s', role_names) + role_names = set([self.template.get_role(role_id)['name'] for role_id in role_ids]) + logger.warning(f'The complex {role_names} will be added to the template.') complex_id = self.template.add_complex_from_role_names(role_names) else: return None complex_ref = '~/complexes/id/' + complex_id if complex_ref in template_rxn.data['templatecomplex_refs']: - logger.debug('already contains complex %s, role %s', role_ids, complex_ref) + logger.debug(f'The template already contains a complex reference {complex_ref} for complex {complex_id}.') return None return complex_ref - def with_compound(self): + def with_compound(self): # !!! can this function be deleted? pass - def with_compound_compartment(self): + def with_compound_compartment(self): # !!! can this function be deleted? pass def with_compartment(self, cmp_id, name, ph=7, index='0'): - res = list(filter(lambda x: x['id'] == cmp_id, self.compartments)) + res = list(x for x in self.compartments if x['id'] == cmp_id) if len(res) > 0: return res[0] - self.compartments.append({ 'id': cmp_id, 'name': name, @@ -905,20 +856,15 @@ def with_compartment(self, cmp_id, name, ph=7, index='0'): 'index': index, 'pH': ph }) - return self def build(self): template = MSTemplate(self.id, self.name, self.domain, self.template_type, self.version) - template.add_compartments(list(map(lambda x: MSTemplateCompartment.from_dict(x), self.compartments))) - template.add_compounds(list(map(lambda x: MSTemplateMetabolite.from_dict(x), self.compounds))) - template.add_comp_compounds( - list(map(lambda x: MSTemplateSpecies.from_dict(x), self.compartment_compounds))) - template.add_roles(list(map(lambda x: NewModelTemplateRole.from_dict(x), self.roles))) - template.add_complexes( - list(map(lambda x: NewModelTemplateComplex.from_dict(x, template), self.complexes))) - template.add_reactions( - list(map(lambda x: MSTemplateReaction.from_dict(x, template), self.reactions))) - template.biomasses += list(map(lambda x: AttrDict(x), self.biomasses)) # TODO: biomass object - + template.add_compartments([MSTemplateCompartment.from_dict(x) for x in self.compartments]) + template.add_compounds([MSTemplateMetabolite.from_dict(x) for x in self.compounds]) + template.add_comp_compounds([MSTemplateSpecies.from_dict(x) for x in self.compartment_compounds]) + template.add_roles([NewModelTemplateRole.from_dict(x) for x in self.roles]) + template.add_complexes([NewModelTemplateComplex.from_dict(x, template) for x in self.complexes]) + template.add_reactions([MSTemplateReaction.from_dict(x, template) for x in self.reactions]) + template.biomasses += [AttrDict(x) for x in self.biomasses] # TODO: biomass object return template