Source code for indra.statements.statements

"""
Statements represent mechanistic relationships between biological agents.

Statement classes follow an inheritance hierarchy, with all Statement types
inheriting from the parent class :py:class:`Statement`. At
the next level in the hierarchy are the following classes:

Open Domain

- :py:class:`Event`
- :py:class:`Influence`
- :py:class:`Association`

Biological Domain

- :py:class:`Complex`
- :py:class:`Modification`
- :py:class:`SelfModification`
- :py:class:`RegulateActivity`
- :py:class:`RegulateAmount`
- :py:class:`ActiveForm`
- :py:class:`Translocation`
- :py:class:`Gef`
- :py:class:`Gap`
- :py:class:`Conversion`

There are several types of Statements representing post-translational
modifications that further inherit from
:py:class:`Modification`:

- :py:class:`Phosphorylation`
- :py:class:`Dephosphorylation`
- :py:class:`Ubiquitination`
- :py:class:`Deubiquitination`
- :py:class:`Sumoylation`
- :py:class:`Desumoylation`
- :py:class:`Hydroxylation`
- :py:class:`Dehydroxylation`
- :py:class:`Acetylation`
- :py:class:`Deacetylation`
- :py:class:`Glycosylation`
- :py:class:`Deglycosylation`
- :py:class:`Farnesylation`
- :py:class:`Defarnesylation`
- :py:class:`Geranylgeranylation`
- :py:class:`Degeranylgeranylation`
- :py:class:`Palmitoylation`
- :py:class:`Depalmitoylation`
- :py:class:`Myristoylation`
- :py:class:`Demyristoylation`
- :py:class:`Ribosylation`
- :py:class:`Deribosylation`
- :py:class:`Methylation`
- :py:class:`Demethylation`

There are additional subtypes of :py:class:`SelfModification`:

- :py:class:`Autophosphorylation`
- :py:class:`Transphosphorylation`

Interactions between proteins are often described simply in terms of their
effect on a protein's "activity", e.g., "Active MEK activates ERK", or "DUSP6
inactives ERK".  These types of relationships are indicated by the
:py:class:`RegulateActivity` abstract base class which has subtypes

- :py:class:`Activation`
- :py:class:`Inhibition`

while the :py:class:`RegulateAmount` abstract base class has subtypes

- :py:class:`IncreaseAmount`
- :py:class:`DecreaseAmount`

Statements involve one or more *Concepts*, which, depending on the
semantics of the Statement, are typically biological *Agents*,
such as proteins, represented by the class :py:class:`Agent`. (However,
:py:class`Influence` statements involve two or more :py:class`Event` objects,
each of which takes a :py:class`Concept` as an argument.)

Agents can have several types of context specified on them including

- a specific post-translational modification state (indicated by one or
  more instances of :py:class:`ModCondition`),
- other bound Agents (:py:class:`BoundCondition`),
- mutations (:py:class:`MutCondition`),
- an activity state (:py:class:`ActivityCondition`), and
- cellular location

The *active* form of an agent (in terms of its post-translational modifications
or bound state) is indicated by an instance of the class
:py:class:`ActiveForm`.

Agents also carry grounding information which links them to database entries.
These database references are represented as a dictionary in the `db_refs`
attribute of each Agent. The dictionary can have multiple entries. For
instance, INDRA's input Processors produce genes and proteins that carry both
UniProt and HGNC IDs in db_refs, whenever possible. FamPlex provides a name
space for protein families that are typically used in the literature.  More
information about FamPlex can be found here:
https://github.com/sorgerlab/famplex

+------------------------+------------------+--------------------------+
| Type                   | Database         | Example                  |
+========================+==================+==========================+
| Gene/Protein           | HGNC             | {'HGNC': '11998'}        |
+------------------------+------------------+--------------------------+
| Gene/Protein           | UniProt          | {'UP': 'P04637'}         |
+------------------------+------------------+--------------------------+
| Gene/Protein           | Entrez           | {'EGID': '5583'}         |
+------------------------+------------------+--------------------------+
| Gene/Protein family    | FamPlex          | {'FPLX': 'ERK'}          |
+------------------------+------------------+--------------------------+
| Gene/Protein family    | InterPro         | {'IP': 'IPR000308'}      |
+------------------------+------------------+--------------------------+
| Gene/Protein family    | Pfam             | {'PF': 'PF00071'}        |
+------------------------+------------------+--------------------------+
| Gene/Protein family    | NextProt family  | {'NXPFA': '03114'}       |
+------------------------+------------------+--------------------------+
| Chemical               | ChEBI            | {'CHEBI': 'CHEBI:63637'} |
+------------------------+------------------+--------------------------+
| Chemical               | PubChem          | {'PUBCHEM': '42611257'}  |
+------------------------+------------------+--------------------------+
| Chemical               | LINCS / HMS-LINCS| {'LINCS': '42611257'}    |
+------------------------+------------------+--------------------------+
| Metabolite             | HMDB             | {'HMDB': 'HMDB00122'}    |
+------------------------+------------------+--------------------------+
| Process, location, etc.| GO               | {'GO': 'GO:0006915'}     |
+------------------------+------------------+--------------------------+
| Process, disease, etc. | MeSH             | {'MESH': 'D008113'}      |
+------------------------+------------------+--------------------------+
| General terms          | NCIT             | {'NCIT': 'C28597'}       |
+------------------------+------------------+--------------------------+
| Raw text               | TEXT             | {'TEXT': 'Nf-kappaB'}    |
+------------------------+------------------+--------------------------+


The evidence for a given Statement, which could include relevant citations,
database identifiers, and passages of text from the scientific literature, is
contained in one or more :py:class:`Evidence` objects associated with the
Statement.


JSON serialization of INDRA Statements
--------------------------------------

Statements can be serialized into JSON and deserialized from JSON to allow
their exchange in a platform-independent way. We also provide a JSON
schema (see http://json-schema.org to learn about schemas) in
https://raw.githubusercontent.com/sorgerlab/indra/master/indra/resources/statements_schema.json
which can be used to validate INDRA Statements JSONs.

Some validation tools include:

- jsonschema
    a Python package to validate JSON content with respect to
    a schema
- ajv-cli
    Available at https://www.npmjs.com/package/ajv-cli
    Install with "npm install -g ajv-cli" and then validate with:
    ajv -s statements_schema.json -d file_to_validate.json. This tool
    provides more sophisticated and better interpretable output than
    jsonschema.
- Web based tools
    There are a variety of web-based tools for validation with JSON schemas,
    including https://www.jsonschemavalidator.net
"""

from __future__ import absolute_import, print_function, unicode_literals
from builtins import dict, str
from future.utils import python_2_unicode_compatible

__all__ = [
    # Condition classes
    'BoundCondition', 'MutCondition', 'ModCondition', 'ActivityCondition',

    # Statement classes
    'Statement', 'Modification', 'AddModification', 'RemoveModification',
    'SelfModification', 'Phosphorylation', 'Autophosphorylation',
    'Transphosphorylation', 'Dephosphorylation', 'Hydroxylation',
    'Dehydroxylation', 'Sumoylation', 'Desumoylation', 'Acetylation',
    'Deacetylation', 'Glycosylation', 'Deglycosylation', 'Ribosylation',
    'Deribosylation', 'Ubiquitination', 'Deubiquitination', 'Farnesylation',
    'Defarnesylation', 'Geranylgeranylation', 'Degeranylgeranylation',
    'Palmitoylation', 'Depalmitoylation', 'Myristoylation', 'Demyristoylation',
    'Methylation', 'Demethylation', 'RegulateActivity', 'Inhibition',
    'Activation', 'GtpActivation', 'ActiveForm', 'HasActivity', 'Gef', 'Gap',
    'Complex', 'Translocation', 'RegulateAmount', 'DecreaseAmount',
    'IncreaseAmount', 'Influence', 'Conversion', 'Unresolved',
    'Association', 'Event', 'Migration',

    # Error classes
    'InputError', 'UnresolvedUuidError', 'InvalidLocationError',
    'InvalidResidueError', 'NotAStatementName',

    # Other classes
    'Concept', 'Agent', 'Evidence', 'QualitativeDelta', 'QuantitativeState',

    # Context classes
    'BioContext', 'WorldContext', 'TimeContext', 'RefContext', 'Context',
    'MovementContext',

    # Functions and values
    'stmts_from_json', 'get_unresolved_support_uuids', 'stmts_to_json',
    'stmts_from_json_file', 'stmts_to_json_file',
    'get_valid_residue', 'get_valid_location', 'get_valid_location',
    'draw_stmt_graph', 'get_all_descendants','make_statement_camel',
    'amino_acids', 'amino_acids_reverse', 'activity_types',
    'cellular_components', 'cellular_components_reverse', 'modtype_to_modclass',
    'modclass_to_modtype', 'modtype_conditions', 'modtype_to_inverse',
    'modclass_to_inverse', 'get_statement_by_name', 'make_hash', 'stmt_type'
    ]

import abc
import sys
import uuid
import logging
import networkx
import itertools
from copy import deepcopy
from collections import OrderedDict as _o
from .util import *
from .concept import *
from .context import *
from .evidence import *
from .resources import *
from .delta import *

logger = logging.getLogger(__name__)


try:  # Python 2
    basestring
except NameError:  # Python 3
    basestring = str


[docs]class Statement(object): """The parent class of all statements. Parameters ---------- evidence : None or :py:class:`Evidence` or list of :py:class:`Evidence` If a list of Evidence objects is passed to the constructor, the value is set to this list. If a bare Evidence object is passed, it is enclosed in a list. If no evidence is passed (the default), the value is set to an empty list. supports : list of :py:class:`Statement` Statements that this Statement supports. supported_by : list of :py:class:`Statement` Statements supported by this statement. """ _agent_order = NotImplemented def __init__(self, evidence=None, supports=None, supported_by=None): if evidence is None: self.evidence = [] elif isinstance(evidence, Evidence): self.evidence = [evidence] elif isinstance(evidence, list): self.evidence = evidence else: raise ValueError('evidence must be an Evidence object, a list ' '(of Evidence objects), or None.') # Initialize supports/supported_by fields, which should be lists self.supports = supports if supports else [] self.supported_by = supported_by if supported_by else [] self.belief = 1 self.uuid = '%s' % uuid.uuid4() self._full_hash = None self._shallow_hash = None return def matches_key(self): raise NotImplementedError("Method must be implemented in child class.") def matches(self, other): return self.matches_key() == other.matches_key()
[docs] def get_hash(self, shallow=True, refresh=False, matches_fun=None): """Get a hash for this Statement. There are two types of hash, "shallow" and "full". A shallow hash is as unique as the information carried by the statement, i.e. it is a hash of the `matches_key`. This means that differences in source, evidence, and so on are not included. As such, it is a shorter hash (14 nibbles). The odds of a collision among all the statements we expect to encounter (well under 10^8) is ~10^-9 (1 in a billion). Checks for collisions can be done by using the matches keys. A full hash includes, in addition to the matches key, information from the evidence of the statement. These hashes will be equal if the two Statements came from the same sentences, extracted by the same reader, from the same source. These hashes are correspondingly longer (16 nibbles). The odds of a collision for an expected less than 10^10 extractions is ~10^-9 (1 in a billion). Note that a hash of the Python object will also include the `uuid`, so it will always be unique for every object. Parameters ---------- shallow : bool Choose between the shallow and full hashes described above. Default is true (e.g. a shallow hash). refresh : bool Used to get a new copy of the hash. Default is false, so the hash, if it has been already created, will be read from the attribute. This is primarily used for speed testing. matches_fun : Optional[function] A function which takes a Statement as argument and returns a string matches key which is then hashed. If not provided the Statement's built-in matches_key method is used. Returns ------- hash : int A long integer hash. """ if shallow: if not hasattr(self, '_shallow_hash') or self._shallow_hash is None\ or refresh: if not matches_fun: matches_key = self.matches_key() else: matches_key = matches_fun(self) self._shallow_hash = make_hash(matches_key, 14) ret = self._shallow_hash else: if not hasattr(self, '_full_hash') or self._full_hash is None \ or refresh: ev_mk_list = sorted([ev.matches_key() for ev in self.evidence]) self._full_hash = \ make_hash(self.matches_key() + str(ev_mk_list), 16) ret = self._full_hash return ret
def _tag_evidence(self): """Set all the Evidence stmt_tag to my deep matches-key hash.""" h = self.get_hash(shallow=False) for ev in self.evidence: ev.stmt_tag = h return def agent_list_with_bound_condition_agents(self): # Returns the list of agents both directly participating in the # statement and referenced through bound conditions. l = self.agent_list() for a in self.agent_list(): if a is not None: bc_agents = [bc.agent for bc in a.bound_conditions] l.extend(bc_agents) return l
[docs] def agent_list(self, deep_sorted=False): """Get the canonicalized agent list.""" ag_list = [] for ag_name in self._agent_order: ag_attr = getattr(self, ag_name) if isinstance(ag_attr, Concept) or ag_attr is None: ag_list.append(ag_attr) elif isinstance(ag_attr, list): if not all([isinstance(ag, Concept) for ag in ag_attr]): raise TypeError("Expected all elements of list to be Agent " "and/or Concept, but got: %s" % {type(ag) for ag in ag_attr}) if deep_sorted: ag_attr = sorted_agents(ag_attr) ag_list.extend(ag_attr) else: raise TypeError("Expected type Agent, Concept, or list, got " "type %s." % type(ag_attr)) return ag_list
def entities_match(self, other): self_key = self.entities_match_key() other_key = other.entities_match_key() if len(self_key) != len(other_key): return False for self_agent, other_agent in zip(self_key, other_key): if self_agent is None or other_agent is None: continue if self_agent != other_agent: return False return True def entities_match_key(self): key = tuple(a.entity_matches_key() if a is not None else None for a in self.agent_list()) return key def print_supports(self): print('%s supported_by:' % str(self)) if self.supported_by: print('-->') for s in self.supported_by: s.print_supports() def __repr__(self): if sys.version_info[0] >= 3: return str(self) else: return str(self).encode('utf-8') def equals(self, other): return self.types_equals(other) \ and self.agents_equal(other) \ and self.evidence_equals(other) def types_equals(self, other): if stmt_type(self) != stmt_type(other): return False return True def agents_equal(self, other): if len(self.agent_list()) == len(other.agent_list()): for s, o in zip(self.agent_list(), other.agent_list()): if (s is None and o is not None) or \ (s is not None and o is None): return False if s is not None and o is not None and not s.equals(o): return False else: return False return True def evidence_equals(self, other): if len(self.evidence) == len(other.evidence): for s, o in zip(self.evidence, other.evidence): if not s.equals(o): return False else: return False return True def contradicts(self, other, hierarchies): # Placeholder for implementation in subclasses return False
[docs] def to_json(self, use_sbo=False, matches_fun=None): """Return serialized Statement as a JSON dict. Parameters ---------- use_sbo : Optional[bool] If True, SBO annotations are added to each applicable element of the JSON. Default: False matches_fun : Optional[function] A custom function which, if provided, is used to construct the matches key which is then hashed and put into the return value. Default: None Returns ------- json_dict : dict The JSON-serialized INDRA Statement. """ stmt_type = type(self).__name__ # Original comment: For backwards compatibility, could be removed later all_stmts = [self] + self.supports + self.supported_by for st in all_stmts: if not hasattr(st, 'uuid'): st.uuid = '%s' % uuid.uuid4() ################## json_dict = _o(type=stmt_type) json_dict['belief'] = self.belief if self.evidence: evidence = [ev.to_json() for ev in self.evidence] json_dict['evidence'] = evidence json_dict['id'] = '%s' % self.uuid json_dict['matches_hash'] = \ '%s' % self.get_hash(shallow=True, refresh=True, matches_fun=matches_fun) if self.supports: json_dict['supports'] = \ ['%s' % st.uuid for st in self.supports] if self.supported_by: json_dict['supported_by'] = \ ['%s' % st.uuid for st in self.supported_by] def get_sbo_term(cls): sbo_term = stmt_sbo_map.get(cls.__name__.lower()) while not sbo_term: cls = cls.__bases__[0] sbo_term = stmt_sbo_map.get(cls.__name__.lower()) return sbo_term if use_sbo: sbo_term = get_sbo_term(self.__class__) json_dict['sbo'] = \ 'http://identifiers.org/sbo/SBO:%s' % sbo_term return json_dict
@classmethod def _from_json(cls, json_dict): stmt_type = json_dict.get('type') stmt_cls = getattr(sys.modules[__name__], stmt_type) stmt = stmt_cls._from_json(json_dict) evidence = json_dict.get('evidence', []) stmt.evidence = [Evidence._from_json(ev) for ev in evidence] stmt.supports = json_dict.get('supports', [])[:] stmt.supported_by = json_dict.get('supported_by', [])[:] stmt.belief = json_dict.get('belief', 1.0) stmt_id = json_dict.get('id') if not stmt_id: stmt_id = '%s' % uuid.uuid4() stmt.uuid = stmt_id return stmt
[docs] def to_graph(self): """Return Statement as a networkx graph.""" def json_node(graph, element, prefix): if not element: return None node_id = '|'.join(prefix) if isinstance(element, list): graph.add_node(node_id, label='') # Enumerate children and add nodes and connect to anchor node for i, sub_element in enumerate(element): sub_id = json_node(graph, sub_element, prefix + ['%s' % i]) if sub_id: graph.add_edge(node_id, sub_id, label='') elif isinstance(element, dict): graph.add_node(node_id, label='') # Add node recursively for each element # Connect to this node with edge label according to key for k, v in element.items(): if k == 'id': continue elif k == 'name': graph.nodes[node_id]['label'] = v continue elif k == 'type': graph.nodes[node_id]['label'] = v continue sub_id = json_node(graph, v, prefix + ['%s' % k]) if sub_id: graph.add_edge(node_id, sub_id, label=('%s' % k)) else: if isinstance(element, basestring) and \ element.startswith('http'): element = element.split('/')[-1] graph.add_node(node_id, label=('%s' % str(element))) return node_id jd = self.to_json() graph = networkx.DiGraph() json_node(graph, jd, ['%s' % self.uuid]) return graph
[docs] def make_generic_copy(self, deeply=False): """Make a new matching Statement with no provenance. All agents and other attributes besides evidence, belief, supports, and supported_by will be copied over, and a new uuid will be assigned. Thus, the new Statement will satisfy `new_stmt.matches(old_stmt)`. If `deeply` is set to True, all the attributes will be deep-copied, which is comparatively slow. Otherwise, attributes of this statement may be altered by changes to the new matching statement. """ if deeply: kwargs = deepcopy(self.__dict__) else: kwargs = self.__dict__.copy() for attr in ['evidence', 'belief', 'uuid', 'supports', 'supported_by', 'is_activation']: kwargs.pop(attr, None) for attr in ['_full_hash', '_shallow_hash']: my_hash = kwargs.pop(attr, None) my_shallow_hash = kwargs.pop(attr, None) for attr in self._agent_order: attr_value = kwargs.get(attr) if isinstance(attr_value, list): kwargs[attr] = sorted_agents(attr_value) new_instance = self.__class__(**kwargs) new_instance._full_hash = my_hash new_instance._shallow_hash = my_shallow_hash return new_instance
[docs] def flip_polarity(self, agent_idx=None): """If applicable, flip the polarity of the statement""" pass
[docs]@python_2_unicode_compatible class Modification(Statement): """Generic statement representing the modification of a protein. Parameters ---------- enz : :py:class:`indra.statement.Agent` The enzyme involved in the modification. sub : :py:class:`indra.statement.Agent` The substrate of the modification. residue : str or None The amino acid residue being modified, or None if it is unknown or unspecified. position : str or None The position of the modified amino acid, or None if it is unknown or unspecified. evidence : None or :py:class:`Evidence` or list of :py:class:`Evidence` Evidence objects in support of the modification. """ _agent_order = ['enz', 'sub'] def __init__(self, enz, sub, residue=None, position=None, evidence=None): super(Modification, self).__init__(evidence) self.enz = enz self.sub = sub self.residue = get_valid_residue(residue) if isinstance(position, int): self.position = str(position) else: self.position = position def matches_key(self): if self.enz is None: enz_key = None else: enz_key = self.enz.matches_key() key = (stmt_type(self, True), enz_key, self.sub.matches_key(), str(self.residue), str(self.position)) return mk_str(key) def set_agent_list(self, agent_list): if len(agent_list) != 2: raise ValueError("Modification has two agents in agent_list.") self.enz = agent_list[0] self.sub = agent_list[1] def refinement_of(self, other, hierarchies): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False # Check agent arguments if self.enz is None and other.enz is None: enz_refinement = True elif self.enz is None and other.enz is not None: enz_refinement = False elif self.enz is not None and other.enz is None: enz_refinement = True else: enz_refinement = self.enz.refinement_of(other.enz, hierarchies) sub_refinement = self.sub.refinement_of(other.sub, hierarchies) if not (enz_refinement and sub_refinement): return False # For this to be a refinement of the other, the modifications either # have to match or have this one be a subtype of the other; in # addition, the sites have to match, or this one has to have site # information and the other one not. residue_matches = (other.residue is None or (self.residue == other.residue)) position_matches = (other.position is None or (self.position == other.position)) return residue_matches and position_matches def equals(self, other): matches = super(Modification, self).equals(other) matches = (matches and (self.residue == other.residue) and (self.position == other.position)) return matches def contradicts(self, other, hierarchies): # If the modifications are not the opposite polarity of the # same subtype if not modclass_to_inverse[self.__class__] == other.__class__: return False # Skip all instances of not fully specified modifications agents = (self.enz, self.sub, other.enz, other.sub) if not all(a is not None for a in agents): return False # If the entities don't match, they can't be contradicting # Here we check pairs of agents at each "position" and # make sure they are the same or they are refinements of each other for self_agent, other_agent in zip(self.agent_list(), other.agent_list()): if not (self_agent.entity_matches(other_agent) or \ self_agent.refinement_of(other_agent, hierarchies) or \ other_agent.refinement_of(self_agent, hierarchies)): return False # At this point the entities definitely match so we need to # check the specific site that is being modified if self.residue == other.residue and self.position == other.position: return True else: return False def _get_mod_condition(self): """Return a ModCondition corresponding to this Modification.""" mod_type = modclass_to_modtype[self.__class__] if isinstance(self, RemoveModification): mod_type = modtype_to_inverse[mod_type] mc = ModCondition(mod_type, self.residue, self.position, True) return mc
[docs] def to_json(self, use_sbo=False, matches_fun=None): generic = super(Modification, self).to_json(use_sbo, matches_fun) json_dict = _o(type=generic['type']) if self.enz is not None: json_dict['enz'] = self.enz.to_json() if use_sbo: # enzymatic catalyst json_dict['enz']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000460' if self.sub is not None: json_dict['sub'] = self.sub.to_json() if use_sbo: # substrate json_dict['sub']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000015' if self.residue is not None: json_dict['residue'] = self.residue if self.position is not None: json_dict['position'] = self.position json_dict.update(generic) return json_dict
@classmethod def _from_json(cls, json_dict): enz = json_dict.get('enz') sub = json_dict.get('sub') residue = json_dict.get('residue') position = json_dict.get('position') evidence = json_dict.get('evidence', []) if enz: enz = Agent._from_json(enz) if sub: sub = Agent._from_json(sub) stmt = cls(enz, sub, residue, position) return stmt def __str__(self): res_str = (', %s' % self.residue) if self.residue is not None else '' pos_str = (', %s' % self.position) if self.position is not None else '' s = ("%s(%s, %s%s%s)" % (type(self).__name__, self.enz, self.sub, res_str, pos_str)) return s
[docs]class AddModification(Modification): pass
[docs]class RemoveModification(Modification): pass
[docs]@python_2_unicode_compatible class SelfModification(Statement): """Generic statement representing the self-modification of a protein. Parameters ---------- enz : :py:class:`indra.statement.Agent` The enzyme involved in the modification, which is also the substrate. residue : str or None The amino acid residue being modified, or None if it is unknown or unspecified. position : str or None The position of the modified amino acid, or None if it is unknown or unspecified. evidence : None or :py:class:`Evidence` or list of :py:class:`Evidence` Evidence objects in support of the modification. """ _agent_order = ['enz'] def __init__(self, enz, residue=None, position=None, evidence=None): super(SelfModification, self).__init__(evidence) self.enz = enz self.residue = get_valid_residue(residue) if isinstance(position, int): self.position = str(position) else: self.position = position def __str__(self): res_str = (', %s' % self.residue) if self.residue is not None else '' pos_str = (', %s' % self.position) if self.position is not None else '' s = ("%s(%s%s%s)" % (type(self).__name__, self.enz, res_str, pos_str)) return s def matches_key(self): key = (stmt_type(self, True), self.enz.matches_key(), str(self.residue), str(self.position)) return mk_str(key) def set_agent_list(self, agent_list): if len(agent_list) != 1: raise ValueError("SelfModification has one agent.") self.enz = agent_list[0] def refinement_of(self, other, hierarchies): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False # Check agent arguments if not self.enz.refinement_of(other.enz, hierarchies): return False # For this to be a refinement of the other, the modifications either # have to match or have this one be a subtype of the other; in # addition, the sites have to match, or this one has to have site # information and the other one not. residue_matches = (other.residue is None or (self.residue == other.residue)) position_matches = (other.position is None or (self.position == other.position)) return residue_matches and position_matches def equals(self, other): matches = super(SelfModification, self).equals(other) matches = (matches and self.residue == other.residue and self.position == other.position) return matches def _get_mod_condition(self): """Return a ModCondition corresponding to this Modification.""" mod_type = modclass_to_modtype[self.__class__] mc = ModCondition(mod_type, self.residue, self.position, True) return mc
[docs] def to_json(self, use_sbo=False, matches_fun=None): generic = super(SelfModification, self).to_json(use_sbo, matches_fun) json_dict = _o(type=generic['type']) if self.enz is not None: json_dict['enz'] = self.enz.to_json() if use_sbo: # enzymatic catalyst json_dict['enz']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000460' if self.residue is not None: json_dict['residue'] = self.residue if self.position is not None: json_dict['position'] = self.position json_dict.update(generic) return json_dict
@classmethod def _from_json(cls, json_dict): enz = json_dict.get('enz') residue = json_dict.get('residue') position = json_dict.get('position') if enz: enz = Agent._from_json(enz) stmt = cls(enz, residue, position) return stmt
[docs]class Phosphorylation(AddModification): """Phosphorylation modification. Examples -------- MEK (MAP2K1) phosphorylates ERK (MAPK1) at threonine 185: >>> mek = Agent('MAP2K1') >>> erk = Agent('MAPK1') >>> phos = Phosphorylation(mek, erk, 'T', '185') """
[docs]class Autophosphorylation(SelfModification): """Intramolecular autophosphorylation, i.e., in *cis*. Examples -------- p38 bound to TAB1 cis-autophosphorylates itself (see :pmid:`19155529`). >>> tab1 = Agent('TAB1') >>> p38_tab1 = Agent('P38', bound_conditions=[BoundCondition(tab1)]) >>> autophos = Autophosphorylation(p38_tab1) """
[docs]class Transphosphorylation(SelfModification): """Autophosphorylation in *trans.* Transphosphorylation assumes that a kinase is already bound to a substrate (usually of the same molecular species), and phosphorylates it in an intra-molecular fashion. The enz property of the statement must have exactly one bound_conditions entry, and we assume that enz phosphorylates this molecule. The bound_neg property is ignored here. """
[docs]class Dephosphorylation(RemoveModification): """Dephosphorylation modification. Examples -------- DUSP6 dephosphorylates ERK (MAPK1) at T185: >>> dusp6 = Agent('DUSP6') >>> erk = Agent('MAPK1') >>> dephos = Dephosphorylation(dusp6, erk, 'T', '185') """
[docs]class Hydroxylation(AddModification): """Hydroxylation modification."""
[docs]class Dehydroxylation(RemoveModification): """Dehydroxylation modification."""
[docs]class Sumoylation(AddModification): """Sumoylation modification."""
[docs]class Desumoylation(RemoveModification): """Desumoylation modification."""
[docs]class Acetylation(AddModification): """Acetylation modification."""
[docs]class Deacetylation(RemoveModification): """Deacetylation modification."""
[docs]class Glycosylation(AddModification): """Glycosylation modification."""
[docs]class Deglycosylation(RemoveModification): """Deglycosylation modification."""
[docs]class Ribosylation(AddModification): """Ribosylation modification."""
[docs]class Deribosylation(RemoveModification): """Deribosylation modification."""
[docs]class Ubiquitination(AddModification): """Ubiquitination modification."""
[docs]class Deubiquitination(RemoveModification): """Deubiquitination modification."""
[docs]class Farnesylation(AddModification): """Farnesylation modification."""
[docs]class Defarnesylation(RemoveModification): """Defarnesylation modification."""
[docs]class Geranylgeranylation(AddModification): """Geranylgeranylation modification."""
[docs]class Degeranylgeranylation(RemoveModification): """Degeranylgeranylation modification."""
[docs]class Palmitoylation(AddModification): """Palmitoylation modification."""
[docs]class Depalmitoylation(RemoveModification): """Depalmitoylation modification."""
[docs]class Myristoylation(AddModification): """Myristoylation modification."""
[docs]class Demyristoylation(RemoveModification): """Demyristoylation modification."""
[docs]class Methylation(AddModification): """Methylation modification."""
[docs]class Demethylation(RemoveModification): """Demethylation modification."""
[docs]@python_2_unicode_compatible class RegulateActivity(Statement): """Regulation of activity. This class implements shared functionality of Activation and Inhibition statements and it should not be instantiated directly. """ # The constructor here is an abstractmethod so that this class cannot # be directly instantiated. __metaclass__ = abc.ABCMeta _agent_order = ['subj', 'obj'] @abc.abstractmethod def __init__(self): pass def __setstate__(self, state): if 'subj_activity' in state: logger.warning('Pickle file is out of date!') state.pop('subj_activity', None) self.__dict__.update(state) def matches_key(self): key = (stmt_type(self, True), self.subj.matches_key(), self.obj.matches_key(), str(self.obj_activity), str(self.is_activation)) return mk_str(key) def set_agent_list(self, agent_list): if len(agent_list) != 2: raise ValueError("%s has two agents." % self.__class__.__name__) self.subj = agent_list[0] self.obj = agent_list[1] def refinement_of(self, other, hierarchies): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False if self.is_activation != other.is_activation: return False if self.subj.refinement_of(other.subj, hierarchies) and \ self.obj.refinement_of(other.obj, hierarchies): obj_act_match = (self.obj_activity == other.obj_activity) or \ hierarchies['activity'].isa('INDRA_ACTIVITIES', self.obj_activity, 'INDRA_ACTIVITIES', other.obj_activity) if obj_act_match: return True else: return False else: return False def contradicts(self, other, hierarchies): # If they aren't opposite classes, it's not a contradiction if {self.__class__, other.__class__} != {Activation, Inhibition}: return False # If they aren't opposite classes, it's not a contradiction if self.is_activation == other.is_activation: return False # Skip all instances of not fully specified statements agents = (self.subj, self.obj, other.subj, other.obj) if not all(a is not None for a in agents): return False # If the entities don't match, they can't be contradicting # Here we check pairs of agents at each "position" and # make sure they are the same or they are refinements of each other for self_agent, other_agent in zip(self.agent_list(), other.agent_list()): if not (self_agent.entity_matches(other_agent) or \ self_agent.refinement_of(other_agent, hierarchies) or \ other_agent.refinement_of(self_agent, hierarchies)): return False # Otherwise they are contradicting return True
[docs] def to_json(self, use_sbo=False, matches_fun=None): generic = super(RegulateActivity, self).to_json(use_sbo, matches_fun) json_dict = _o(type=generic['type']) if self.subj is not None: json_dict['subj'] = self.subj.to_json() if use_sbo: if self.is_activation: json_dict['subj']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000459' # stimulator else: json_dict['subj']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000020' # inhibitor if self.obj is not None: json_dict['obj'] = self.obj.to_json() if use_sbo: if self.is_activation: json_dict['obj']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000643' # stimulated else: json_dict['obj']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000642' # inhibited if self.obj_activity is not None: json_dict['obj_activity'] = self.obj_activity json_dict.update(generic) return json_dict
@classmethod def _from_json(cls, json_dict): subj = json_dict.get('subj') obj = json_dict.get('obj') obj_activity = json_dict.get('obj_activity') if subj: subj = Agent._from_json(subj) if obj: obj = Agent._from_json(obj) stmt = cls(subj, obj, obj_activity) return stmt def __str__(self): obj_act_str = ', %s' % self.obj_activity if \ self.obj_activity != 'activity' else '' s = ("%s(%s, %s%s)" % (type(self).__name__, self.subj, self.obj, obj_act_str)) return s def __repr__(self): return self.__str__() def equals(self, other): matches = super(RegulateActivity, self).equals(other) matches = (matches and self.obj_activity == other.obj_activity and self.is_activation == other.is_activation) return matches def _get_activity_condition(self): """Return ActivityCondition corresponding to this RegulateActivity.""" return ActivityCondition(self.obj_activity, self.is_activation)
[docs]class Inhibition(RegulateActivity): """Indicates that a protein inhibits or deactivates another protein. This statement is intended to be used for physical interactions where the mechanism of inhibition is not explicitly specified, which is often the case for descriptions of mechanisms extracted from the literature. Parameters ---------- subj : :py:class:`Agent` The agent responsible for the change in activity, i.e., the "upstream" node. obj : :py:class:`Agent` The agent whose activity is influenced by the subject, i.e., the "downstream" node. obj_activity : Optional[str] The activity of the obj Agent that is affected, e.g., its "kinase" activity. evidence : None or :py:class:`Evidence` or list of :py:class:`Evidence` Evidence objects in support of the modification. """ def __init__(self, subj, obj, obj_activity='activity', evidence=None): super(RegulateActivity, self).__init__(evidence) self.subj = subj self.obj = obj if obj_activity not in activity_types: logger.warning('Invalid activity type: %s' % obj_activity) self.obj_activity = obj_activity self.is_activation = False
[docs]class Activation(RegulateActivity): """Indicates that a protein activates another protein. This statement is intended to be used for physical interactions where the mechanism of activation is not explicitly specified, which is often the case for descriptions of mechanisms extracted from the literature. Parameters ---------- subj : :py:class:`Agent` The agent responsible for the change in activity, i.e., the "upstream" node. obj : :py:class:`Agent` The agent whose activity is influenced by the subject, i.e., the "downstream" node. obj_activity : Optional[str] The activity of the obj Agent that is affected, e.g., its "kinase" activity. evidence : None or :py:class:`Evidence` or list of :py:class:`Evidence` Evidence objects in support of the modification. Examples -------- MEK (MAP2K1) activates the kinase activity of ERK (MAPK1): >>> mek = Agent('MAP2K1') >>> erk = Agent('MAPK1') >>> act = Activation(mek, erk, 'kinase') """ def __init__(self, subj, obj, obj_activity='activity', evidence=None): super(RegulateActivity, self).__init__(evidence) self.subj = subj self.obj = obj if obj_activity not in activity_types: logger.warning('Invalid activity type: %s' % obj_activity) self.obj_activity = obj_activity self.is_activation = True
[docs]class GtpActivation(Activation): pass
[docs]@python_2_unicode_compatible class ActiveForm(Statement): """Specifies conditions causing an Agent to be active or inactive. Types of conditions influencing a specific type of biochemical activity can include modifications, bound Agents, and mutations. Parameters ---------- agent : :py:class:`Agent` The Agent in a particular active or inactive state. The sets of ModConditions, BoundConditions, and MutConditions on the given Agent instance indicate the relevant conditions. activity : str The type of activity influenced by the given set of conditions, e.g., "kinase". is_active : bool Whether the conditions are activating (True) or inactivating (False). """ _agent_order = ['agent'] def __init__(self, agent, activity, is_active, evidence=None): super(ActiveForm, self).__init__(evidence) self.agent = agent if agent.activity is not None: logger.warning('Agent in ActiveForm should not have ' + 'ActivityConditions.') agent.activity = None if activity not in activity_types: logger.warning('Invalid activity type: %s' % activity) self.activity = activity self.is_active = is_active def matches_key(self): key = (stmt_type(self, True), self.agent.matches_key(), str(self.activity), str(self.is_active)) return mk_str(key) def set_agent_list(self, agent_list): if len(agent_list) != 1: raise ValueError("ActiveForm has one agent.") self.agent = agent_list[0] def refinement_of(self, other, hierarchies): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False # Check agent arguments if not self.agent.refinement_of(other.agent, hierarchies): return False # Make sure that the relationships and activities match if (self.is_active == other.is_active) and \ (self.activity == other.activity or hierarchies['activity'].isa('INDRA_ACTIVITIES', self.activity, 'INDRA_ACTIVITIES', other.activity)): return True else: return False def contradicts(self, other, hierarchies): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False # Check that the polarity is constradicting up front # TODO: we could also check for cases where the polarities are # the same but some of the state conditions have opposite # polarities, for instance, if the presence/lack of the # same modification activates a given agent, that could be # considered a contradiction. if self.is_active == other.is_active: return False # Check that the activity types are the same # TODO: we could check for refinements here if self.activity != other.activity: return False # If the agents are exactly the same, this is a contradiction if self.agent.matches_key() == other.agent.matches_key(): return True # Otherwise, if the two agents are related at the level of entities # and their state is exactly the same, then they contradict if self.agent.state_matches_key() == other.agent.state_matches_key(): if self.agent.isa(other.agent, hierarchies) or \ other.agent.isa(self.agent, hierarchies): return True return False
[docs] def to_json(self, use_sbo=False, matches_fun=None): generic = super(ActiveForm, self).to_json(use_sbo, matches_fun) json_dict = _o(type=generic['type']) json_dict.update({'agent': self.agent.to_json(), 'activity': self.activity, 'is_active': self.is_active}) if use_sbo: json_dict['agent']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000644' # modified json_dict.update(generic) return json_dict
@classmethod def _from_json(cls, json_dict): agent = json_dict.get('agent') if agent: agent = Agent._from_json(agent) else: logger.error('ActiveForm statement missing agent') return None activity = json_dict.get('activity') is_active = json_dict.get('is_active') if activity is None: logger.warning('ActiveForm activity missing, defaulting ' + 'to `activity`') activity = 'activity' if is_active is None: logger.warning('ActiveForm is_active missing, defaulting ' + 'to True') is_active = True stmt = cls(agent, activity, is_active) return stmt def __str__(self): s = ("ActiveForm(%s, %s, %s)" % (self.agent, self.activity, self.is_active)) return s def equals(self, other): matches = super(ActiveForm, self).equals(other) matches = (matches and self.activity == other.activity and self.is_active == other.is_active) return matches
[docs]@python_2_unicode_compatible class HasActivity(Statement): """States that an Agent has or doesn't have a given activity type. With this Statement, one cane express that a given protein is a kinase, or, for instance, that it is a transcription factor. It is also possible to construct negative statements with which one epxresses, for instance, that a given protein is not a kinase. Parameters ---------- agent : :py:class:`Agent` The Agent that that statement is about. Note that the detailed state of the Agent is not relevant for this type of statement. activity : str The type of activity, e.g., "kinase". has_activity : bool Whether the given Agent has the given activity (True) or not (False). """ _agent_order = ['agent'] def __init__(self, agent, activity, has_activity, evidence=None): super(HasActivity, self).__init__(evidence) if agent.activity is not None: logger.warning('Agent in HasActivity should not have ' + 'ActivityConditions.') agent.activity = None self.agent = agent if activity not in activity_types: logger.warning('Invalid activity type: %s' % activity) self.activity = activity self.has_activity = has_activity def matches_key(self): key = (stmt_type(self, True), self.agent.matches_key(), str(self.activity), str(self.has_activity)) return mk_str(key) def set_agent_list(self, agent_list): if len(agent_list) != 1: raise ValueError("HasActivity has one agent.") self.agent = agent_list[0] def refinement_of(self, other, hierarchies): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False # Check agent arguments if not self.agent.refinement_of(other.agent, hierarchies): return False # Make sure that the relationships and activities match if (self.has_activity == other.has_activity) and \ (self.activity == other.activity or hierarchies['activity'].isa(self.activity, other.activity)): return True else: return False def __str__(self): s = ("HasActivity(%s, %s, %s)" % (self.agent, self.activity, self.has_activity)) return s def equals(self, other): matches = super(HasActivity, self).equals(other) matches = (matches and self.activity == other.activity and self.has_activity == other.has_activity) return matches
[docs]@python_2_unicode_compatible class Gef(Statement): """Exchange of GTP for GDP on a small GTPase protein mediated by a GEF. Represents the generic process by which a guanosine exchange factor (GEF) catalyzes nucleotide exchange on a GTPase protein. Parameters ---------- gef : :py:class:`Agent` The guanosine exchange factor. ras : :py:class:`Agent` The GTPase protein. Examples -------- SOS1 catalyzes nucleotide exchange on KRAS: >>> sos = Agent('SOS1') >>> kras = Agent('KRAS') >>> gef = Gef(sos, kras) """ _agent_order = ['gef', 'ras'] def __init__(self, gef, ras, evidence=None): super(Gef, self).__init__(evidence) self.gef = gef self.ras = ras def matches_key(self): key = (stmt_type(self, True), self.gef.matches_key(), self.ras.matches_key()) return mk_str(key) def set_agent_list(self, agent_list): if len(agent_list) != 2: raise ValueError("Gef has two agents.") self.gef = agent_list[0] self.ras = agent_list[1] def __str__(self): s = "Gef(%s, %s)" % (self.gef.name, self.ras.name) return s def refinement_of(self, other, hierarchies): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False # Check the GEF if self.gef.refinement_of(other.gef, hierarchies) and \ self.ras.refinement_of(other.ras, hierarchies): return True else: return False def equals(self, other): matches = super(Gef, self).equals(other) return matches
[docs] def to_json(self, use_sbo=False, matches_fun=None): generic = super(Gef, self).to_json(use_sbo, matches_fun) json_dict = _o(type=generic['type']) if self.gef is not None: json_dict['gef'] = self.gef.to_json() if use_sbo: json_dict['gef']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000013' # catalyst if self.ras is not None: json_dict['ras'] = self.ras.to_json() if use_sbo: json_dict['ras']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000015' # substrate json_dict.update(generic) return json_dict
@classmethod def _from_json(cls, json_dict): gef = json_dict.get('gef') ras = json_dict.get('ras') if gef: gef = Agent._from_json(gef) if ras: ras = Agent._from_json(ras) stmt = cls(gef, ras) return stmt
[docs]@python_2_unicode_compatible class Gap(Statement): """Acceleration of a GTPase protein's GTP hydrolysis rate by a GAP. Represents the generic process by which a GTPase activating protein (GAP) catalyzes GTP hydrolysis by a particular small GTPase protein. Parameters ---------- gap : :py:class:`Agent` The GTPase activating protein. ras : :py:class:`Agent` The GTPase protein. Examples -------- RASA1 catalyzes GTP hydrolysis on KRAS: >>> rasa1 = Agent('RASA1') >>> kras = Agent('KRAS') >>> gap = Gap(rasa1, kras) """ _agent_order = ['gap', 'ras'] def __init__(self, gap, ras, evidence=None): super(Gap, self).__init__(evidence) self.gap = gap self.ras = ras def matches_key(self): key = (stmt_type(self, True), self.gap.matches_key(), self.ras.matches_key()) return mk_str(key) def set_agent_list(self, agent_list): if len(agent_list) != 2: raise ValueError("Gap has two agents.") self.gap = agent_list[0] self.ras = agent_list[1] def refinement_of(self, other, hierarchies): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False # Check the GAP if self.gap.refinement_of(other.gap, hierarchies) and \ self.ras.refinement_of(other.ras, hierarchies): return True else: return False def __str__(self): s = "Gap(%s, %s)" % (self.gap.name, self.ras.name) return s def equals(self, other): matches = super(Gap, self).equals(other) return matches
[docs] def to_json(self, use_sbo=False, matches_fun=None): generic = super(Gap, self).to_json(use_sbo, matches_fun) json_dict = _o(type=generic['type']) if self.gap is not None: json_dict['gap'] = self.gap.to_json() if use_sbo: json_dict['gap']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000013' # catalyst if self.ras is not None: json_dict['ras'] = self.ras.to_json() if use_sbo: json_dict['ras']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000015' # substrate json_dict.update(generic) return json_dict
@classmethod def _from_json(cls, json_dict): gap = json_dict.get('gap') ras = json_dict.get('ras') if gap: gap = Agent._from_json(gap) if ras: ras = Agent._from_json(ras) stmt = cls(gap, ras) return stmt
[docs]@python_2_unicode_compatible class Complex(Statement): """A set of proteins observed to be in a complex. Parameters ---------- members : list of :py:class:`Agent` The set of proteins in the complex. Examples -------- BRAF is observed to be in a complex with RAF1: >>> braf = Agent('BRAF') >>> raf1 = Agent('RAF1') >>> cplx = Complex([braf, raf1]) """ _agent_order = ['members'] def __init__(self, members, evidence=None): super(Complex, self).__init__(evidence) self.members = members def matches_key(self): key = (stmt_type(self, True), tuple(m.matches_key() for m in self.sorted_members())) return mk_str(key) def sorted_members(self): return sorted(self.members, key=lambda x: x.matches_key()) def entities_match_key(self): key = tuple(a.entity_matches_key() if a is not None else None for a in sorted(self.members, key=lambda x: x.matches_key())) return key def set_agent_list(self, agent_list): self.members = agent_list def __str__(self): s = '%s(%s)' % (type(self).__name__, (', '.join([('%s' % m) for m in self.members]))) return s def refinement_of(self, other, hierarchies): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False # Make sure the length of the members list is the same. Note that this # treats Complex([A, B, C]) as distinct from Complex([A, B]), rather # than as a refinement. if len(self.members) != len(other.members): return False def match_members(self_members, other_members): # First build a bipartite graph of refinement links G = networkx.Graph() for (self_idx, self_member), (other_idx, other_member) in \ itertools.product(enumerate(self_members), enumerate(other_members)): if self_member.refinement_of(other_member, hierarchies): G.add_edge('S%d' % self_idx, 'O%d' % other_idx) # Then find a maximal matching in the bipartite graph match = networkx.algorithms.max_weight_matching(G) # If every member has a pair, it is a valid refinement return len(match) == len(self_members) return match_members(self.members, other.members)
[docs] def to_json(self, use_sbo=False, matches_fun=None): generic = super(Complex, self).to_json(use_sbo, matches_fun) json_dict = _o(type=generic['type']) members = [m.to_json() for m in self.members] json_dict['members'] = members json_dict.update(generic) return json_dict
@classmethod def _from_json(cls, json_dict): members = json_dict.get('members') members = [Agent._from_json(m) for m in members] stmt = cls(members) return stmt
[docs]@python_2_unicode_compatible class Translocation(Statement): """The translocation of a molecular agent from one location to another. Parameters ---------- agent : :py:class:`Agent` The agent which translocates. from_location : Optional[str] The location from which the agent translocates. This must be a valid GO cellular component name (e.g. "cytoplasm") or ID (e.g. "GO:0005737"). to_location : Optional[str] The location to which the agent translocates. This must be a valid GO cellular component name or ID. """ _agent_order = ['agent'] def __init__(self, agent, from_location=None, to_location=None, evidence=None): super(Translocation, self).__init__(evidence) self.agent = agent self.from_location = get_valid_location(from_location) self.to_location = get_valid_location(to_location) def set_agent_list(self, agent_list): if(len(agent_list) != 1): raise ValueError("Translocation has 1 agent") self.agent = agent_list[0] def __str__(self): s = ("Translocation(%s, %s, %s)" % (self.agent, self.from_location, self.to_location)) return s def refinement_of(self, other, hierarchies=None): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False # Check several conditions for refinement ch = hierarchies['cellular_component'] ref1 = self.agent.refinement_of(other.agent, hierarchies) ref2 = (other.from_location is None or self.from_location == other.from_location or ch.partof('INDRA_LOCATIONS', self.from_location, 'INDRA_LOCATIONS', other.from_location)) ref3 = (other.to_location is None or self.to_location == other.to_location or ch.partof('INDRA_LOCATIONS', self.to_location, 'INDRA_LOCATIONS', other.to_location)) return (ref1 and ref2 and ref3) def equals(self, other): matches = super(Translocation, self).equals(other) matches = matches and (self.from_location == other.from_location) matches = matches and (self.to_location == other.to_location) return matches def matches_key(self): key = (stmt_type(self, True), self.agent.matches_key(), str(self.from_location), str(self.to_location)) return mk_str(key)
[docs] def to_json(self, use_sbo=False, matches_fun=None): generic = super(Translocation, self).to_json(use_sbo, matches_fun) json_dict = _o(type=generic['type']) json_dict['agent'] = self.agent.to_json() if self.from_location is not None: json_dict['from_location'] = self.from_location if self.to_location is not None: json_dict['to_location'] = self.to_location json_dict.update(generic) return json_dict
@classmethod def _from_json(cls, json_dict): agent = json_dict.get('agent') if agent: agent = Agent._from_json(agent) else: logger.error('Translocation statement missing agent') return None from_location = json_dict.get('from_location') to_location = json_dict.get('to_location') stmt = cls(agent, from_location, to_location) return stmt
[docs]@python_2_unicode_compatible class RegulateAmount(Statement): """Superclass handling operations on directed, two-element interactions.""" _agent_order = ['subj', 'obj'] def __init__(self, subj, obj, evidence=None): super(RegulateAmount, self).__init__(evidence) self.subj = subj if obj is None: raise ValueError('Object of %s cannot be None.' % type(self).__name__) self.obj = obj def matches_key(self): if self.subj is None: subj_key = None else: subj_key = self.subj.matches_key() key = (stmt_type(self, True), subj_key, self.obj.matches_key()) return mk_str(key) def set_agent_list(self, agent_list): if len(agent_list) != 2: raise ValueError("%s has two agents in agent_list." % type(self).__name__) self.subj = agent_list[0] self.obj = agent_list[1]
[docs] def to_json(self, use_sbo=False, matches_fun=None): generic = super(RegulateAmount, self).to_json(use_sbo, matches_fun) json_dict = _o(type=generic['type']) if self.subj is not None: json_dict['subj'] = self.subj.to_json() if use_sbo: if isinstance(self, IncreaseAmount): json_dict['subj']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000459' # stimulator else: json_dict['subj']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000020' # inhibitor if self.obj is not None: json_dict['obj'] = self.obj.to_json() if use_sbo: if isinstance(self, IncreaseAmount): json_dict['obj']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000011' # product else: json_dict['obj']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000010' # reactant json_dict.update(generic) return json_dict
@classmethod def _from_json(cls, json_dict): subj = json_dict.get('subj') obj = json_dict.get('obj') if subj: subj = Agent._from_json(subj) if obj: obj = Agent._from_json(obj) stmt = cls(subj, obj) return stmt def refinement_of(self, other, hierarchies): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False # Check agent arguments if self.subj is None and other.subj is None: subj_refinement = True elif self.subj is None and other.subj is not None: subj_refinement = False elif self.subj is not None and other.subj is None: subj_refinement = True else: subj_refinement = self.subj.refinement_of(other.subj, hierarchies) obj_refinement = self.obj.refinement_of(other.obj, hierarchies) return (subj_refinement and obj_refinement) def equals(self, other): matches = super(RegulateAmount, self).equals(other) return matches def contradicts(self, other, hierarchies): # If they aren't opposite classes, it's not a contradiction if {self.__class__, other.__class__} != \ {IncreaseAmount, DecreaseAmount}: return False # Skip all instances of not fully specified statements agents = (self.subj, self.obj, other.subj, other.obj) if not all(a is not None for a in agents): return False # If the entities don't match, they can't be contradicting # Here we check pairs of agents at each "position" and # make sure they are the same or they are refinements of each other for self_agent, other_agent in zip(self.agent_list(), other.agent_list()): if not (self_agent.entity_matches(other_agent) or \ self_agent.refinement_of(other_agent, hierarchies) or \ other_agent.refinement_of(self_agent, hierarchies)): return False # Otherwise they are contradicting return True def __str__(self): s = ("%s(%s, %s)" % (type(self).__name__, self.subj, self.obj)) return s
[docs]class DecreaseAmount(RegulateAmount): """Degradation of a protein, possibly mediated by another protein. Note that this statement can also be used to represent inhibitors of synthesis (e.g., cycloheximide). Parameters ---------- subj : :py:class:`indra.statement.Agent` The protein mediating the degradation. obj : :py:class:`indra.statement.Agent` The protein that is degraded. evidence : None or :py:class:`Evidence` or list of :py:class:`Evidence` Evidence objects in support of the degradation statement. """
[docs]class IncreaseAmount(RegulateAmount): """Synthesis of a protein, possibly mediated by another protein. Parameters ---------- subj : :py:class:`indra.statement.Agent` The protein mediating the synthesis. obj : :py:class:`indra.statement.Agent` The protein that is synthesized. evidence : None or :py:class:`Evidence` or list of :py:class:`Evidence` Evidence objects in support of the synthesis statement. """
[docs]class Influence(Statement): """An influence on the quantity of a concept of interest. Parameters ---------- subj : :py:class:`indra.statement.Event` The event which acts as the influencer. obj : :py:class:`indra.statement.Event` The event which acts as the influencee. evidence : None or :py:class:`Evidence` or list of :py:class:`Evidence` Evidence objects in support of the statement. """ _agent_order = ['subj', 'obj']
[docs] def agent_list(self, deep_sorted=False): return [self.subj.concept, self.obj.concept]
def __init__(self, subj, obj, evidence=None): super(Influence, self).__init__(evidence) self.subj = subj self.obj = obj def refinement_of(self, other, hierarchies): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False # Check agent arguments subj_refinement = self.subj.concept.refinement_of( other.subj.concept, hierarchies) obj_refinement = self.obj.concept.refinement_of( other.obj.concept, hierarchies) op = other.overall_polarity() sp = self.overall_polarity() # If we have "less" polarity here than in other then it's # not a refinement. if self.polarity_count() < other.polarity_count(): delta_refinement = False # If we have some polarity and the other doesn't then it's # always a refinement elif sp is not None and op is None: delta_refinement = True # Otherwise we need to check if the overall polarity matches, if it # does then this is a refinement. Otherwise it isn't. else: delta_refinement = (op == sp) return (subj_refinement and obj_refinement and delta_refinement) def equals(self, other): equals = super(Influence, self).equals(other) and \ self.subj.equals(other.subj) and self.obj.equals(other.obj) return equals def matches_key(self): # With polarities, here, the goal is to match overall polarity # if both polarities are given, i.e. +/+ matches -/-. Also, if only # one polarity is given, we match the overall polarity e.g. # None/+, +/None will match. key = (stmt_type(self, True), self.subj.matches_key(), self.obj.matches_key(), self.polarity_count(), self.overall_polarity() ) return mk_str(key) def contradicts(self, other, hierarchies): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False # Determine some refinements and opposites up front subj_ref = self.subj.concept.refinement_of(other.subj.concept, hierarchies) or \ other.subj.concept.refinement_of(self.subj.concept, hierarchies) obj_ref = self.obj.concept.refinement_of(other.obj.concept, hierarchies) or \ other.obj.concept.refinement_of(self.obj.concept, hierarchies) subj_opp = self.subj.concept.is_opposite(other.subj.concept, hierarchies) obj_opp = self.obj.concept.is_opposite(other.obj.concept, hierarchies) sp = self.overall_polarity() op = other.overall_polarity() # If all entities are "compatible" or mutually opposites # but the polarities are explicitly different then this is # a contradiction if (subj_ref and obj_ref) or (subj_opp and obj_opp): if sp is not None and op is not None and sp != op: return True # If one entity is the opposite and the other compatible and the # polarities are the same then this is a contradiction if (subj_ref and obj_opp) or (subj_opp and obj_ref): if sp is not None and op is not None and sp == op: return True return False def overall_polarity(self): # Set p1 and p2 to None / 1 / -1 depending on polarity p1 = self.subj.delta.polarity p2 = self.obj.delta.polarity if p1 is None and p2 is None: pol = None elif p2 is None: pol = p1 elif p1 is None: pol = p2 else: pol = p1 * p2 return pol
[docs] def flip_polarity(self, agent_idx): if agent_idx == 0: self.subj.flip_polarity() elif agent_idx == 1: self.obj.flip_polarity()
def polarity_count(self): return ((1 if self.subj.delta.polarity is not None else 0) + (1 if self.obj.delta.polarity is not None else 0))
[docs] def to_json(self, use_sbo=False, matches_fun=None): generic = super(Influence, self).to_json(use_sbo, matches_fun) json_dict = _o(type=generic['type'], subj=self.subj.to_json(with_evidence=False, matches_fun=matches_fun), obj=self.obj.to_json(with_evidence=False, matches_fun=matches_fun)) json_dict.update(generic) return json_dict
@classmethod def _from_json(cls, json_dict): subj = json_dict.get('subj') obj = json_dict.get('obj') if subj: subj = Statement._from_json(subj) if obj: obj = Statement._from_json(obj) stmt = cls(subj, obj) return stmt def __repr__(self): if sys.version_info[0] >= 3: return self.__str__() else: return self.__str__().encode('utf-8') def __str__(self): s = "%s(%s, %s)" % (type(self).__name__, str(self.subj), str(self.obj)) return s
[docs]class Association(Complex): """A set of events associated with each other without causal relationship. Parameters ---------- members : list of :py:class:Event A list of events associated with each other. evidence : None or :py:class:`Evidence` or list of :py:class:`Evidence` Evidence objects in support of the modification. """ _agent_order = ['members'] def __init__(self, members, evidence=None): if len(members) != 2: raise ValueError('Association Statement can only have 2 members, ' '%d were given.' % len(members)) super().__init__(members, evidence) def matches_key(self): key = (stmt_type(self, True), tuple(m.matches_key() for m in self.sorted_members()), self.polarity_count(), self.overall_polarity() ) return mk_str(key) def overall_polarity(self): p1 = self.members[0].delta.polarity p2 = self.members[1].delta.polarity if p1 is None and p2 is None: pol = None elif p2 is None: pol = p1 elif p1 is None: pol = p2 else: pol = p1 * p2 return pol def polarity_count(self): return sum( 1 if m.delta.polarity is not None else 0 for m in self.members)
[docs] def agent_list(self, deep_sorted=False): members = self.members if not deep_sorted else \ sorted_agents(self.members) return [m.concept for m in members]
def refinement_of(self, other, hierarchies): members_refinement = super().refinement_of(other, hierarchies) op = other.overall_polarity() sp = self.overall_polarity() if self.polarity_count() < other.polarity_count(): delta_refinement = False elif sp is not None and op is None: delta_refinement = True else: delta_refinement = (op == sp) return (members_refinement and delta_refinement) def equals(self, other): def members_equal(a1, a2): if len(a1.members) == len(a2.members): for m1, m2 in zip(a1.members, a2.members): if (m1 is None and m2 is not None) or \ (m1 is not None and m2 is None): return False if m1 is not None and m2 is not None and not m1.equals(m2): return False else: return False return True equals = super().equals(other) and members_equal(self, other) return equals def contradicts(self, other, hierarchies): if stmt_type(self) != stmt_type(other): return False def match_members(self_members, other_members): rel_types = {'refinement_of': 0, 'is_opposite': 0} G = networkx.Graph() for (self_idx, self_member), (other_idx, other_member) in \ itertools.product(enumerate(self_members), enumerate(other_members)): if ('S%d' % self_idx) in G or ('O%d' % other_idx) in G: continue if self_member.concept.refinement_of(other_member.concept, hierarchies): G.add_edge('S%d' % self_idx, 'O%d' % other_idx) rel_types['refinement_of'] += 1 elif self_member.concept.is_opposite(other_member.concept, hierarchies): G.add_edge('S%d' % self_idx, 'O%d' % other_idx) rel_types['is_opposite'] += 1 return rel_types rel_types = match_members(self.members, other.members) sp = self.overall_polarity() op = other.overall_polarity() # If all entities are "compatible" or mutually opposites # but the polarities are explicitly different then this is # a contradiction if set(rel_types.values()) == set([0, 2]): if sp is not None and op is not None and sp != op: return True # If one entity is the opposite and the other compatible and the # polarities are the same then this is a contradiction if set(rel_types.values()) == set([1, 1]): if sp is not None and op is not None and sp == op: return True return False
[docs] def flip_polarity(self, agent_idx): self.members[agent_idx].flip_polarity()
[docs] def to_json(self, use_sbo=False, matches_fun=None): # Get generic from two inheritance levels above - from Statement class generic = super(Complex, self).to_json(use_sbo, matches_fun) json_dict = _o(type=generic['type']) members = [m.to_json(with_evidence=False, matches_fun=matches_fun) for m in self.members] json_dict['members'] = members json_dict.update(generic) return json_dict
@classmethod def _from_json(cls, json_dict): members = json_dict.get('members') members = [Statement._from_json(m) for m in members] stmt = cls(members) return stmt
[docs]class Conversion(Statement): """Conversion of molecular species mediated by a controller protein. Parameters ---------- subj : :py:class:`indra.statement.Agent` The protein mediating the conversion. obj_from : list of :py:class:`indra.statement.Agent` The list of molecular species being consumed by the conversion. obj_to : list of :py:class:`indra.statement.Agent` The list of molecular species being created by the conversion. evidence : None or :py:class:`Evidence` or list of :py:class:`Evidence` Evidence objects in support of the synthesis statement. """ _agent_order = ['subj', 'obj_from', 'obj_to'] def __init__(self, subj, obj_from=None, obj_to=None, evidence=None): super(Conversion, self).__init__(evidence=evidence) self.subj = subj self.obj_from = obj_from if obj_from is not None else [] if isinstance(obj_from, Agent): self.obj_from = [obj_from] self.obj_to = obj_to if obj_to is not None else [] if isinstance(obj_to, Agent): self.obj_to = [obj_to] def matches_key(self): keys = [stmt_type(self, True)] keys += [self.subj.matches_key() if self.subj else None] keys += [agent.matches_key() for agent in sorted_agents(self.obj_to)] keys += [agent.matches_key() for agent in sorted_agents(self.obj_from)] return mk_str(keys) def set_agent_list(self, agent_list): num_obj_from = len(self.obj_from) num_obj_to = len(self.obj_to) if len(agent_list) != 1 + num_obj_from + num_obj_to: raise Exception('Conversion agent number must be preserved ' 'when setting agent list.') self.subj = agent_list[0] self.obj_from = agent_list[1:num_obj_from+1] self.obj_to = agent_list[num_obj_from+1:]
[docs] def to_json(self, use_sbo=False, matches_fun=None): generic = super(Conversion, self).to_json(use_sbo, matches_fun) json_dict = _o(type=generic['type']) if self.subj is not None: json_dict['subj'] = self.subj.to_json() if use_sbo: json_dict['subj']['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000013' # catalyst json_dict['obj_from'] = [o.to_json() for o in self.obj_from] if use_sbo: for of in json_dict['obj_from']: of['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000010' # reactant json_dict['obj_to'] = [o.to_json() for o in self.obj_to] if use_sbo: for ot in json_dict['obj_to']: ot['sbo'] = \ 'http://identifiers.org/sbo/SBO:0000011' # product json_dict.update(generic) return json_dict
@classmethod def _from_json(cls, json_dict): subj = json_dict.get('subj') obj_from = json_dict.get('obj_from') obj_to = json_dict.get('obj_to') if subj: subj = Agent._from_json(subj) if obj_from: obj_from = [Agent._from_json(o) for o in obj_from] if obj_to: obj_to = [Agent._from_json(o) for o in obj_to] stmt = cls(subj, obj_from, obj_to) return stmt def refinement_of(self, other, hierarchies): # Make sure the statement types match if stmt_type(self) != stmt_type(other): return False if self.subj is None and other.subj is None: subj_refinement = True elif self.subj is None and other.subj is not None: subj_refinement = False elif self.subj is not None and other.subj is None: subj_refinement = True else: subj_refinement = self.subj.refinement_of(other.subj, hierarchies) def refinement_agents(lst1, lst2): if len(lst1) != len(lst2): return False # Check that every agent in other is refined in self, but # only once! self_match_indices = set([]) for other_agent in lst2: for self_agent_ix, self_agent in enumerate(lst1): if self_agent_ix in self_match_indices: continue if self_agent.refinement_of(other_agent, hierarchies): self_match_indices.add(self_agent_ix) break if len(self_match_indices) != len(lst2): return False return True obj_from_refinement = refinement_agents(self.obj_from, other.obj_from) obj_to_refinement = refinement_agents(self.obj_to, other.obj_to) return (subj_refinement and obj_from_refinement and obj_to_refinement) def equals(self, other): matches = super(Conversion, self).equals(other) return matches def __str__(self): s = ("%s(%s, %s, %s)" % (type(self).__name__, self.subj, self.obj_from, self.obj_to)) return s
[docs]class Event(Statement): """An event representing the change of a Concept. Attributes ---------- concept : indra.statements.concept.Concept The concept over which the event is defined. delta : indra.statements.delta.Delta Represents a change in the concept, with a polarity and an adjectives entry. context : indra.statements.context.Context The context associated with the event. """ _agent_order = ['concept'] def __init__(self, concept, delta=None, context=None, evidence=None, supports=None, supported_by=None): super().__init__(evidence, supports, supported_by) self.concept = concept self.delta = delta if delta else QualitativeDelta( polarity=None, adjectives=None) self.context = context def matches_key(self): mk = (self.concept.matches_key(),) return str(mk) def refinement_of(self, other, hierarchies): concept_ref = self.concept.refinement_of(other.concept, hierarchies) pol_ref = (self.delta.polarity and not other.delta.polarity) or \ self.delta.polarity == other.delta.polarity return concept_ref and pol_ref def equals(self, other): return self.concept.equals(other.concept) and \ self.delta.equals(other.delta)
[docs] def to_json(self, with_evidence=True, use_sbo=False, matches_fun=None): generic = super(Event, self).to_json(False, matches_fun) json_dict = _o(type=generic['type'], concept=self.concept.to_json(), delta=self.delta.to_json()) if self.context: json_dict['context'] = self.context.to_json() if with_evidence and 'evidence' in generic: json_dict['evidence'] = generic['evidence'] json_dict.update(generic) return json_dict
@classmethod def _from_json(cls, json_dict): concept = Concept._from_json(json_dict['concept']) delta = QualitativeDelta.from_json(json_dict['delta']) context_entry = json_dict.get('context') if context_entry: context = Context.from_json(json_dict['context']) else: context = None stmt = cls(concept, delta=delta, context=context) return stmt
[docs] def flip_polarity(self, agent_idx=None): # If we have an explicit polarity, flip it, otherwise do nothing if self.delta.polarity == 1: self.delta.polarity = -1 elif self.delta.polarity == -1: self.delta.polarity = 1
def __str__(self): return '%s(%s)' % (type(self).__name__, self.concept.name)
[docs]class Migration(Event): """A special class of Event representing Migration.""" _agent_order = ['concept'] def __init__(self, concept, delta=None, context=None, evidence=None, supports=None, supported_by=None): self.delta = delta if delta else QuantitativeState(entity='person') super().__init__(concept, self.delta, context, evidence, supports, supported_by) @classmethod def _from_json(cls, json_dict): concept = Concept._from_json(json_dict['concept']) delta = QuantitativeState.from_json(json_dict['delta']) context_entry = json_dict.get('context') if context_entry: context = Context.from_json(json_dict['context']) else: context = None stmt = cls(concept, delta=delta, context=context) return stmt
[docs]class Unresolved(Statement): """A special statement type used in support when a uuid can't be resolved. When using the `stmts_from_json` method, it is sometimes not possible to resolve the uuid found in `support` and `supported_by` in the json representation of an indra statement. When this happens, this class is used as a place-holder, carrying only the uuid of the statement. """ def __init__(self, uuid_str=None, shallow_hash=None, full_hash=None): super(Unresolved, self).__init__() self.uuid = uuid_str self._shallow_hash = shallow_hash self._full_hash = full_hash assert self.uuid or self._shallow_hash or self._full_hash,\ "Some identifying information must be given." def __str__(self): if self.uuid: return "%s(uuid=%s)" % (type(self).__name__, self.uuid) elif self._shallow_hash: return "%s(shallow_hash=%s)" % (type(self).__name__, self._shallow_hash) else: return "%s(full_hash=%s)" % (type(self).__name__, self._full_hash)
# Mapping between modification type strings and subclasses of Modification modtype_to_modclass = {str(cls.__name__.lower()): cls for cls in AddModification.__subclasses__() + RemoveModification.__subclasses__()} # Add modification as a generic type modtype_to_modclass['modification'] = Modification modclass_to_modtype = {cls: str(cls.__name__.lower()) for cls in AddModification.__subclasses__() + RemoveModification.__subclasses__()} # Add modification as a generic type modclass_to_modtype[Modification] = 'modification' modclass_to_modtype[Autophosphorylation] = 'phosphorylation' modclass_to_modtype[Transphosphorylation] = 'phosphorylation' # These are the modification types that are valid in ModConditions modtype_conditions = [modclass_to_modtype[mt] for mt in AddModification.__subclasses__()] modtype_conditions.append('modification') def _get_mod_inverse_maps(): modtype_to_inverse = {} modclass_to_inverse = {} for cls in AddModification.__subclasses__(): modtype = modclass_to_modtype[cls] modtype_inv = 'de' + modtype cls_inv = modtype_to_modclass[modtype_inv] modtype_to_inverse[modtype] = modtype_inv modtype_to_inverse[modtype_inv] = modtype modclass_to_inverse[cls] = cls_inv modclass_to_inverse[cls_inv] = cls return modtype_to_inverse, modclass_to_inverse modtype_to_inverse, modclass_to_inverse = _get_mod_inverse_maps() from .io import * from .agent import * stmt_sbo_map = { 'acetylation': '0000215', 'glycosylation': '0000217', 'hydroxylation': '0000233', 'methylation': '0000214', 'myristoylation': '0000219', 'palmitoylation': '0000218', 'phosphorylation': '0000216', 'farnesylation': '0000222', 'geranylgeranylation': '0000223', 'ubiquitination': '0000224', 'dephosphorylation': '0000330', 'addmodification': '0000210', # addition of a chemical group 'removemodification': '0000211', # removal of a chemical group 'modification': '0000182', # conversion 'conversion': '0000182', # conversion 'autophosphorylation': '0000216', # phosphorylation 'transphosphorylation': '0000216', # phosphorylation 'decreaseamount': '0000179', # degradation 'increaseamount': '0000183', # transcription 'complex': '0000526', # protein complex formation 'translocation': '0000185', # transport reaction 'regulateactivity': '0000182', # conversion 'activeform': '0000412', # biological activity 'rasgef': '0000172', # catalysis 'rasgap': '0000172', # catalysis 'statement': '0000231' # occuring entity representation } def sorted_agents(agent_list): return sorted(agent_list, key=lambda ag: ag.matches_key())
[docs]def get_all_descendants(parent): """Get all the descendants of a parent class, recursively.""" children = parent.__subclasses__() descendants = children[:] for child in children: descendants += get_all_descendants(child) return descendants
# In the future, when hierarchy is no longer determined by sub-classing, this # function should be altered to account for the change. def get_type_hierarchy(s): """Get the sequence of parents from `s` to Statement. Parameters ---------- s : a class or instance of a child of Statement For example the statement `Phosphorylation(MEK(), ERK())` or just the class `Phosphorylation`. Returns ------- parent_list : list[types] A list of the types leading up to Statement. Examples -------- >> s = Phosphorylation(MAPK1(), Elk1()) >> get_type_hierarchy(s) [Phosphorylation, AddModification, Modification, Statement] >> get_type_hierarchy(AddModification) [AddModification, Modification, Statement] """ tp = type(s) if not isinstance(s, type) else s p_list = [tp] for p in tp.__bases__: if p is not Statement: p_list.extend(get_type_hierarchy(p)) else: p_list.append(p) return p_list
[docs]class NotAStatementName(Exception): pass
[docs]def get_statement_by_name(stmt_name): """Get a statement class given the name of the statement class.""" stmt_classes = get_all_descendants(Statement) for stmt_class in stmt_classes: if stmt_class.__name__.lower() == stmt_name.lower(): return stmt_class raise NotAStatementName('\"%s\" is not recognized as a statement type!' % stmt_name)
[docs]def make_statement_camel(stmt_name): """Makes a statement name match the case of the corresponding statement.""" return get_statement_by_name(stmt_name).__name__
[docs]def get_unresolved_support_uuids(stmts): """Get uuids unresolved in support from stmts from stmts_from_json.""" return {s.uuid for stmt in stmts for s in stmt.supports + stmt.supported_by if isinstance(s, Unresolved)}
[docs]def stmt_type(obj, mk=True): """Return standardized, backwards compatible object type String. This is a temporary solution to make sure type comparisons and matches keys of Statements and related classes are backwards compatible. """ if isinstance(obj, Statement) and mk: return type(obj) else: return type(obj).__name__
def mk_str(mk): """Replace class path for backwards compatibility of matches keys.""" return str(mk).replace('indra.statements.statements', 'indra.statements')