"""
A module for representing simple graph-oriented views of an ontology
See also:
- ontol_factory.py
"""
import networkx as nx
import logging
import re
logger = logging.getLogger(__name__)
[docs]class Ontology():
"""An object that represents a basic graph-oriented view over an ontology.
The ontology may be represented in memory, or it may be located
remotely. See subclasses for details.
The default implementation is an in-memory wrapper onto the python networkx library
"""
def __init__(self,
handle=None,
id=None,
graph=None,
xref_graph=None,
meta=None,
payload=None,
graphdoc=None):
"""
initializes based on an ontology name.
**Note**: do not call this directly, use OntologyFactory instead
"""
self.handle = handle
self.meta = meta
if id is None:
if payload is not None:
id = payload.get('id')
if id is None:
id = handle
self.id = id
# networkx object
self.graph = graph
if self.graph is None:
self.graph = nx.MultiDiGraph()
logger.debug('Graph initialized, nodes={}'.format(self.graph.nodes()))
self.xref_graph = xref_graph
# obograph
self.graphdoc = graphdoc
self.all_logical_definitions = []
self.all_property_chain_axioms = []
# alternatively accept a payload object
if payload is not None:
self.meta = payload.get('meta')
self.graph = payload.get('graph')
self.xref_graph = payload.get('xref_graph')
self.graphdoc = payload.get('graphdoc')
self.all_logical_definitions = payload.get('logical_definitions')
self.all_property_chain_axioms = payload.get('property_chain_axioms')
def __str__(self):
return '{} handle: {} meta: {}'.format(self.id, self.handle, self.meta)
def __repr__(self):
return self.__str__()
[docs] def get_graph(self):
"""
Return a networkx graph for the whole ontology.
Note: Only implemented for *eager* implementations
Return
------
nx.MultiDiGraph
A networkx MultiDiGraph object representing the complete ontology
"""
return self.graph
# consider caching
[docs] def get_filtered_graph(self, relations=None, prefix=None):
"""
Returns a networkx graph for the whole ontology, for a subset of relations
Only implemented for eager methods.
Implementation notes: currently this is not cached
Arguments
---------
- relations : list
list of object property IDs, e.g. subClassOf, BFO:0000050. If empty, uses all.
- prefix : String
if specified, create a subgraph using only classes with this prefix, e.g. ENVO, PATO, GO
Return
------
nx.MultiDiGraph
A networkx MultiDiGraph object representing the filtered ontology
"""
# trigger synonym cache
self.all_synonyms()
self.all_obsoletes()
# default method - wrap get_graph
srcg = self.get_graph()
if prefix is not None:
srcg = srcg.subgraph([n for n in srcg.nodes() if n.startswith(prefix+":")])
if relations is None:
logger.info("No filtering on "+str(self))
return srcg
logger.info("Filtering {} for {}".format(self, relations))
g = nx.MultiDiGraph()
# TODO: copy full metadata
logger.info("copying nodes")
for (n,d) in srcg.nodes(data=True):
g.add_node(n, **d)
logger.info("copying edges")
num_edges = 0
for (x,y,d) in srcg.edges(data=True):
if d['pred'] in relations:
num_edges += 1
g.add_edge(x,y,**d)
logger.info("Filtered edges: {}".format(num_edges))
return g
[docs] def merge(self, ontologies):
"""
Merges specified ontology into current ontology
"""
if self.xref_graph is None:
self.xref_graph = nx.MultiGraph()
logger.info("Merging source: {} xrefs: {}".format(self, len(self.xref_graph.edges())))
for ont in ontologies:
logger.info("Merging {} into {}".format(ont, self))
g = self.get_graph()
srcg = ont.get_graph()
for n in srcg.nodes():
g.add_node(n, **srcg.nodes[n])
for (o,s,m) in srcg.edges(data=True):
g.add_edge(o,s,**m)
if ont.xref_graph is not None:
for (o,s,m) in ont.xref_graph.edges(data=True):
self.xref_graph.add_edge(o,s,**m)
if ont.all_logical_definitions is not None:
for ld in ont.all_logical_definitions:
self.add_logical_definition(ld)
if ont.all_property_chain_axioms is not None:
for pca in ont.all_property_chain_axioms:
self.add_property_chain_axiom(pca)
[docs] def subgraph(self, nodes=None):
"""
Return an induced subgraph
By default this wraps networkx subgraph,
but this may be overridden in specific implementations
"""
if nodes is None:
nodes = []
return self.get_graph().subgraph(nodes)
[docs] def subontology(self, nodes=None, minimal=False, relations=None):
"""
Return a new ontology that is an extract of this one
Arguments
---------
- nodes: list
list of node IDs to include in subontology. If None, all are used
- relations: list
list of relation IDs to include in subontology. If None, all are used
"""
g = None
if nodes is not None:
g = self.subgraph(nodes)
else:
g = self.get_graph()
if minimal:
from ontobio.slimmer import get_minimal_subgraph
g = get_minimal_subgraph(g, nodes)
ont = Ontology(graph=g, xref_graph=self.xref_graph) # TODO - add metadata
if relations is not None:
g = ont.get_filtered_graph(relations)
ont = Ontology(graph=g, xref_graph=self.xref_graph)
return ont
[docs] def create_slim_mapping(self, subset=None, subset_nodes=None, relations=None, disable_checks=False):
"""
Create a dictionary that maps between all nodes in an ontology to a subset
Arguments
---------
ont : `Ontology`
Complete ontology to be mapped. Assumed pre-filtered for relationship types
subset : str
Name of subset to map to, e.g. goslim_generic
nodes : list
If no named subset provided, subset is passed in as list of node ids
relations : list
List of relations to filter on
disable_checks: bool
Unless this is set, this will prevent a mapping being generated with non-standard relations.
The motivation here is that the ontology graph may include relations that it is inappropriate to
propagate gene products over, e.g. transports, has-part
Return
------
dict
maps all nodes in ont to one or more non-redundant nodes in subset
Raises
------
ValueError
if the subset is empty
"""
if subset is not None:
subset_nodes = self.extract_subset(subset)
logger.info("Extracting subset: {} -> {}".format(subset, subset_nodes))
if subset_nodes is None or len(subset_nodes) == 0:
raise ValueError("subset nodes is blank")
subset_nodes = set(subset_nodes)
logger.debug("SUBSET: {}".format(subset_nodes))
# Use a sub-ontology for mapping
subont = self
if relations is not None:
subont = self.subontology(relations=relations)
if not disable_checks:
for r in subont.relations_used():
if r != 'subClassOf' and r != 'BFO:0000050' and r != 'subPropertyOf':
raise ValueError("Not safe to propagate over a graph with edge type: {}".format(r))
m = {}
for n in subont.nodes():
ancs = subont.ancestors(n, reflexive=True)
ancs_in_subset = subset_nodes.intersection(ancs)
m[n] = list(subont.filter_redundant(ancs_in_subset))
return m
[docs] def filter_redundant(self, ids):
"""
Return all non-redundant ids from a list
"""
sids = set(ids)
for id in ids:
sids = sids.difference(self.ancestors(id, reflexive=False))
return sids
[docs] def subsets(self, nid, contract=True):
"""
Retrieves subset ids for a class or ontology object
"""
n = self.node(nid)
subsets = []
meta = self._meta(nid)
if 'subsets' in meta:
subsets = meta['subsets']
else:
subsets = []
if contract:
subsets = [self._contract_subset(s) for s in subsets]
return subsets
def _contract_subset(self, s):
if s.find("#") > -1:
return s.split('#')[-1]
else:
return s
def _meta(self, nid):
n = self.node(nid)
return n.get("meta", {})
[docs] def prefixes(self):
"""
list all prefixes used
"""
pset = set()
for n in self.nodes():
pfx = self.prefix(n)
if pfx is not None:
pset.add(pfx)
return list(pset)
[docs] def prefix_fragment(self, nid):
"""
Return prefix and fragment/localid for a node
"""
sep=':'
if nid.startswith('http'):
if '#' in nid:
sep='#'
else:
sep='/'
parts = nid.split(sep)
frag = parts.pop()
prefix = sep.join(parts)
return prefix, frag
[docs] def prefix(self, nid):
"""
Return prefix for a node
"""
pfx,_ = self.prefix_fragment(nid)
return pfx
[docs] def nodes(self):
"""
Return all nodes in ontology
Wraps networkx by default
"""
return self.get_graph().nodes()
[docs] def node(self, id):
"""
Return a node with a given ID. If the node with the ID exists the
Node object is returned, otherwise None is returned.
Wraps networkx by default
"""
return self.get_graph().nodes.get(id, None)
[docs] def has_node(self, id):
"""
True if id identifies a node in the ontology graph
"""
return id in self.get_graph().nodes()
[docs] def sorted_nodes(self):
"""
Returns all nodes in ontology, after topological sort
"""
return nx.topological_sort(self.get_graph())
[docs] def node_type(self, id):
"""
If stated, either CLASS, PROPERTY or INDIVIDUAL
"""
return self.node(id).get('type', None)
[docs] def relations_used(self):
"""
Return list of all relations used to connect edges
"""
g = self.get_graph()
types = set()
for (x,y,d) in g.edges(data=True):
types.add(d['pred'])
return list(types)
def neighbors(self, node, relations=None):
return self.parents(node, relations=relations) + self.children(node, relations=relations)
[docs] def child_parent_relations(self, subj, obj, graph=None):
"""
Get all relationship type ids between a subject and a parent.
Typically only one relation ID returned, but in some cases there may be more than one
Arguments
---------
subj: string
Child (subject) id
obj: string
Parent (object) id
Returns
-------
list
"""
if graph is None:
graph = self.get_graph()
preds = set()
for _,ea in graph[obj][subj].items():
preds.add(ea['pred'])
logger.debug('{}->{} = {}'.format(subj,obj,preds))
return preds
[docs] def parents(self, node, relations=None):
"""
Return all direct 'parents' of specified node.
Note that in the context of ontobio, 'parent' means any node that
is traversed in a single hop along an edge from a subject to object.
For example, if the ontology has an edge "finger part-of some hand", then
"hand" is the parent of finger.
This can sometimes be counter-intutitive, for example, if the ontology
contains has-part axioms. If the ontology has an edge
"X receptor activity has-part some X binding", then "X binding" is the 'parent'
of "X receptor activity" over a has-part edge.
Wraps networkx by default.
Arguments
---------
node: string
identifier for node in ontology
relations: list of strings
list of relation (object property) IDs used to filter
"""
g = self.get_graph()
if node in g:
parents = list(g.predecessors(node))
if relations is None:
return parents
else:
rset = set(relations)
return [p for p in parents if len(self.child_parent_relations(node, p, graph=g).intersection(rset)) > 0 ]
else:
return []
[docs] def children(self, node, relations=None):
"""
Return all direct children of specified node.
Wraps networkx by default.
Arguments
---------
node: string
identifier for node in ontology
relations: list of strings
list of relation (object property) IDs used to filter
"""
g = self.get_graph()
if node in g:
children = list(g.successors(node))
if relations is None:
return children
else:
rset = set(relations)
return [c for c in children if len(self.child_parent_relations(c, node, graph=g).intersection(rset)) > 0 ]
else:
return []
[docs] def ancestors(self, node, relations=None, reflexive=False):
"""Return all ancestors of specified node.
The default implementation is to use networkx, but some
implementations of the Ontology class may use a database or
service backed implementation, for large graphs.
Arguments
---------
node : str
identifier for node in ontology
reflexive : bool
if true, return query node in graph
relations : list
relation (object property) IDs used to filter
Returns
-------
list[str]
ancestor node IDs
"""
seen = set()
nextnodes = [node]
while len(nextnodes) > 0:
nn = nextnodes.pop()
if not nn in seen:
seen.add(nn)
nextnodes += self.parents(nn, relations=relations)
if not reflexive:
seen -= {node}
return list(seen)
[docs] def descendants(self, node, relations=None, reflexive=False):
"""
Returns all descendants of specified node.
The default implementation is to use networkx, but some
implementations of the Ontology class may use a database or
service backed implementation, for large graphs.
Arguments
---------
node : str
identifier for node in ontology
reflexive : bool
if true, return query node in graph
relations : list
relation (object property) IDs used to filter
Returns
-------
list[str]
descendant node IDs
"""
seen = set()
nextnodes = [node]
while len(nextnodes) > 0:
nn = nextnodes.pop()
if not nn in seen:
seen.add(nn)
nextnodes += self.children(nn, relations=relations)
if not reflexive:
seen -= {node}
return list(seen)
[docs] def equiv_graph(self):
"""
Returns
-------
graph
bidirectional networkx graph of all equivalency relations
"""
eg = nx.Graph()
for (u,v,d) in self.get_graph().edges(data=True):
if d['pred'] == 'equivalentTo':
eg.add_edge(u,v)
return eg
[docs] def traverse_nodes(self, qids, up=True, down=False, **args):
"""
Traverse (optionally) up and (optionally) down from an input set of nodes
Arguments
---------
qids : list[str]
list of seed node IDs to start from
up : bool
if True, include ancestors
down : bool
if True, include descendants
relations : list[str]
list of relations used to filter
Return
------
list[str]
nodes reachable from qids
"""
g = self.get_filtered_graph(**args)
nodes = set()
for id in qids:
# reflexive - always add self
nodes.add(id)
if down:
nodes.update(nx.descendants(g, id))
if up:
nodes.update(nx.ancestors(g, id))
return nodes
[docs] def get_roots(self, relations=None, prefix=None):
"""
Get all nodes that lack parents
Arguments
---------
relations : list[str]
list of relations used to filter
prefix : str
E.g. GO. Exclude nodes that lack this prefix when testing parentage
"""
g = self.get_filtered_graph(relations=relations, prefix=prefix)
# note: we also eliminate any singletons, which includes obsolete classes
roots = [n for n in g.nodes() if len(list(g.predecessors(n))) == 0 and len(list(g.successors(n))) > 0]
return roots
[docs] def get_level(self, level, relations=None, **args):
"""
Get all nodes at a particular level
Arguments
---------
relations : list[str]
list of relations used to filter
"""
g = self.get_filtered_graph(relations)
nodes = self.get_roots(relations=relations, **args)
for i in range(level):
logger.info(" ITERATING TO LEVEL: {} NODES: {}".format(i, nodes))
nodes = [c for n in nodes
for c in g.successors(n)]
logger.info(" FINAL: {}".format(nodes))
return nodes
[docs] def parent_index(self, relations=None):
"""
Returns a mapping of nodes to all direct parents
Arguments
---------
relations : list[str]
list of relations used to filter
Returns:
list
list of lists [[CLASS_1, PARENT_1,1, ..., PARENT_1,N], [CLASS_2, PARENT_2,1, PARENT_2,2, ... ] ... ]
"""
g = None
if relations is None:
g = self.get_graph()
else:
g = self.get_filtered_graph(relations)
l = []
for n in g:
l.append([n] + list(g.predecessors(n)))
return l
[docs] def text_definition(self, nid):
"""
Retrieves logical definitions for a class or relation id
Arguments
---------
nid : str
Node identifier for entity to be queried
Returns
-------
TextDefinition
"""
tdefs = []
meta = self._meta(nid)
if 'definition' in meta:
obj = meta['definition']
return TextDefinition(nid, **obj)
else:
return None
[docs] def logical_definitions(self, nid):
"""
Retrieves logical definitions for a class id
Arguments
---------
nid : str
Node identifier for entity to be queried
Returns
-------
LogicalDefinition
"""
ldefs = self.all_logical_definitions
if ldefs is not None:
#print("TESTING: {} AGAINST LD: {}".format(nid, str(ldefs)))
return [x for x in ldefs if x.class_id == nid]
else:
return []
def add_logical_definition(self, logical_def):
if self.all_logical_definitions is None:
self.all_logical_definitions = []
self.all_logical_definitions.append(logical_def)
[docs] def get_property_chain_axioms(self, nid):
"""
Retrieves property chain axioms for a class id
Arguments
---------
nid : str
Node identifier for relation to be queried
Returns
-------
PropertyChainAxiom
"""
pcas = self.all_property_chain_axioms
if pcas is not None:
return [x for x in pcas if x.predicate_id == nid]
else:
return []
def add_property_chain_axiom(self, pca):
if self.all_property_chain_axioms is None:
self.all_property_chain_axioms = []
self.all_property_chain_axioms.append(pca)
def get_node_type(self, nid):
n = self.node(nid)
if 'type' in n:
return n['type']
return None
def _get_meta_prop(self, nid, prop):
n = self.node(nid)
if 'meta' in n:
meta = n['meta']
if prop in meta:
return meta[prop]
return None
def _get_meta(self, nid):
n = self.node(nid)
if 'meta' in n:
return n['meta']
return None
def _get_basic_property_values(self, nid):
r = self._get_meta_prop(nid, 'basicPropertyValues')
if r is None:
return []
else:
return r
def _get_basic_property_value(self, nid, prop):
bpvs = self._get_basic_property_values(nid)
return [x['val'] for x in bpvs if x['pred'] == prop]
[docs] def is_obsolete(self, nid):
"""
True if node is obsolete
Arguments
---------
nid : str
Node identifier for entity to be queried
"""
dep = self._get_meta_prop(nid, 'deprecated')
return dep is not None and dep
[docs] def replaced_by(self, nid, strict=True):
"""
Returns value of 'replaced by' (IAO_0100001) property for obsolete nodes
Arguments
---------
nid : str
Node identifier for entity to be queried
strict: bool
If true, raise error if cardinality>1. If false, return list if cardinality>1
Return
------
None if no value set, otherwise returns node id (or list if multiple values, see strict setting)
"""
vs = self._get_basic_property_value(nid, 'IAO:0100001')
if len(vs) > 1:
msg = "replaced_by has multiple values: {}".format(vs)
if strict:
raise ValueError(msg)
else:
logger.error(msg)
return vs
[docs] def synonyms(self, nid, include_label=False):
"""
Retrieves synonym objects for a class
Arguments
---------
nid : str
Node identifier for entity to be queried
include_label : bool
If True, include label/names as Synonym objects
Returns
-------
list[Synonym]
:class:`Synonym` objects
"""
n = self.node(nid)
syns = []
if 'meta' in n:
meta = n['meta']
if 'synonyms' in meta:
for obj in meta['synonyms']:
syns.append(Synonym(nid, **obj))
if include_label:
syns.append(Synonym(nid, val=self.label(nid), pred='label'))
return syns
def obo_namespace(self, nid):
go_namespace = [predval for predval in
self.get_graph().nodes
.get(nid, {})
.get("meta", {})
.get("basicPropertyValues", []) if predval["pred"] == "OIO:hasOBONamespace"]
if len(go_namespace) >= 1:
return go_namespace[0]["val"]
else:
return None
[docs] def add_node(self, id, label=None, type='CLASS', meta=None):
"""
Add a new node to the ontology
"""
g = self.get_graph()
if meta is None:
meta={}
g.add_node(id, label=label, type=type, meta=meta)
[docs] def add_text_definition(self, textdef):
"""
Add a new text definition to the ontology
"""
self._add_meta_element(textdef.subject, 'definition', textdef.as_dict())
def set_obsolete(self, nid):
if nid not in self.get_graph():
self.add_node(nid)
self._add_meta_element(nid, 'deprecated', True)
def _add_meta_element(self, id, k, edict):
n = self.node(id)
if n is None:
raise ValueError('no such node {}'.format(id))
if 'meta' not in n:
n['meta'] = {}
n['meta'][k] = edict
[docs] def inline_xref_graph(self):
"""
Copy contents of xref_graph to inlined meta object for each node
"""
xg = self.xref_graph
for n in self.nodes():
if n in xg:
self._add_meta_element(n, 'xrefs', [{'val':x} for x in xg.neighbors(n)])
[docs] def add_parent(self, id, pid, relation='subClassOf'):
"""
Add a new edge to the ontology
"""
g = self.get_graph()
g.add_edge(pid, id, pred=relation)
[docs] def add_xref(self, id, xref):
"""
Adds an xref to the xref graph
"""
# note: does not update meta object
if self.xref_graph is None:
self.xref_graph = nx.MultiGraph()
self.xref_graph.add_edge(xref, id)
[docs] def add_synonym(self, syn):
"""
Adds a synonym for a node
"""
n = self.node(syn.class_id)
if 'meta' not in n:
n['meta'] = {}
meta = n['meta']
if 'synonyms' not in meta:
meta['synonyms'] = []
meta['synonyms'].append(syn.as_dict())
[docs] def add_to_subset(self, id, s):
"""
Adds a node to a subset
"""
n = self.node(id)
if 'meta' not in n:
n['meta'] = {}
meta = n['meta']
if 'subsets' not in meta:
meta['subsets'] = []
meta['subsets'].append(s)
[docs] def all_synonyms(self, include_label=False):
"""
Retrieves all synonyms
Arguments
---------
include_label : bool
If True, include label/names as Synonym objects
Returns
-------
list[Synonym]
:class:`Synonym` objects
"""
syns = []
for n in self.nodes():
syns = syns + self.synonyms(n, include_label=include_label)
return syns
[docs] def all_obsoletes(self):
"""
Returns all obsolete nodes
"""
return [n for n in self.nodes() if self.is_obsolete(n)]
[docs] def label(self, nid, id_if_null=False):
"""
Fetches label for a node
Arguments
---------
nid : str
Node identifier for entity to be queried
id_if_null : bool
If True and node has no label return id as label
Return
------
str
"""
g = self.get_graph()
if nid in g:
n = g.nodes[nid]
if 'label' in n:
return n['label']
else:
if id_if_null:
return nid
else:
return None
else:
if id_if_null:
return nid
else:
return None
[docs] def xrefs(self, nid, bidirectional=False, prefix=None):
"""
Fetches xrefs for a node
Arguments
---------
nid : str
Node identifier for entity to be queried
bidirection : bool
If True, include nodes xreffed to nid
Return
------
list[str]
"""
xrefs = []
if self.xref_graph is not None:
xg = self.xref_graph
if nid not in xg:
xrefs = []
else:
if bidirectional:
xrefs = list(xg.neighbors(nid))
else:
xrefs = [x for x in xg.neighbors(nid) if xg[nid][x][0]['source'] == nid]
if prefix is not None:
xrefs = [x for x in xrefs if self.prefix(x) == prefix]
return xrefs
[docs] def resolve_names(self, names, synonyms=False, **args):
"""
returns a list of identifiers based on an input list of labels and identifiers.
Arguments
---------
names: list
search terms. '%' treated as wildcard
synonyms: bool
if true, search on synonyms in addition to labels
is_regex : bool
if true, treats each name as a regular expression
is_partial_match : bool
if true, treats each name as a regular expression .*name.*
"""
g = self.get_graph()
r_ids = []
for n in names:
logger.debug("Searching for {} syns={}".format(n,synonyms))
if len(n.split(":")) == 2:
r_ids.append(n)
else:
matches = set([nid for nid in g.nodes() if self._is_match(self.label(nid), n, **args)])
if synonyms:
logger.debug("Searching syns for {}".format(names))
for nid in g.nodes():
for s in self.synonyms(nid):
if self._is_match(s.val, n, **args):
matches.add(nid)
r_ids += list(matches)
return r_ids
def _is_match(self, label, term, is_partial_match=False, is_regex=False, **args):
if term == '%':
# always match if client passes '%'
return True
if label is None:
label = ''
if term.find('%') > -1:
term = term.replace('%','.*')
is_regex = True
if is_regex:
return re.search(term, label) is not None
if is_partial_match:
return label.find(term) > -1
else:
return label == term
[docs] def search(self, searchterm, **args):
"""
Simple search. Returns list of IDs.
Arguments
---------
searchterm: list
search term. '%' treated as wildcard
synonyms: bool
if true, search on synonyms in addition to labels
is_regex : bool
if true, treats each name as a regular expression
is_partial_match : bool
if true, treats each name as a regular expression .*name.*
Return
------
list
match node IDs
"""
return self.resolve_names([searchterm], **args)
[docs]class LogicalDefinition():
"""
A simple OWL logical definition conforming to the pattern:
::
class_id = (genus_id_1 AND ... genus_id_n) AND (P_1 some FILLER_1) AND ... (P_m some FILLER_m)
See `obographs <https://github.com/geneontology/obographs>`_ docs for more details
"""
def __init__(self, class_id, genus_ids, restrictions):
"""
Arguments
---------
class_id : string
the class that is being defined
genus_ids : list
a list of named classes (typically length 1)
restrictions : list
a list of (PROPERTY_ID, FILLER_CLASS_ID) tuples
"""
self.class_id = class_id
self.genus_ids = genus_ids
self.restrictions = restrictions
def __str__(self):
return "{} =def {} AND {}".format(self.class_id, self.genus_ids, self.restrictions)
def __repr__(self):
return self.__str__()
class AbstractPropertyValue(object):
"""
Abstract superclass of all property-value mapping classes.
These correspond to Annotations in OWL
"""
def __str__(self):
return '{} "{}" {}'.format(self.subject, self.val, self.xrefs)
def __repr__(self):
return self.__str__()
def __cmp__(self, other):
(x,y) = (str(self),str(other))
if x > y:
return 1
elif x < y:
return -1
else:
return 0
class TextDefinition(AbstractPropertyValue):
"""
Represents a textual definition for a class or relation
"""
def __init__(self, subject, val=None, xrefs=None, ontology=None):
"""
Arguments
---------
- subject : string
id for the class or relation that is being defined
- val : string
the definition itself
- xrefs: list
Provenance or cross-references to same usage
"""
self.subject = subject
self.val = val
self.xrefs = xrefs
self.ontology = ontology
def as_dict(self):
"""
Returns TextDefinition as obograph dict
"""
return {
'val': self.val,
'xrefs': self.xrefs
}
[docs]class Synonym(AbstractPropertyValue):
"""
Represents a synonym using the OBO model
"""
predmap = dict(
label='label',
hasExactSynonym='exact',
hasBroadSynonym='broad',
hasNarrowSynonym='narrow',
hasRelatedSynonym='related')
def __init__(self, class_id, val=None, pred='hasRelatedSynonym', lextype=None, xrefs=None, ontology=None, confidence=1.0, synonymType=None):
"""
Arguments
---------
- class_id : string
the class that is being defined
- val : string
the synonym itself
- pred: string
oboInOwl predicate used to model scope. One of: has{Exact,Narrow,Related,Broad}Synonym - may also be 'label'
- lextype: string
From an open ended set of types
- xrefs: list
Provenance or cross-references to same usage
"""
pred = pred.replace("http://www.geneontology.org/formats/oboInOwl#", "")
self.class_id = class_id
self.val = val
self.pred = pred
self.lextype = lextype
self.xrefs = xrefs
self.ontology = ontology
self.confidence = confidence
self.synonymType = synonymType
def __str__(self):
return '{} "{}" {} {} {} {} {}'.format(self.class_id, self.val, self.pred, self.lextype, self.xrefs, self.confidence, self.synonymType)
def __repr__(self):
return self.__str__()
def scope(self):
if self.pred in self.predmap:
return self.predmap[self.pred].upper()
else:
return self.pred.upper()
def is_label(self):
return self.pred == 'label'
def is_abbreviation(self, v=None):
if v is not None:
self.lextype = 'abbreviation'
return self.lextype is not None and self.lextype.lower() == 'abbreviation'
def exact_or_label(self):
return self.pred == 'hasExactSynonym' or self.pred == 'label'
[docs] def as_dict(self):
"""
Returns Synonym as obograph dict
"""
# TODO: complete metadata
return {
'pred': self.pred,
'val': self.val,
'xrefs': self.xrefs
}
def __cmp__(self, other):
(x, y) = (str(self), str(other))
if x > y:
return 1
if x < y:
return -1
return 0
class PropertyChainAxiom(object):
"""
Represents a property chain axiom used to infer the existence of a property from a chain of properties.
See the OWL primer for a description of property chains: https://www.w3.org/TR/owl2-primer/#Property_Chains
"""
def __init__(self, predicate_id, chain_predicate_ids):
"""
Arguments
---------
- predicate_id : string
the object property
- chain_predicate_ids: list
ordered list of chained properties
"""
self.predicate_id = predicate_id
self.chain_predicate_ids = chain_predicate_ids
def __str__(self):
return '{} ({})'.format(self.predicate_id, " o ".join(self.chain_predicate_ids))
def as_dict(self):
"""
Returns PropertyChainAxiom as obograph dict
"""
return {
"predicateId": self.predicate_id,
"chainPredicateIds": self.chain_predicate_ids
}