Changeset 1908:a5404b514061 in orange-bioinformatics


Ignore:
Timestamp:
11/12/13 16:27:03 (5 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

Interface cleanup, minor parsing speedup.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • orangecontrib/bio/obiOntology.py

    r1907 r1908  
    2020    >>> from StringIO import StringIO 
    2121    >>> buffer = StringIO() 
    22     >>> ontology.dump(buffer) # Save the ontology to a file like object 
     22    >>> ontology.write(buffer) # Save the ontology to a file like object 
    2323    >>> print buffer.getvalue() # Print the contents of the buffer 
    2424    created-by: ales 
     
    215215        avoid the name clash with the python's ``def`` keyword. 
    216216 
     217    .. seealso:: :class:`Term` :class:`Typedef` :class:`Instance` 
     218 
    217219    """ 
    218220    def __init__(self, stanza_type="Term", **kwargs): 
     
    221223        """ 
    222224        self.stanza_type = stanza_type 
    223  
    224         self.modifiers = [] 
    225         self.comments = [] 
    226225        self.tag_values = [] 
    227226        self.values = {} 
     
    235234            if isinstance(value, basestring): 
    236235                tag, value, modifiers, comment = \ 
    237                     self.parse_tag_value(self.name_demangle(tag), value) 
     236                    parse_tag_value(name_demangle(tag) + ": " + value) 
    238237            elif isinstance(value, tuple): 
    239238                tag, value, modifiers, comment = \ 
    240                     ((self.name_demangle(tag),) + value + (None, None))[:4] 
     239                    ((name_demangle(tag),) + value + (None, None))[:4] 
    241240            self.add_tag(tag, value, modifiers, comment) 
    242241 
     
    248247        Is this object anonymous. 
    249248        """ 
    250         value = self.get_value("is_annonymous") 
     249        value = self.get_values("is_annonymous") 
    251250        return bool(value) 
    252251 
     252    @property 
     253    def id(self): 
     254        """ 
     255        The id of this object. 
     256        """ 
     257        value = self.get_values("id") 
     258        return value[0] if value else None 
     259 
     260    @property 
     261    def name(self): 
     262        """ 
     263        Name of this object 
     264        """ 
     265        value = self.get_values("name") 
     266        return value[0] if value else None 
     267 
    253268    def name_mangle(self, tag): 
    254         """ 
    255         Mangle tag name if it conflicts with python keyword. 
    256  
    257         Example:: 
    258  
    259             >>> term.name_mangle("def"), term.name_mangle("class") 
    260             ('def_', 'class_') 
    261  
    262         """ 
    263         if keyword.iskeyword(tag): 
    264             return tag + "_" 
    265         else: 
    266             return tag 
     269        return name_mangle(tag) 
    267270 
    268271    def name_demangle(self, tag): 
    269         """ 
    270         Reverse of `name_mangle`. 
    271         """ 
    272         if tag.endswith("_") and keyword.iskeyword(tag[:-1]): 
    273             return tag[:-1] 
    274         else: 
    275             return tag 
     272        return name_demangle(tag) 
    276273 
    277274    def add_tag(self, tag, value, modifiers=None, comment=None): 
     
    290287        """ 
    291288        tag = intern(tag)  # a small speed and memory benefit 
    292         self.tag_values.append((tag, value)) 
    293         self.modifiers.append(modifiers) 
    294         self.comments.append(comment) 
     289        self.tag_values.append((tag, value, modifiers, comment)) 
    295290        self.values.setdefault(tag, []).append(value) 
    296291 
    297         #  TODO: fix multiple tags grouping 
    298         if hasattr(self, tag): 
    299             if isinstance(getattr(self, tag), list): 
    300                 getattr(self, tag).append(value) 
    301             else: 
    302                 setattr(self, tag, [getattr(self, tag)] + [value]) 
    303         else: 
    304             setattr(self, self.name_mangle(tag), value) 
     292    def add_tags(self, tag_value_iter): 
     293        for tag, value, modifiers, comment in tag_value_iter: 
     294            self.tag_values.append((tag, value, modifiers, comment)) 
     295            self.values.setdefault(tag, []).append(value) 
    305296 
    306297    def update(self, other): 
     
    311302 
    312303        """ 
    313         for (tag, value), modifiers, comment in \ 
    314                 zip(other.tag_values, other.modifiers, other.comments): 
     304        for tag, value, modifiers, comment in other.tag_values: 
    315305            if tag != "id": 
    316306                self.add_tag(tag, value, modifiers, comment) 
    317307 
    318     def get_value(self, tag, group=True): 
    319         if group: 
    320             pairs = [pair for pair in self.tag_values if pair[0] == tag] 
    321             return pairs 
    322         else: 
    323             tag = self.name_mangle(tag) 
    324             if tag in self.__dict__: 
    325                 return self.__dict__[tag] 
    326             else: 
    327                 raise ValueError("No value for tag: %s" % tag) 
     308    def get_values(self, tag): 
     309        try: 
     310            return self.values[tag] 
     311        except KeyError: 
     312            return [] 
    328313 
    329314    def tag_count(self): 
     
    335320    def tags(self): 
    336321        """ 
    337         Return an iterator over the (tag, value) pairs. 
    338         """ 
    339         for i in range(self.tag_count()): 
    340             yield self.tag_values[i] + (self.modifiers[i], self.comments[i]) 
    341  
    342     def format_single_tag(self, index): 
     322        Return an list of all (tag, value, modifiers, comment) tuples. 
     323        """ 
     324        return list(self.tag_values) 
     325 
     326    def _format_single_tag(self, index): 
    343327        """ 
    344328        Return a formated string representing index-th tag pair value 
     
    350334            ...      def_="Example definition {modifier=frob} ! Comment") 
    351335            ... 
    352             >>> term.format_single_tag(0) 
     336            >>> term._format_single_tag(0) 
    353337            'id: FOO:001' 
    354             >>> term.format_single_tag(1) 
     338            >>> term._format_single_tag(1) 
    355339            'def: Example definition { modifier=frob } ! Comment' 
    356340 
    357341        .. 
    358             Why by index, and not by tag? 
    359  
    360         """ 
    361         tag, value = self.tag_values[index] 
    362         modifiers = self.modifiers[index] 
    363         comment = self.comments[index] 
     342            Why by index, and not by tag? Multiple tags are allowed. 
     343 
     344        """ 
     345        tag, value, modifiers, comment = self.tag_values[index] 
    364346        res = ["%s: %s" % (tag, value)] 
    365347        if modifiers: 
     
    375357        stanza = ["[%s]" % self.stanza_type] 
    376358        for i in range(self.tag_count()): 
    377             stanza.append(self.format_single_tag(i)) 
     359            stanza.append(self._format_single_tag(i)) 
    378360        return "\n".join(stanza) 
    379361 
     
    392374        lines = stanza.splitlines() 
    393375        stanza_type = lines[0].strip("[]") 
    394 #        tag_values = [] 
    395 #        for line in lines[1:]: 
    396 #            if ":" in line: 
    397 #                tag_values.append(cls.parse_tag_value(line)) 
    398         tag_values = [cls.parse_tag_value(line) for line in lines[1:] 
     376 
     377        tag_values = [parse_tag_value(line) for line in lines[1:] 
    399378                      if ":" in line] 
    400379 
    401380        obo = OBOObject(stanza_type) 
    402         for tag, value, modifiers, comment in tag_values: 
    403             obo.add_tag(tag, value, modifiers, comment) 
     381        obo.add_tags(tag_values) 
    404382        return obo 
    405  
    406     @classmethod 
    407     def parse_tag_value(cls, tag_value_pair, *args): 
    408         """ 
    409         Parse and return a four-tuple containing a tag, value, a 
    410         list of modifier pairs, comment. If no modifiers or comments 
    411         are present the corresponding entries will be ``None``. 
    412  
    413         Example:: 
    414  
    415             >>> OBOObject.parse_tag_value("foo: bar {modifier=frob} ! Comment") 
    416             ('foo', 'bar', 'modifier=frob', 'Comment') 
    417             >>> OBOObject.parse_tag_value("foo: bar") 
    418             ('foo', 'bar', None, None) 
    419             >>> # Can also pass tag, value pair already split 
    420             >>> OBOObject.parse_tag_value("foo", "bar {modifier=frob} ! Comment") 
    421             ('foo', 'bar', 'modifier=frob', 'Comment') 
    422  
    423         """ 
    424         if args and ":" not in tag_value_pair: 
    425             tag, rest = tag_value_pair, args[0] 
    426         else: 
    427             tag, rest = _split_and_strip(tag_value_pair, ":") 
    428         value, modifiers, comment = None, None, None 
    429  
    430         if "{" in rest: 
    431             value, rest = _split_and_strip(rest, "{",) 
    432             modifiers, rest = _split_and_strip(rest, "}") 
    433         if "!" in rest: 
    434             if value is None: 
    435                 value, comment = _split_and_strip(rest, "!") 
    436             else: 
    437                 _, comment = _split_and_strip(rest, "!") 
    438         if value is None: 
    439             value = rest 
    440  
    441         if modifiers is not None: 
    442             modifiers = modifiers  # TODO: split modifiers in a list 
    443  
    444         return tag, value, modifiers, comment 
    445  
    446     _RE_TAG_VALUE = re.compile(r"^(?P<tag>.+?[^\\])\s*:\s*(?P<value>.+?)\s*(?P<modifiers>[^\\]{.+?[^\\]})?\s*(?P<comment>[^\\]!.*)?$") 
    447     _RE_VALUE = re.compile(r"^\s*(?P<value>.+?)\s*(?P<modifiers>[^\\]{.+?[^\\]})?\s*(?P<comment>[^\\]!.*)?$") 
    448  
    449     _RE_TAG_VALUE = re.compile( 
    450         r"^(?P<tag>.+?)\s*(?<!\\):\s*(?P<value>.+?)\s*(?P<modifiers>(?<!//){.*?(?<!//)})?\s*(?P<coment>(?<!//)!.*)?$") 
    451     _RE_VALUE = re.compile( 
    452         r"^\s*(?P<value>.+?)\s*(?P<modifiers>(?<!//){.*?(?<!//)})?\s*(?P<coment>(?<!//)!.*)?$") 
    453  
    454     @classmethod 
    455     def parse_tag_value_1(cls, tag_value_pair, arg=None): 
    456         """ 
    457         Parse and return a four-tuple containing a tag, value, a list 
    458         of modifier pairs, comment. If no modifiers or comments are 
    459         present the corresponding entries will be None. 
    460  
    461         Example:: 
    462             >>> OBOObject.parse_tag_value("foo: bar {modifier=frob} ! Comment") 
    463             ('foo', 'bar', 'modifier=frob', 'Comment') 
    464             >>> OBOObject.parse_tag_value("foo: bar") 
    465             ('foo', 'bar', None, None) 
    466             >>> #  Can also pass tag, value pair already split 
    467             >>> OBOObject.parse_tag_value("foo", "bar {modifier=frob} ! Comment") 
    468             ('foo', 'bar', 'modifier=frob', 'Comment') 
    469  
    470         .. warning: This function assumes comment an modifiers are prefixed 
    471             with a whitespace i.e. 'tag: bla! comment' will be parsed 
    472             incorrectly! 
    473  
    474         """ 
    475         if arg is not None:  # tag_value_pair is actually a tag only 
    476             tag = tag_value_pair 
    477             value, modifiers, comment = cls._RE_VALUE.findall(arg)[0] 
    478         else: 
    479             tag, value, modifiers, comment = \ 
    480                 cls._RE_TAG_VALUE.findall(tag_value_pair)[0] 
    481         none_if_empyt = lambda val: None if not val.strip() else val.strip() 
    482         modifiers = modifiers.strip(" {}") 
    483         comment = comment.lstrip(" !") 
    484         return (none_if_empyt(tag), none_if_empyt(value), 
    485                 none_if_empyt(modifiers), none_if_empyt(comment)) 
    486383 
    487384    def related_objects(self): 
     
    500397        return result 
    501398 
     399    def __str__(self): 
     400        """ 
     401        Return a string representation of the object in OBO format 
     402        """ 
     403        return self.format_stanza() 
     404 
    502405    def __repr__(self): 
    503         """ 
    504         Return a string representation of the object in OBO format 
    505         """ 
    506         return self.format_stanza() 
     406        return ("{0.__name__}(id={1.id!r}, name={1.name}, ...)" 
     407                .format(type(self), self)) 
    507408 
    508409    def __iter__(self): 
     
    577478        startswith = str.startswith 
    578479        endswith = str.endswith 
    579 #        parse_tag_value = OBOObject.parse_tag_value 
    580480        parse_tag_value_ = parse_tag_value 
    581481 
     
    603503class OBOOntology(object): 
    604504    """ 
    605     An class for representing OBO ontologies. 
     505    An class representing an OBO ontology. 
    606506 
    607507    :param file-like file: 
     
    613513 
    614514    def __init__(self, file=None): 
    615         """ 
    616         Initialize an ontology instance from a file like object (.obo format) 
    617         """ 
    618515        self.objects = [] 
    619516        self.header_tags = [] 
     
    662559        parser = OBOParser(file) 
    663560        current = None 
     561        tag_values = [] 
    664562        for event, value in parser.parse(progress_callback=progress_callback): 
    665563            if event == "TAG_VALUE": 
    666                 current.add_tag(*value) 
     564                tag_values.append(value) 
    667565            elif event == "START_STANZA": 
    668566                current = OBOObject(value) 
    669567            elif event == "CLOSE_STANZA": 
     568                current.add_tags(tag_values) 
    670569                self.add_object(current) 
    671570                current = None 
     571                tag_values = [] 
    672572            elif event == "HEADER_TAG": 
    673573                self.add_header_tag(*value) 
     
    690590 
    691591    def dump(self, file): 
    692         """ 
    693         Dump the contents of the ontology to a `file` in .obo format. 
     592        # deprecated use write 
     593        self.write(file) 
     594 
     595    def write(self, stream): 
     596        """ 
     597        Write the contents of the ontology to a `file` in .obo format. 
    694598 
    695599        :param file-like file: 
     
    697601 
    698602        """ 
    699         if isinstance(file, basestring): 
    700             file = open(file, "wb") 
     603        if isinstance(stream, basestring): 
     604            stream = open(file, "wb") 
    701605 
    702606        for key, value in self.header_tags: 
    703             file.write(key + ": " + value + "\n") 
     607            stream.write(key + ": " + value + "\n") 
    704608 
    705609        # Skip the builtins 
    706         for object in self.objects[len(self.BUILTINS):]: 
    707             file.write("\n") 
    708             file.write(object.format_stanza()) 
    709             file.write("\n") 
     610        for obj in self.objects[len(self.BUILTINS):]: 
     611            stream.write("\n") 
     612            stream.write(obj.format_stanza()) 
     613            stream.write("\n") 
    710614 
    711615    def update(self, other): 
     
    790694        return [obj for obj in self.objects if obj.stanza_type == "Instance"] 
    791695 
     696    def root_terms(self): 
     697        """ 
     698        Return all root terms (terms without any parents). 
     699        """ 
     700        return [term for term in self.terms() if not self.parent_terms(term)] 
     701 
    792702    def related_terms(self, term): 
    793703        """ 
     
    889799 
    890800    def __len__(self): 
     801        """ 
     802        Return the number of all objects in the ontology. 
     803        """ 
    891804        return len(self.objects) 
    892805 
    893806    def __iter__(self): 
     807        """ 
     808        Return an iterator over all objects in the ontology. 
     809        """ 
    894810        return iter(self.objects) 
    895811 
    896     def __contains__(self, obj): 
    897         if isinstance(obj, basestring): 
    898             return obj in self.id2term 
    899         else: 
    900             return obj in self.objects 
    901  
    902     def __getitem__(self, key): 
    903         return self.id2term[key] 
    904  
    905     def has_key(self, key): 
    906         return key in self.id2term 
     812    def __contains__(self, oboid): 
     813        return oboid in self.id2term 
     814 
     815    def __getitem__(self, oboid): 
     816        """ 
     817        Get the object by it's id `oboid` 
     818        """ 
     819        return self.id2term[oboid] 
    907820 
    908821    def traverse_bf(self, term): 
     
    1040953 
    1041954        return graph 
     955 
     956 
     957def name_mangle(tag): 
     958    """ 
     959    Mangle tag name if it conflicts with python keyword. 
     960 
     961    >>> term.name_mangle("def"), term.name_mangle("class") 
     962    ('def_', 'class_') 
     963 
     964    """ 
     965    if keyword.iskeyword(tag): 
     966        return tag + "_" 
     967    else: 
     968        return tag 
     969 
     970 
     971def name_demangle(tag): 
     972    """ 
     973    Reverse of `name_mangle`. 
     974    """ 
     975    if tag.endswith("_") and keyword.iskeyword(tag[:-1]): 
     976        return tag[:-1] 
     977    else: 
     978        return tag 
    1042979 
    1043980 
Note: See TracChangeset for help on using the changeset viewer.