source: orange-bioinformatics/obiOntology.py @ 1349:f82ea66d57ff

Revision 1349:f82ea66d57ff, 25.6 KB checked in by ales_erjavec <ales.erjavec@…>, 3 years ago (diff)

Reworked OBO file parsing.
Handle escape characters in files.
Added methods to query the ontology for parent/child, super/sub terms.

Line 
1"""\
2obiOntology
3-----------
4
5Python module for manipulating OBO Ontology files (http://www.obofoundry.org/)
6
7Example::
8
9    >>> term = OBOObject("Term", id="foo:bar", name="Foo bar")
10    >>> print term
11    [Term]
12    id: foo:bar
13    name: Foo bar
14   
15    >>> ontology = OBOOntology()
16    >>> ontology.add_object(term)
17   
18"""
19
20from itertools import chain
21from collections import defaultdict
22from StringIO import StringIO
23import itertools
24import re
25
26BUILTIN_OBO_OBJECTS = [
27"""[Typedef]
28id: is_a
29name: is_a
30range: OBO:TERM_OR_TYPE
31domain: OBO:TERM_OR_TYPE
32definition: The basic subclassing relationship [OBO:defs]"""
33,
34"""[Typedef]
35id: disjoint_from
36name: disjoint_from
37range: OBO:TERM
38domain: OBO:TERM
39definition: Indicates that two classes are disjoint [OBO:defs]"""
40,
41"""[Typedef]
42id: instance_of
43name: instance_of
44range: OBO:TERM
45domain: OBO:INSTANCE
46definition: Indicates the type of an instance [OBO:defs]"""
47,
48"""[Typedef]
49id: inverse_of
50name: inverse_of
51range: OBO:TYPE
52domain: OBO:TYPE
53definition: Indicates that one relationship type is the inverse of another [OBO:defs]"""
54,
55"""[Typedef]
56id: union_of
57name: union_of
58range: OBO:TERM
59domain: OBO:TERM
60definition: Indicates that a term is the union of several others [OBO:defs]"""
61,
62"""[Typedef]
63id: intersection_of
64name: intersection_of
65range: OBO:TERM
66domain: OBO:TERM
67definition: Indicates that a term is the intersection of several others [OBO:defs]"""
68]
69   
70def _split_and_strip(string, sep):
71    head, tail = string.split(sep, 1)
72    return head.rstrip(" "), tail.lstrip(" ")
73
74
75class OBOObject(object):
76    """ Represents a generic OBO object (e.g. Term, Typedef, Instance, ...)
77    Example::
78        >>> term = OBOObject(stanza_type="Term", id="FOO:001", name="bar")
79    """
80    def __init__(self, stanza_type="Term", **kwargs):
81        """ Init from keyword arguments.
82        Example::
83            >>> term = OBOObject(stanza_type="Term", id="FOO:001", name="bar", def_="Example definition { modifier=frob } ! Comment")
84            >>> term = OBOObject(stanza_type="Term", id="FOO:001", name="bar", def_=("Example definition", [("modifier", "frob")], "Comment"))
85            >>> term = OBOObject(stanza_type="Term", id="FOO:001", name="bar", def_=("Example definition", [("modifier", "frob")])) # without the comment
86            >>> term = OBOObject(stanza_type="Term", id="FOO:001", name="bar", def_=("Example definition",)) # without the modifiers and comment
87        """
88        self.stanza_type = stanza_type
89       
90        self.modifiers = []
91        self.comments = []
92        self.tag_values = []
93        self.values = {}
94       
95        sorted_tags = sorted(kwargs.iteritems(), key=lambda key_val: chr(1) if key_val[0] == "id" else key_val[0])
96        for tag, value in sorted_tags:
97            if isinstance(value, basestring):
98                tag, value, modifiers, comment = self.parse_tag_value(self.name_demangle(tag), value)
99            elif isinstance(value, tuple):
100                tag, value, modifiers, comment = ((self.name_demangle(tag),) + value + (None, None))[:4]
101            self.add_tag(tag, value, modifiers, comment)
102       
103        self.related = set()
104#        self.related_to = set()
105           
106    @property
107    def is_annonymous(self):
108        value = self.get_value("is_annonymous")
109        return bool(value)
110   
111    def name_mangle(self, tag):
112        """ Mangle tag name if it conflicts with python keyword
113        Example::
114            >>> term.name_mangle("def"), term.name_mangle("class")
115            ('def_', 'class_')
116        """
117        if tag in ["def", "class", "in", "not"]:
118            return tag + "_"
119        else:
120            return tag
121       
122    def name_demangle(self, tag):
123        """ Reverse of name_mangle
124        """
125        if tag in ["def_", "class_", "in_", "not_"]:
126            return tag[:-1]
127        else:
128            return tag
129       
130    def add_tag(self, tag, value, modifiers=None, comment=None):
131        """ Add `tag`, `value` pair to the object with optional modifiers and
132        comment.
133        Example::
134            >>> term = OBOObject("Term")
135            >>> term.add_tag("id", "FOO:002", comment="This is an id")
136            >>> print term
137            [Term]
138            id: FOO:002 ! This is an id
139             
140        """
141        tag = intern(tag) # a small speed and memory benefit
142        self.tag_values.append((tag, value))
143        self.modifiers.append(modifiers)
144        self.comments.append(comment)
145        self.values.setdefault(tag, []).append(value)
146       
147        #  TODO: fix multiple tags grouping
148        if hasattr(self, tag):
149            if isinstance(getattr(self, tag), list):
150                getattr(self, tag).append(value)
151            else:
152                setattr(self, tag, [getattr(self, tag)] + [value])
153        else:
154            setattr(self, self.name_mangle(tag), value)
155           
156    def update(self, other):
157        """ Update the term with tag value pairs from `other`
158        (a OBOObject instance). The tag value pairs are appended
159        to the end except for the `id` tag.
160        """ 
161        for (tag, value), modifiers, comment in zip(other.tag_values, other.modifiers, other.comments):
162            if tag != "id":
163                self.add_tag(tag, value, modifiers, comment)
164       
165    def get_value(self, tag, group=True):
166        if group:
167            pairs = [pair for pair in self.tag_values if pair[0] == tag]
168            return pairs
169        else:
170            tag = self.name_mangle(tag)
171            if tag in self.__dict__:
172                return self.__dict__[tag]
173            else:
174                raise ValueError("No value for tag: %s" % tag)
175       
176    def tag_count(self):
177        """ Retrun the number of tags in this object
178        """
179        return len(self.tag_values)
180   
181    def tags(self):
182        """ Retrun an iterator over the (tag, value) pairs.
183        """
184        for i in range(self.tag_count()):
185            yield self.tag_values[i] + (self.modifiers[i], self.comments[i])
186       
187    def format_single_tag(self, index):
188        """Return a formated string representing index-th tag pair value
189        Example::
190            >>> term = OBOObject("Term", id="FOO:001", name="bar", def_="Example definition {modifier=frob} ! Comment")
191            >>> term.format_single_tag(0)
192            'id: FOO:001'
193            >>> term.format_single_tag(1)
194            'def: Example definition { modifier=frob } ! Comment'
195        """
196        tag, value = self.tag_values[index]
197        modifiers = self.modifiers[index]
198        comment = self.comments[index]
199        res = ["%s: %s" % (tag, value)]
200        if modifiers:
201            res.append("{ %s }" % modifiers)
202        if comment:
203            res.append("! " + comment)
204        return " ".join(res)
205   
206    def format_stanza(self):
207        """ Return a string stanza representation of this object
208        """
209        stanza = ["[%s]" % self.stanza_type]
210        for i in range(self.tag_count()):
211            stanza.append(self.format_single_tag(i))
212        return "\n".join(stanza)
213           
214    @classmethod     
215    def parse_stanza(cls, stanza):
216        r""" Parse and return an OBOObject instance from a single stanza.
217        Example::
218            >>> term = OBOObject.parse_stanza("[Term]\nid: FOO:001\nname:bar")
219            >>> print term.id, term.name
220            FOO:001 bar
221           
222        """
223        lines = stanza.splitlines()
224        stanza_type = lines[0].strip("[]")
225        tag_values = []
226        for line in lines[1:]:
227            if ":" in line:
228                tag_values.append(cls.parse_tag_value(line))
229       
230        obo = OBOObject(stanza_type)
231        for i, (tag, value, modifiers, comment) in enumerate(tag_values):
232#            print tag, value, modifiers, comment
233            obo.add_tag(tag, value, modifiers, comment)
234        return obo
235   
236       
237    @classmethod
238    def parse_tag_value_1(cls, tag_value_pair, *args):
239        """ Parse and return a four-tuple containing a tag, value, a list of modifier pairs, comment.
240        If no modifiers or comments are present the corresponding entries will be None.
241       
242        Example::
243            >>> OBOObject.parse_tag_value("foo: bar {modifier=frob} ! Comment")
244            ('foo', 'bar', 'modifier=frob', 'Comment')
245            >>> OBOObject.parse_tag_value("foo: bar")
246            ('foo', 'bar', None, None)
247            >>> #  Can also pass tag, value pair already split   
248            >>> OBOObject.parse_tag_value("foo", "bar {modifier=frob} ! Comment")
249            ('foo', 'bar', 'modifier=frob', 'Comment')
250        """
251        if args and ":" not in tag_value_pair:
252            tag, rest = tag_value_pair, args[0]
253        else:
254            tag, rest = _split_and_strip(tag_value_pair, ":")
255        value, modifiers, comment = None, None, None
256       
257        if "{" in rest:
258            value, rest = _split_and_strip(rest, "{",)
259            modifiers, rest = _split_and_strip(rest, "}")
260        if "!" in rest:
261            if value is None:
262                value, comment = _split_and_strip(rest, "!")
263            else:
264                _, comment = _split_and_strip(rest, "!")
265        if value is None:
266            value = rest
267           
268        if modifiers is not None:
269            modifiers = modifiers #TODO: split modifiers in a list
270           
271        return tag, value, modifiers, comment
272   
273    _RE_TAG_VALUE = re.compile(r"^(?P<tag>.+?[^\\])\s*:\s*(?P<value>.+?)\s*(?P<modifiers>[^\\]{.+?[^\\]})?\s*(?P<comment>[^\\]!.*)?$")
274    _RE_VALUE = re.compile(r"^\s*(?P<value>.+?)\s*(?P<modifiers>[^\\]{.+?[^\\]})?\s*(?P<comment>[^\\]!.*)?$")
275   
276    @classmethod
277    def parse_tag_value(cls, tag_value_pair, arg=None):
278        """ Parse and return a four-tuple containing a tag, value, a list of modifier pairs, comment.
279        If no modifiers or comments are present the corresponding entries will be None.
280       
281        Example::
282            >>> OBOObject.parse_tag_value("foo: bar {modifier=frob} ! Comment")
283            ('foo', 'bar', 'modifier=frob', 'Comment')
284            >>> OBOObject.parse_tag_value("foo: bar")
285            ('foo', 'bar', None, None)
286            >>> #  Can also pass tag, value pair already split   
287            >>> OBOObject.parse_tag_value("foo", "bar {modifier=frob} ! Comment")
288            ('foo', 'bar', 'modifier=frob', 'Comment')
289           
290        .. warning: This function assumes comment an modifiers are prefixed
291            with a whitespace i.e. 'tag: bla! comment' will be parsed incorrectly!
292        """
293        if arg is not None: # tag_value_pair is actually a tag only
294            tag = tag_value_pair
295            value, modifiers, comment =  cls._RE_VALUE.findall(arg)[0]
296        else:
297            tag, value, modifiers, comment = cls._RE_TAG_VALUE.findall(tag_value_pair)[0]
298        none_if_empyt = lambda val: None if not val.strip() else val.strip()
299        modifiers = modifiers.strip(" {}")
300        comment = comment.lstrip(" !")
301        return (none_if_empyt(tag), none_if_empyt(value),
302                none_if_empyt(modifiers), none_if_empyt(comment))
303         
304    def related_objects(self):
305        """ Return a list of tuple pairs where the first element is relationship (typedef id)
306        is and the second object id whom the relationship applies to.
307        """
308        result = [(type_id, id) for type_id in ["is_a"] for id in self.values.get(type_id, [])] ##TODO add other defined Typedef ids
309        result = result + [tuple(r.split(None, 1)) for r in self.values.get("relationship", [])]
310        return result
311
312    def __repr__(self):
313        """ Return a string representation of the object in OBO format
314        """
315        return self.format_stanza()
316
317    def __iter__(self):
318        """ Iterates over sub terms
319        """
320        for type_id, id in self.related_objects():
321            yield (type_id, id)
322       
323       
324class Term(OBOObject):
325    def __init__(self, *args, **kwargs):
326        OBOObject.__init__(self, "Term", *args, **kwargs)
327
328class Typedef(OBOObject):
329    def __init__(self, *args, **kwargs):
330        OBOObject.__init__(self, "Typedef", *args, **kwargs)
331
332class Instance(OBOObject):
333    def __init__(self, *args, **kwargs):
334        OBOObject.__init__(self, "Instance", *args, **kwargs)
335
336
337import re
338
339class OBOParser(object):
340    r""" A simple parser for .obo files (inspired by xml.dom.pulldom)
341   
342    Example::
343        >>> from StringIO import StringIO
344        >>> file = StringIO("header_tag: header_value\n[Term]\nid: FOO { modifier=bar } ! comment\n\n")
345        >>> parser = OBOParser(file)
346        >>> for event, value in parser:
347        ...     print event, value
348        ...     
349        HEADER_TAG ['header_tag', ' header_value']
350        START_STANZA Term
351        TAG_VALUE ('id', 'FOO', 'modifier=bar', 'comment')
352        CLOSE_STANZA None
353               
354    """
355    def __init__(self, file):
356        self.file = file
357       
358    def parse(self, progress_callback=None):
359        """ Parse the file and yield parse events.
360       
361        .. TODO: List events
362        """
363        data = self.file.read()
364        header = data[: data.index("\n[")]
365        body = data[data.index("\n[") + 1:]
366        for line in header.splitlines():
367            if line.strip():
368                yield "HEADER_TAG", line.split(":", 1)
369               
370        current = None
371        #  For speed make these functions local
372        startswith = str.startswith
373        endswith = str.endswith
374        parse_tag_value = OBOObject.parse_tag_value
375       
376        for line in body.splitlines():
377#            line = line.strip()
378            if startswith(line, "[") and endswith(line, "]"):
379                yield "START_STANZA", line.strip("[]")
380                current = line
381            elif startswith(line, "!"):
382                yield "COMMENT", line[1:]
383            elif line:
384                yield "TAG_VALUE", parse_tag_value(line)
385            else: #  empty line is the end of a term
386                yield "CLOSE_STANZA", None
387                current = None
388        if current is not None:
389            yield "CLOSE_STANZA", None
390   
391    def __iter__(self):
392        """ Iterate over parse events (same as parse())
393        """
394        return self.parse() 
395       
396       
397class OBOOntology(object):
398    BUILTINS = BUILTIN_OBO_OBJECTS
399   
400    def __init__(self, file=None):
401        """ Init an ontology instance from a file like object (.obo format)
402       
403        """
404        self.objects = []
405        self.header_tags = []
406        self.id2term = {}
407        self.alt2id = {}
408        self._resolved_imports = []
409        self._invalid_cache_flag = False
410        self._related_to = {}
411       
412        # First load the built in OBO objects
413        builtins = StringIO("\n" + "\n\n".join(self.BUILTINS) + "\n")
414        self.load(builtins)
415        if file:
416            self.load(file)
417       
418    def add_object(self, object):
419        """ Add OBOObject instance to this ontology.
420        """
421        if object.id in self.id2term:
422            raise ValueError("OBOObject with id: %s already in the ontology" % object.id)
423        self.objects.append(object)
424        self.id2term[object.id] = object
425        self._invalid_cache_flag = True
426       
427    def add_header_tag(self, tag, value):
428        """ Add header tag, value pair to this ontology
429        """
430        self.header_tags.append((tag, value))
431   
432    def load(self, file, progress_callback=None):
433        """ Load terms from a file.
434        """
435        if isinstance(file, basestring):
436            file = open(file, "rb")
437        parser = OBOParser(file)
438        current = None
439        for event, value in parser.parse(progress_callback=progress_callback):
440            if event == "TAG_VALUE":
441                current.add_tag(*value)
442            elif event == "START_STANZA":
443                current = OBOObject(value)
444            elif event == "CLOSE_STANZA":
445                self.add_object(current)
446                current = None
447            elif event == "HEADER_TAG":
448                self.add_header_tag(*value)
449            elif event != "COMMENT":
450                raise Exception("Parse Error! Unknown parse event {0}".format(event))
451           
452        imports = [value for tag, value, in self.header_tags if tag == "import"]
453       
454        while imports:
455            url = imports.pop(0)
456            if uri not in self._resolved_imports:
457                imported = self.parse_file(open(url, "rb"))
458                ontology.update(imported)
459                self._resolved_imports.append(uri)
460               
461    def dump(self, file):
462        """ Dump the contents of the ontology to a .obo `file`.
463        """
464        if isinstance(file, basestring):
465            file = open(file, "wb")
466           
467        for key, value in self.header_tags:
468            file.write(key + ":" + value + "\n")
469        for object in self.objects:
470            file.write("\n")
471            file.write(object.format_stanza())
472            file.write("\n")
473   
474    def update(self, other):
475        """ Update this ontology with the terms from another.
476        """
477        for term in other:
478            if term.id in self:
479                if not term.is_annonymous:
480                    self.term(term.id).update(term)
481                else: #  Do nothing
482                    pass 
483            else:
484                self.add_object(term)
485        self._invalid_cache_flag = True
486       
487    def _cache_validate(self, force=False):
488        """ Update the relations cache if `self._invalid_cache` flag is set.
489        """
490        if self._invalid_cache_flag or force:
491            self._cache_relations()
492           
493    def _cache_relations(self):
494        """ Collect all relations from parent to a child and store it in
495        `self._related_to` member.
496       
497        """
498        related_to = defaultdict(list)
499        for obj in self.objects:
500            for rel_type, id in self.related_terms(obj):
501                term = self.term(id)
502                related_to[term].append((rel_type, obj))
503               
504        self._related_to = related_to
505        self._invalid_cache_flag = False
506       
507    def term(self, id):
508        """ Return the OBOObject associated with this id.
509        """
510        if isinstance(id, basestring):
511            if id in self.id2term:
512                return self.id2term[id]
513            elif id in self.alt2id:
514                return self.id2term[self.alt2id[id]]
515            else:
516                raise ValueError("Unknown term id: %s" % id)
517        elif isinstance(id, OBOObject):
518            return id
519       
520    def terms(self):
521        """ Return all `Term` instances in the ontology.
522        """
523        return [obj for obj in self.objects if obj.stanza_type == "Term"]
524   
525    def typedefs(self):
526        """ Return all `Typedef` instances in the ontology.
527        """
528        return [obj for obj in self.objects if obj.stanza_type == "Typedef"]
529   
530    def instances(self):
531        """ Return all `Instance` instances in the ontology.
532        """
533        return [obj for obj in self.objects if obj.stanza_type == "Instance"]
534       
535    def related_terms(self, term):
536        """ Return a list of (rel_type, term_id) tuples where rel_type is
537        relationship type (e.g. 'is_a', 'has_part', ...) and term_id is the
538        id of the term in the relationship.
539       
540        """
541        term = self.term(term) if not isinstance(term, OBOObject) else term
542        related = [(tag, value) for tag in ["is_a"] for value in term.values.get(tag, [])] #TODO: add other typedef ids
543        relationships = term.values.get("relationship", [])
544        for rel in relationships:
545            related.append(tuple(rel.split(None, 1)))
546        return related
547           
548    def to_network(self):
549        """ Return an Orange.network.Network instance constructed from
550        this ontology.
551       
552        """
553        edge_types = self.edge_types()
554        terms = self.terms()
555        import orngNetwork, orange
556       
557        network = orngNetwork.Network(len(terms), True, len(edge_types))
558        network.objects = dict([(term.id, i) for i, term in enumerate(terms)])
559       
560        edges = defaultdict(set)
561        for term in self.terms():
562            related = self.related_terms(term)
563            for relType, relTerm in related:
564                edges[(term.id, relTerm)].add(relType)
565               
566        edgeitems = edges.items()
567        for (src, dst), eTypes in edgeitems:
568            network[src, dst] = [1 if e in eTypes else 0 for e in edge_types]
569           
570        domain = orange.Domain([orange.StringVariable("id"),
571                                orange.StringVariable("name"),
572                                orange.StringVariable("def"),
573                                ], False)
574       
575        items = orange.ExampleTable(domain)
576        for term in terms:
577            ex = orange.Example(domain, [term.id, term.name, term.values.get("def", [""])[0]])
578            items.append(ex)
579       
580        relationships = set([", ".join(sorted(eTypes)) for (_, _), eTypes in edgeitems])
581        domain = orange.Domain([orange.FloatVariable("u"),
582                                orange.FloatVariable("v"),
583                                orange.EnumVariable("relationship", values=list(edge_types))
584                                ], False)
585       
586        id2index = dict([(term.id, i + 1) for i, term in enumerate(terms)])
587        links = orange.ExampleTable(domain)
588        for (src, dst), eTypes in edgeitems:
589            ex = orange.Example(domain, [id2index[src], id2index[dst], eTypes.pop()])
590            links.append(ex)
591           
592        network.items = items
593        network.links = links
594        network.optimization = None
595        return network
596   
597    def to_networkx(self):
598        """ Return a NetworkX graph of this ontology
599        """
600        raise NotImplementedError
601       
602    def edge_types(self):
603        """ Return a list of all edge types in the ontology
604        """
605        return [obj.id for obj in self.objects if obj.stanza_type == "Typedef"]
606   
607    def parent_edges(self, term):
608        """ Return a list of (rel_type, parent_term) tuples
609        """
610        term = self.term(term)
611        parents = []
612        for rel_type, parent in self.related_terms(term):
613            parents.append((rel_type, self.term(parent)))
614        return parents
615       
616    def child_edges(self, term):
617        """ Return a list of (rel_type, source_term) tuples
618        """
619        self._cache_validate()
620        term = self.term(term)
621        return self._related_to.get(term, [])
622       
623       
624    def super_terms(self, term):
625        """ Return a set of all super terms of `term` up to the most general one.
626        """
627        terms = self.parent_terms(term)
628        visited = set()
629        queue = set(terms)
630        while queue:
631            term = queue.pop()
632            visited.add(term)
633            queue.update(self.parent_terms(term) - visited)
634        return visited
635   
636    def sub_terms(self, term):
637        """ Return a set of all sub terms for `term`.
638        """
639        terms = self.child_terms(term)
640        visited = set()
641        queue = set(terms)
642        while queue:
643            term = queue.pop()
644            visited.add(term)
645            queue.update(self.child_terms(term) - visited)
646        return visited
647   
648    def child_terms(self, term):
649        """ Return a set of all child terms for this `term`.
650        """
651        self._cache_validate()
652        term = self.term(term)
653        children = []
654        for rel_type, term in self.child_edges(term):
655            children.append(term)
656        return set(children)
657       
658   
659    def parent_terms(self, term):
660        """ Return a set of all parent terms for this `term`
661        """
662        term = self.term(term)
663        parents = []
664        for rel_type, id in self.parent_edges(term): #term.related_objects():
665            parents.append(self.term(id))
666        return set(parents)
667   
668    def relations(self):
669        """ Return a list of all relations in the ontology.
670        """
671        relations = []
672        for obj in self.objects:
673            for type_id, id in  obj.related:
674                target_term = self.term(id)
675            relations.append((obj, type_id, target_term))
676        return relations
677   
678    def __len__(self):
679        return len(self.objects)
680   
681    def __iter__(self):
682        return iter(self.objects)
683   
684    def __contains__(self, obj):
685        if isinstance(obj, basestring):
686            return obj in self.id2term
687        else:
688            return obj in self.objects
689   
690    def __getitem__(self, key):
691        return self.id2term[key]
692   
693    def has_key(self, key):
694        return self.id2tem.has_key(key)
695   
696   
697def load(file):
698    """ Load an ontology from a .obo file
699    """
700    return OBOOntology(file)
701   
702   
703def foundry_ontologies():
704    """ List ontologies available from the OBOFoundry website
705    (`http://www.obofoundry.org/`_)
706    Example::
707        >>> foundry_ontologies()
708        [('Biological process', 'http://obo.cvs.sourceforge.net/*checkout*/obo/obo/ontology/genomic-proteomic/gene_ontology_edit.obo'), ...
709   
710    """
711    import urllib2, re
712    stream = urllib2.urlopen("http://www.obofoundry.org/")
713    text = stream.read()
714    pattern = r'<td class=".+?">\s*<a href=".+?">(.+?)</a>\s*</td>\s*<td class=".+?">.*?</td>\s*<td class=".+?">.*?</td>\s*?<td class=".+?">\s*<a href="(.+?obo)">.+?</a>'
715    return re.findall(pattern, text)
716   
717   
718if __name__ == "__main__":
719    import doctest
720    stanza = '''[Term]
721id: FOO:001
722name: bar
723'''
724    from StringIO import StringIO
725    seinfeld = StringIO("""
726[Typedef]
727id: parent
728
729[Typedef]
730id: child
731inverse_of: parent ! not actually used yet
732
733[Term]
734id: 001
735name: George
736
737[Term]
738id: 002
739name: Estelle
740relationship: parent 001 ! George
741
742[Term]
743id: 003
744name: Frank
745relationship: parent 001 ! George
746
747""") # TODO: fill the ontology with all characters
748    term = OBOObject.parse_stanza(stanza)
749   
750    seinfeld = OBOOntology(seinfeld)
751    print seinfeld.child_edges("001")
752   
753    doctest.testmod(extraglobs={"stanza": stanza, "term": term}, optionflags=doctest.ELLIPSIS)
754       
Note: See TracBrowser for help on using the repository browser.