source: orange-bioinformatics/obiKEGG.py @ 1324:1f8d414831c2

Revision 1324:1f8d414831c2, 41.1 KB checked in by ales_erjavec <ales.erjavec@…>, 3 years ago (diff)
  • fixed regular expression for extracting brite entry titles
Line 
1"""obiKEGG is an interface to Kyoto Encyclopedia of Genes and Genomes (http://www.genome.jp/kegg/) that allows easy access to KEGG pathway and genes data.
2
3"""
4try:
5    import Image, ImageDraw, ImageMath
6except:
7    pass
8
9import cStringIO
10import math
11import time
12import os, sys, tarfile
13import re
14
15import obiData
16import obiProb
17import orngServerFiles
18
19from cPickle import load, loads, dump
20from collections import defaultdict
21
22
23import xml.dom.minidom as minidom
24
25from functools import partial, wraps
26import obiTaxonomy
27import orngEnviron
28import urllib2
29import cPickle
30
31DEFAULT_DATABASE_PATH = orngServerFiles.localpath("KEGG")
32KEGG_FTP_PATH = "ftp://ftp.genome.jp/pub/kegg/"
33
34_join = lambda *args: os.path.join(DEFAULT_DATABASE_PATH, *args)
35
36def special_tags(domain, filename):
37    return dict([tuple(tag.split(":", 1)) for tag in orngServerFiles.info(domain, filename)["tags"] \
38                 if tag.startswith("#") and ":" in tag])
39def loads(domain, filename, version=None):
40    """ A function decorator for a function that loads data from server files.
41    Makes sure the file is downloaded and of the right version
42    """
43    def decorator(func):
44        @wraps(func)
45        def wrapper(*args, **kwargs):
46            try:
47                ## First try downloading the required server file 
48                file = filename if isinstance(filename, basestring) else filename(*args, **kwargs)
49                orngServerFiles.localpath_download(domain, file)
50                if version is not None and special_tags(domain, file).get("#version", str(version)) < str(version):
51                    orngServerFiles.update(domain, file) 
52            except Exception, ex:
53                print ex, args, kwargs
54            return func(*args, **kwargs)
55        return wrapper
56    return decorator
57           
58def deprecated(first_arg, text=None):
59    import warnings
60    def wrapper(*args, **kwargs):
61        warnings.warn_explicit(("Call to deprecated function %s. " % first_arg.__name__) + (text or ""), 
62                               category=DeprecationWarning,
63                               filename=first_arg.func_code.co_filename,
64                               lineno=first_arg.func_code.co_firstlineno + 1
65                      )
66#        warnings.warn(("Call to deprecated function %s." % first_arg.__name__) + (text or ""),
67#                               category=DeprecationWarning,;
68#                               stacklevel =  -1 #-2 if text is None else -3
69#                        )
70        return first_arg(*args, **kwargs)
71    if isinstance(first_arg, basestring): #deprecated called with a string as first argument
72        return partial(deprecated, text=first_arg)
73    else:
74        return wraps(first_arg)(wrapper)
75       
76def cached(func):
77    @wraps(func)
78    def wrapper(self, *args, **kwargs):
79        sig = args + tuple(sorted(kwargs.items()))
80        if sig in wrapper:
81            pass
82    return wrapper
83           
84def cached_property(func):
85    @property
86    @wraps(func)
87    def wrapper_func(self):
88        cache_name = "_" + func.__name__
89        if not hasattr(self, cache_name):
90            setattr(self, cache_name, func(self))
91        return getattr(self, cache_name)
92    return wrapper_func
93
94def cached_method(func, cache_name="_cached_method_cache", store=None):
95    def wrapper(self, *args, **kwargs):
96        sig = (func.__name__,) + args + tuple(sorted(kwargs.items()))
97        if not hasattr(self, cache_name):
98            setattr(self, cache_name, store() if store is not None else {})
99        if sig not in getattr(self, cache_name):
100            getattr(self, cache_name)[sig] = func(self, *args, **kwargs)
101        return getattr(self, cache_name)[sig]
102    return wrapper
103
104class pickle_cache(dict):
105    def __init__(self, filename, sync=True):
106        dict.__init__(self)
107        self.filename = filename
108        if os.path.exists(self.filename):
109            try:
110                self.update(cPickle.load(open(self.filename, "rb")))
111            except Exception, ex:
112                print self.filename, "loading failed", ex
113               
114    def __setitem__(self, key, value):
115        super(pickle_cache, self).__setitem__(key, value)
116        self.sync()
117       
118    def __delitem__(self, key):
119        super(pickle_cache, self).__delitem__(key)
120        self.sync()
121       
122    def sync(self):
123#        print "sync", self, self.filename
124        cPickle.dump(dict(self.items()), open(self.filename, "wb,", 2))
125       
126def persistent_cached_method(func):
127    return cached_method(func, cache_name="_persistent_cached_method_cache")
128
129def persistent_cached_class(cls, store=pickle_cache):
130    def cache_loader(self):
131        if not hasattr(self, "_cache_filename"):
132            setattr(self, "_cache_filename", getattr(self, "filename") + ".cache")
133#        print "creating cache", self._cache_filename
134        try:
135#            print os.stat(self.filename).st_mtime, os.stat(self._cache_filename).st_mtime
136            if os.stat(self.filename).st_mtime > os.stat(self._cache_filename).st_mtime:
137#                print "cache obsolete"
138                os.remove(self._cache_filename)
139        except Exception, ex:
140            pass
141        return store(self._cache_filename)
142    cls._persistent_cached_method_cache = cached_property(cache_loader)
143    return cls
144
145class cache_manager(object):
146    """ takes an fuction that returns a dict like object (cache)
147    """
148    def __init__(self, opener):
149        self.opener = opener
150        self.name = opener.__name__
151       
152    def __call__(self, *args, **kwargs):
153        return self.opener(self, *args, **kwargs)
154           
155    def __get__(self, instance, type_=None):
156        """ Return an cache store from instance """
157        ## TODO: check if instance is a type
158        if getattr(instance, self.name):
159            return getattr(instance, self.name)
160        return self.opener.__get__(instance, type)
161   
162    def cached_method(manager, wrapped):
163        def wrapper(self, *args, **kwargs):
164            sig = (wrapped.__name__,) + args + tuple(sorted(kwargs.items()))
165            cache = getattr(self, manager.__name__)
166            if sig not in cache:
167                cache[sig] = func(self, *args, **kwargs)
168            return cache[sig]
169
170def proxy_dict_decorator(cls, proxy_name):
171    for name in ["__len__", "__contains__", "__getitem__", "__iter__", "get", "has_key", "items", 
172                 "iteritems", "iterkeys", "itervalues", "keys", "values", "update"]:
173        def proxy(func):
174            def f(self, *args, **kwargs):
175                return func(getattr(self, proxy_name), *args, **kwargs)
176            return f
177        setattr(cls, name, proxy(getattr(dict, name)))
178    return cls
179
180def downloader(func):
181    def wrapper(*args, **kwargs):
182        import shutil
183        def download(src, dst):
184            if isinstance(dst, basestring):
185                orngServerFiles.createPathForFile(dst)
186                dst = open(dst, "wb")
187            shutil.copyfileobj(src, dst)
188        jobs = func(*args, **kwargs)
189        for src, dst in [jobs] if type(jobs) == tuple else jobs:
190            download(src, dst)
191    return wrapper
192
193from orngMisc import with_gc_disabled
194   
195           
196SLINE, MLINE, LINELIST = range(1, 4)
197
198class _field_mutators(object):
199    @classmethod
200    def deindent(cls, text, field):
201        pad = len(text) - len(text[len(field):].lstrip())
202        return "\n".join([line[pad:] for line in text.splitlines()])
203   
204    @classmethod
205    def to_list(cls, text, field):
206        return [line for line in text.splitlines() if line.strip()]
207   
208    @classmethod
209    def to_id_list(cls, text, field):
210        return [tuple(line.split(": ", 1)) for line in cls.to_list(cls.deindent(text, field), field)]
211   
212    @classmethod
213    def to_dict(cls, text, field):
214        text = cls.deindent(text, field).replace("\n ", " ")
215        lines = [t.split(": ",1) for t in text.split("\n")]
216        return dict([(key, [t.strip() for t in value.split(" ") if t]) for key, value in lines])
217   
218    @classmethod
219    def to_ids(cls, text, field):
220        lines = [line.split(" ", 2)[:2] for line in cls.deindent(text, field).split("\n") if ":" in line.split(" ", 1)[0]]
221        return [db.lower() + id for db, id in lines] 
222#        ids = reduce(lambda col, line:1, text.split("\n"), [])
223        return ids
224    default = deindent
225#    entry = classmethod(lambda cls, text, field: cls.deindent(text, field).split()[0])
226    orthology = to_id_list
227    pathway = classmethod(lambda cls, text, field: [t[1].split()[0] for t in cls.to_id_list(text, field)])
228    codon_usage = classmethod(lambda cls, text, field: text.replace(field, " " * len(field)))
229    structure = to_id_list
230    motif = to_dict
231    pathway = to_ids
232    orthology = to_ids
233   
234    @classmethod
235    def genes(cls, text, field):
236        genes = [line.split(": ", 1) for line in cls.deindent(text, field).replace("\n    ", "").splitlines()]
237        return dict([(org.lower(), [gene.split("(")[0] for gene in genes.split(" ")]) for org, genes in genes])
238   
239    @classmethod
240    def dblinks(cls, text, field):
241        links = [line.split(": ", 1) for line in cls.deindent(text, field).replace("\n   ", "").splitlines()]
242        return dict([(db.lower(), [name for name in links.split(" ")]) for db, links in links])
243   
244    dblinks = to_dict
245
246def entry_decorate(cls):
247    reserved_map = {"class": "class_"}
248    for field in cls.FIELDS:
249        mutator = getattr(_field_mutators, field.lower(), _field_mutators.default)
250        def construct(field, mutator):
251            def get(self):
252                text = self.get(field)
253                return mutator(text, field) if text else None
254            return get
255        if not hasattr(cls, field.lower()):
256            setattr(cls, reserved_map.get(field.lower(), field.lower()), property(construct(field, mutator)))
257        else:
258            import warnings
259            warnings.warn(str(cls)+ "already contains " + field.lower())
260    return cls
261
262def borg_class(cls):
263    borg__init__ = cls.__init__
264    cls._borg_instance = None
265    def __init__(self, *args, **kwargs):
266        if not cls._borg_instance:
267            borg__init__(self, *args, **kwargs)
268            cls._borg_instance = args + tuple(sorted(kwargs.items())), self
269        elif cls._borg_instance[0] == args + tuple(sorted(kwargs.items())):
270            self.__dict__ = cls._borg_instance[1].__dict__
271        else:
272            print "Warning. Atempting to create an individual instance of '%s'.\nWe are borg. You will be assimilated. Resistance is futile." % cls.__name__
273            self.__dict__ = cls._borg_instance[1].__dict__
274    cls.__init__ = __init__
275    return cls
276
277def defaultpath(func):
278#    @wraps
279    def wrapper(*args, **kwargs):
280        if "path" not in kwargs:
281            kwargs["path"] = DEFAULT_DATABASE_PATH
282        return func(*args, **kwargs)
283    return wrapper
284             
285       
286class KEGGDBEntry(object):
287    FIELDS = []
288    MULTIPLE_FIELDS = []
289    def __init__(self, entrytext, index=None):
290        self.entrytext = entrytext
291        if not self.FIELDS:
292            self.FIELDS = set([line.split()[0] for line in entrytext.split("\n") if line.strip() and not line.startswith(" ")])
293        self._index(index)
294       
295    def _indices(self, field):
296        text = self.entrytext
297        fieldlen = len(field)
298        return reduce(lambda indices,s: indices.append(text.find(field, indices[-1] + fieldlen)) or indices, range(text.count(field)), [-fieldlen])[1:]
299         
300    def _index(self, index=None):
301        if index is not None:
302            index, self.partitions = index
303            self.index = dict(zip(self.FIELDS, index))
304            return 
305        index = dict([(field, self._indices(field)) for field in self.FIELDS])
306        sorted_indices = sorted(reduce(set.union, index.values(), set())) + [-1]
307        self.partitions = sorted_indices #[(sorted_indices[i], sorted_indices[i+1]) for i in range(len(sorted_indices) - 1)]
308        self.index = dict([(key, [sorted_indices.index(ind) for ind in indices]) for key, indices in index.items()])
309       
310    def section_iter(self, field):
311        for ind in self.index[field]:
312            yield self.entrytext[self.partitions[ind]: self.partitions[ind+1]]
313       
314    def get(self, key, default=None):
315        if key not in self.FIELDS:
316            return default
317        sections = [section for section in self.section_iter(key)]
318        if key in self.MULTIPLE_FIELDS:
319            return sections
320        elif sections:
321            return sections[0]
322        else: 
323            return default
324       
325    def entry_key(self):
326        return self.entry.split()[0]
327       
328class KEGGDataBase(object):
329    Entry = KEGGDBEntry
330    VERSION = "v1.0"
331    def __init__(self, file=None):
332        self.load(file)
333       
334    @with_gc_disabled
335    def load(self, file=None):
336        if file is None:
337            file = self.FILENAME % {"path":DEFAULT_DATABASE_PATH}
338        self.filename = file
339        data = open(file, "rb").read().split("///\n")
340        file_timestamp = os.stat(self.filename).st_mtime
341#        print file_timestamp
342        build_index = False
343#        if os.path.exists(self.filename + ".index"):
344        try:
345            version, timestamp, index = cPickle.load(open(self.filename + ".index", "rb"))
346            assert(version == self.VERSION and timestamp == file_timestamp)
347        except Exception, ex:
348#            print "error", ex
349            build_index = True
350            index = [None] * len(data)
351        self.entrys = [self.Entry(entry, ind) for entry, ind in zip(data, index) if entry.strip()]
352        if build_index:
353#            self.entrys = [self.Entry(entry) for entry in data if entry.strip()]
354            index = [([e.index[field] for field in e.FIELDS], e.partitions) for e in self.entrys]
355            cPickle.dump((self.VERSION, file_timestamp, index), open(self.filename + ".index", "wb"), cPickle.HIGHEST_PROTOCOL)
356       
357        self.entry_dict = dict([(entry.entry_key(), entry) for entry in self.entrys])
358       
359proxy_dict_decorator(KEGGDataBase, "entry_dict")
360       
361class KEGGGeneEntry(KEGGDBEntry):
362    FIELDS = ["ENTRY", "NAME", "DEFINITION","ORTHOLOGY", "PATHWAY", "CLASS", "POSITION",
363              "MOTIF", "DBLINKS", "STRUCTURE", "CODON_USAGE", "AASEQ", "NTSEQ"]
364   
365    def aliases(self):
366        return [self.entry_key()] + (self.name.split(",") if self.name else []) + [link[1][0] for link in self.dblinks.items() if self.dblinks]
367   
368    @property
369    def alt_names(self):
370        return self.aliases()
371       
372entry_decorate(KEGGGeneEntry)
373   
374class KEGGGenes(KEGGDataBase):
375    Entry = KEGGGeneEntry
376    FILENAME = "genes/organisms/%(org_code)s/%(org_name)s.ent"
377   
378    @loads("KEGG", lambda self, org: "kegg_genes_%s.tar.gz" % org)
379    def load(self, org):
380        super(KEGGGenes, self).load(_join(self.filename(org)))
381        self.entry_dict = dict([(org + ":" + key, value) for key, value in self.entry_dict.items()])
382        self.org_code = org
383       
384    def gene_aliases(self):
385        aliases = {}
386        for entry in self.entrys:
387            aliases.update(dict.fromkeys(entry.aliases(), self.org_code + ":" + entry.entry_key()))
388        return aliases
389           
390    @classmethod
391    def filename(cls, org):
392        return cls.FILENAME % {"org_code":org, "org_name":KEGGGenome()[org].name.split(",")[1].strip()}
393     
394    @classmethod
395    @downloader
396    def download(cls, org):
397#        local_dir = orngServerFiles.localpath("KEGG") if local_dir is None else local_dir
398        filename = cls.filename(org)
399        return (urllib2.urlopen(KEGG_FTP_PATH + filename), 
400                _join(filename))
401   
402class KEGGGenomeEntry(KEGGDBEntry):
403    FIELDS = ["ENTRY", "NAME", "DEFINITION", "ANNOTATION", "TAXONOMY", "DATA_SOURCE",
404              "ORIGINAL_DB", "CHROMOSOME", "STATISTICS", "REFERENCE"]
405    MULTIPLE_FIELDS = ["REFERENCE"]
406   
407    def org_code(self):
408        if self.name is not None:
409            return self.name.split(",")[0]
410        else:
411            return self.entry.split()[0]
412       
413    def entry_key(self):
414        return self.org_code()
415   
416entry_decorate(KEGGGenomeEntry)
417       
418class KEGGGenome(KEGGDataBase):
419    Entry = KEGGGenomeEntry
420    VERSION = "v1.1"
421    TAXID_MAP = {"4932" : "559292"}
422    def name(self):
423        return super(KEGGGeneome, self).name()
424   
425    @loads("KEGG", "kegg_genome.tar.gz", version=VERSION)
426    def load(self, file=None):
427        filename = _join("genes", "genome")
428        super(KEGGGenome, self).load(filename)
429   
430    @classmethod
431    def common_organisms(cls):
432#        genome = KEGGGenome()
433#        id_map = {"562":"511145", "2104":"272634", "5833":"36329", "4896":"284812", "11103":None, "4754":None, "4577":None}
434#        return [genome.get(id_map.get(taxid, taxid)).entry_key() for taxid in obiTaxonomy.common_taxids() if id_map.get(taxid, taxid) is not None]
435        return ['ath', 'bta', 'cel', 'cre', 'dre', 'ddi', 'dme', 'eco', 'hsa', 'mmu', 'mpn', 'osa',
436                'pfa', 'rno', 'sce', 'spo', 'zma', 'xla']
437       
438    @classmethod
439    def essential_organisms(cls):
440        genome = KEGGGenome()
441       
442        return [genome.get(cls.TAXID_MAP.get(taxid, taxid)).entry_key() for taxid in obiTaxonomy.essential_taxids()]
443           
444    def get(self, key, *args):
445        if key in self.TAXID_MAP:
446            key = self.TAXID_MAP[key]
447        if key not in self:
448            keys = self.search(key)
449            keys = [k for k in keys if key in self[k].name or key in self[k].taxonomy]
450            key = keys.pop(0) if keys else key
451        return super(KEGGGenome, self).get(key, *args)
452   
453    def search(self, string, relevance=False):
454        if string in self.TAXID_MAP:
455            string = self.TAXID_MAP[string]
456           
457        def match(entry, string):
458            rel = 0
459            if string in entry.entrytext:
460                weight_f = [(entry.definition or "", 2), (entry.name or "", 4), (entry.taxonomy or "", 8), (entry.entry_key(), 16)]
461                rel += 1 + sum([w for text, w in weight_f if string in text]) + (16 if entry.entry_key() in self.common_organisms() else 0)
462            return rel
463        matched = sorted(zip([match(entry, string) for entry in self.entrys], self.entrys), reverse=True)
464        return [(entry.entry_key(), rel) if relevance else entry.entry_key() for rel, entry in matched if rel]
465           
466    @classmethod
467    @downloader
468    def download(cls, file=None):
469        return (urllib2.urlopen(KEGG_FTP_PATH + "/genes/genome"), 
470                _join("genes", "genome") if file is None else file)
471       
472borg_class(KEGGGenome)
473       
474class KEGGCompoundEntry(KEGGDBEntry):
475    FIELDS = ["ENTRY", "NAME", "FORMULA", "MASS", "REMARK", "REACTION", "PATHWAY",
476              "ENZYME", "DBLINKS", "ATOM", "BOND"]
477    def entry_key(self):
478        return "cpd:" + super(KEGGCompoundEntry, self).entry_key()
479   
480entry_decorate(KEGGCompoundEntry)
481   
482class KEGGCompounds(KEGGDataBase):
483    Entry = KEGGCompoundEntry
484    FILENAME = "%(path)s/ligand/compound/compound"
485   
486    @loads("KEGG", "kegg_ligand.tar.gz")
487    def load(self, file=None):
488        super(KEGGCompounds, self).load(file)
489       
490    @classmethod
491    @downloader
492    def download(cls, file=None):
493        return (urllib2.urlopen(KEGG_FTP_PATH + "/ligand/compound/compound"),
494                _join("ligand", "compound", "compound") if file is None else file)
495   
496borg_class(KEGGCompounds)
497   
498class KEGGEnzymeEntry(KEGGDBEntry):
499    FIELDS = ["ENTRY", "NAME", "CLASS", "SYSNAME", "REACTION", "ALL_REAC", "SUBSTRATE",
500              "PRODUCT", "COFACTOR", "COMMENT", "REFERENCE", "PATHWAY", "ORTHOLOGY", 
501              "GENES", "STRUCTURES", "DBLINKS"]
502    MULTIPLE_FIELDS = ["REFERENCE"]
503    def entry_key(self):
504        return "EC:" + self.entry.split()[1]
505   
506entry_decorate(KEGGEnzymeEntry)
507
508class KEGGEnzymes(KEGGDataBase):
509    Entry = KEGGEnzymeEntry
510    FILENAME = "%(path)s/ligand/enzyme/enzyme"
511
512    @loads("KEGG", "kegg_ligand.tar.gz")
513    def load(self, file=None):
514        super(KEGGEnzymes, self).load(file)
515   
516    @classmethod
517    @downloader
518    def download(cls, file=None):
519        return (urllib2.urlopen(KEGG_FTP_PATH + "ligand/enzyme/enzyme"),
520                _join("ligand", "enzyme", "enzyme"))
521   
522borg_class(KEGGEnzymes)
523
524class KEGGReactionEntry(KEGGDBEntry):
525    FIELDS = ["ENTRY", "NAME", "DEFINITION", "EQUATION", "COMMENT", "RPAIR", "PATHWAY", 
526              "ENZYME", "ORTHOLOGY"]
527    def entry_key(self):
528        return "rn:" + super(KEGGReactionEntry, self).entry_key()
529   
530entry_decorate(KEGGReactionEntry)
531
532class KEGGReactions(KEGGDataBase):
533    Entry = KEGGReactionEntry
534    FILENAME = "%(path)s/ligand/reaction/reaction"
535   
536    @loads("KEGG", "kegg_ligand.tar.gz")
537    def load(self, file=None):
538        super(KEGGReactions, self).load(file)
539       
540    @classmethod
541    @downloader
542    def download(cls, file=None):
543        return (urllib2.urlopen(KEGG_FTP_PATH + "ligand/reaction/reaction"),
544                _join("ligand", "reaction", "reaction"))
545       
546borg_class(KEGGReactions)
547
548class KEGGKOEntry(KEGGDBEntry):
549    FIELDS = ["ENTRY", "NAME", "DEFINITION", "CLASS", "DBLINKS", "GENES"]
550   
551    def entry_key(self):
552        return "ko:" + super(KEGGKOEntry, self).entry_key()
553   
554entry_decorate(KEGGKOEntry)
555   
556class KEGGKO(KEGGDataBase):
557    Entry = KEGGKOEntry
558    FILENAME = "%(path)s/genes/ko"
559   
560    @loads("KEGG", "kegg_orthology.tar.gz")
561    def load(self, file=None):
562        super(KEGGKO, self).load(file)
563       
564    @classmethod
565    @downloader
566    def download(cls, file=None):
567        return (urllib2.urlopen(KEGG_FTP_PATH + "genes/ko"),
568                _join("genes", "ko"))
569       
570borg_class(KEGGKO)
571
572class KEGGBriteEntry(object):
573    _search_re = {"ids": re.compile('(?P<ids>\[.*:.*\])'),
574                  "title": re.compile(r'(<[Bb]>)?(?P<title>\b[a-zA-Z0-9_/\s,;:.+=\-\[\]{}\(\)]+?)(?(1)</[Bb]>)$'),
575                  "links": re.compile('(?P<links><a href=".+?">.*?</a>)')}
576    def __init__(self, line, entrys=None):
577        self.entrys = [] #entrys if entrys is not None else []
578        self.line = line[1:].strip()
579        for name, re in self._search_re.items():
580            search = re.search(self.line)
581            setattr(self, name, search.group(name) if search else None)
582#        self.identifiers = self.groups.get("ids")
583#        self.title = self.groups. # TODO: parse line (html, kegg identifiers ...)
584
585    def __iter__(self):
586        return iter(self.entrys)
587
588class KEGGBrite(KEGGBriteEntry):
589    VERSION = "v1.0"
590    def __init__(self, brite_id):
591        super(KEGGBrite, self).__init__("")
592        self.load(brite_id)
593       
594    @loads("KEGG", "kegg_brite.tar.gz")
595    def load(self, brite_id):
596        file = self.filename(brite_id)
597        lines = open(file, "rb").read().split("\n!\n")[1].splitlines()
598        def collect(lines, depth, collection):
599            while lines:
600                line = lines[0]
601                if line.startswith("#"):
602                    lines.pop(0)
603                elif line.startswith(depth) and len(line.strip()) > 1:
604                    collection.append(KEGGBriteEntry(lines.pop(0))) 
605                elif line[0] > depth:
606                    collect(lines, line[0], collection[-1].entrys)
607                elif line[0] < depth:
608                    return
609                else:
610                    lines.pop(0)
611                       
612        collect([line for line in lines if not line.startswith("#") and len(line) > 1], "A", self.entrys)
613   
614    @classmethod
615    @defaultpath
616    def filename(cls, brite_id, path=DEFAULT_DATABASE_PATH):
617        if path is None:
618            path = "%(path)s"
619        if brite_id.startswith("br"):
620            return ("%(path)s/brite/br/" + brite_id + ".keg") % dict(path=path)
621        elif brite_id.startswith("ko"):
622            return ("%(path)s/brite/ko/" + brite_id + ".keg") % dict(path=path)
623        else:
624            org = brite[:-5]
625            return ("%(path)s/brite/organisms/" + org + "/" + brite_id + ".keg") % dict(path=path)
626       
627    @classmethod
628    @downloader
629    def download(cls, brite_id):
630        filename = cls.filename(brite_id, path="")
631        return (urllib2.urlopen(cls.filename(brite_id, path=KEGG_FTP_PATH.rstrip("/"))),
632                cls.filename(brite_id))
633
634class KEGGOrganism(object):
635    VERSION = "v2.0"
636    DOMAIN = "KEGG"
637    def __init__(self, org, genematcher=None):
638        self.load(org)
639        self.genematcher = genematcher
640       
641#    @loads("KEGG", lambda self, org: "kegg_genes_%s.tar.gz" % self.organism_name_search(org))
642    def load(self, org):
643        org = self.organism_name_search(org)
644        self.org_code = org
645   
646    @property
647#    @deprecated("Use org_code instead")
648    def org(self):
649        return self.org_code
650   
651    @cached_property
652    def genes(self):
653        return KEGGGenes(self.org_code)
654       
655    @cached_property
656    def gene_aliases(self):
657        return self.genes.gene_aliases()
658   
659    def pathways(self, with_ids=None):
660        return [pathway for pathway, values in KEGGPathway.list(self.org_code).items() if all([id in values for id in (with_ids or [])])]
661   
662#    @deprecated("Use KEGGOrganism.pathways instead")
663    def list_pathways(self):
664        return self.pathways()
665   
666#    @deprecated
667    def get_linked_pathways(self, pathway_id):
668        return KEGGPathway(pathway_id).genes()
669   
670    def enzymes(self, genes=None):
671        enzymes = KEGGEnzymes()
672        return [enzyme.entry_key() for enzyme in enzymes.itervalues() if enzyme.genes and self.org_code in enzyme.genes]
673   
674    def get_enriched_pathways(self, genes, reference=None, prob=obiProb.Binomial(), callback=None):
675        """Return a dictionary with enriched pathways ids as keys and (list_of_genes, p_value, num_of_reference_genes) tuples as items."""
676        allPathways = defaultdict(lambda :[[], 1.0, []])
677        import orngMisc
678        milestones = orngMisc.progressBarMilestones(len(genes), 100)
679        for i, gene in enumerate(genes):
680            pathways = self.pathways([gene])
681            for pathway in pathways:
682                allPathways[pathway][0].append(gene)
683            if callback and i in milestones:
684                callback(i*100.0/len(genes))
685        reference = set(reference if reference is not None else self.genes.keys())
686        for p_id, entry in allPathways.items():
687            entry[2].extend(reference.intersection(KEGGPathway(p_id).genes()))
688            entry[1] = prob.p_value(len(entry[0]), len(reference), len(entry[2]), len(genes))
689        return dict([(pid, (genes, p, len(ref))) for pid, (genes, p, ref) in allPathways.items()])
690   
691    def get_genes_by_enzyme(self, enzyme):
692        enzyme = KEGGEnzymes()[enzyme]
693        return enzyme.genes.get(self.org_code, []) if enzyme.genes else []
694   
695    def get_genes_by_pathway(self, pathway_id):
696        return KEGGPathway(pathway_id).genes()
697   
698    def get_enzymes_by_pathway(self, pathway_id):
699        return KEGGPathway(pathway_id).enzymes()
700   
701    def get_compounds_by_pathway(self, pathway_id):
702        return KEGGPathway(pathway_id).compounds()
703   
704    def get_pathways_by_genes(self, gene_ids):
705        gene_ids = set(gene_ids)
706        pathways = [self.genes[id].pathway for id in gene_ids if self.genes[id].pathway]
707        pathways = reduce(set.union, pathways, set())
708        return [id for id in pathways if gene_ids.issubset(KEGGPathway(id).genes())] 
709   
710    def get_pathways_by_enzymes(self, enzyme_ids):
711        enzyme_ids = set(enzyme_ids)
712        pathways = [KEGGEnzymes()[id].pathway for id in enzyme_ids]
713        pathwats = reduce(set.union, pathways, set())
714        return [id for id in pathways if enzyme_ids.issubset(KEGGPathway(id).enzymes())]
715   
716    def get_pathways_by_compounds(self, compound_ids):
717        compound_ids = set(compound_ids)
718        pathways = [KEGGCompounds()[id].pathway for id in compound_ids]
719        pathwats = reduce(set.union, pathways, set())
720        return [id for id in pathways if compound_ids.issubset(KEGGPathway(id).compounds())]
721   
722    def get_enzymes_by_compound(self, compound_id):
723        return KEGGCompound()[compound_id].enzyme
724   
725    def get_enzymes_by_gene(self, gene_id):
726        return self.genes[gene_id].enzymes
727   
728    def get_compounds_by_enzyme(self, enzyme_id):
729        return self._enzymes_to_compounds.get(enzyme_id)
730   
731    def get_unique_gene_ids(self, genes, caseSensitive=True):
732        """Return a tuple with three elements. The first is a dictionary mapping from unique gene
733        ids to gene names in genes, the second is a list of conflicting gene names and the third is a list
734        of unknown genes.
735        """
736        unique, conflicting, unknown = {}, [], []
737        for gene in genes:
738            names = self.genematcher.match(gene)
739            if len(names) == 1:
740                unique[names[0]] = gene
741            elif len(names) == 0:
742                unknown.append(gene)
743            else:
744                conflicting.append(gene)
745        return unique, conflicting, unknown
746   
747    @property
748    @cached_method
749    def _enzymes_to_compounds(self):
750        dd = {}
751        for val in KEGGCompounds().values():
752            dd.update(dict.fromkeys(val.enzymes, val.entry_key()))
753        return dd
754   
755    def _set_genematcher(self, genematcher):
756        setattr(self, "_genematcher", genematcher)
757       
758    def _get_genematcher(self):
759        if getattr(self, "_genematcher", None) == None:
760            import obiGene
761            if self.org_code == "ddi":
762                self._genematcher = obiGene.matcher([[obiGene.GMKEGG(self.org_code), obiGene.GMDicty()]])
763            else:
764                self._genematcher = obiGene.matcher([obiGene.GMKEGG(self.org_code)])
765            self._genematcher.set_targets(self.genes.keys())
766        return self._genematcher
767    genematcher = property(_get_genematcher, _set_genematcher)
768   
769    def get_genes(self):
770        return self.genes
771   
772    @classmethod
773    def organism_name_search(cls, name):
774        genome = KEGGGenome()
775        if name not in genome:
776            ids = genome.search(name)
777            if not ids:
778                import obiTaxonomy
779                ids = obiTaxonomy.search(name)
780                ids = [id for id in ids if genome.search(id)]
781            name = ids.pop(0) if ids else name
782           
783        try:
784            return KEGGGenome()[name].entry_key()
785        except KeyError:
786            raise ValueError("Organims with name='%s' not found in KEGG." % name)
787       
788    @classmethod
789    def organism_version(cls, name):
790        name = cls.organism_name_search(name)
791        orngServerFiles.localpath_download("KEGG", "kegg_genes_%s.tar.gz" % name)
792        return orngServerFiles.info("KEGG", "kegg_genes_%s.tar.gz" % name)["datetime"]
793             
794class KEGGPathway(object):
795    PNG_FILENAME = "%(path)s/pathway/organisms/%(org)s/%(org)s%(map_id)s.png"
796    KGML_FILENAME = "%(path)s/xml/kgml/metabolic/organisms/%(org)s/%(org)s%(map_id)s.xml"
797    VERSION = "v2.0"
798   
799    class entry(object):
800        def __init__(self, dom_element):
801            self.__dict__.update(dom_element.attributes.items())
802            self.graphics = ()
803            self.components = []
804            self.graphics = dict(dom_element.getElementsByTagName("graphics")[0].attributes.items())
805            self.components = [node.getAttribute("id") for node in dom_element.getElementsByTagName("component")]
806    class reaction(object):
807        def __init__(self, dom_element):
808            self.__dict__.update(dom_element.attributes.items())
809            self.substrates = [node.getAttribute("name") for node in dom_element.getElementsByTagName("substrate")]
810            self.products = [node.getAttribute("name") for node in dom_element.getElementsByTagName("product")]
811           
812    class relation(object):
813        def __init__(self, dom_element):
814            self.__dict__.update(dom_element.attributes.items())
815            self.subtypes = [node.attributes.items() for node in dom_element.getElementsByTagName("subtype")]
816       
817    def __init__(self, file):
818        if not os.path.exists(file):
819            path, org, map_id = self.split_pathway_id(file)
820#            file = self.KGML_FILENAME % dict(path=DEFAULT_DATABASE_PATH, org=org, map_id=id)
821            file = self.filename_kgml(org, map_id)
822        if not os.path.exists(file) and os.path.exists(file.replace("metabolic", "non-metabolic")):
823            file = file.replace("metabolic", "non-metabolic")
824        self.filename = file
825#        self.load(file)
826       
827    @persistent_cached_method
828    def pathway_attributes(self):
829        return dict(self.pathway_dom().attributes.items())
830   
831    @property
832    def name(self):
833        return self.pathway_attributes().get("name")
834   
835    @property
836    def org(self):
837        return self.pathway_attributes().get("org")
838   
839    @property
840    def number(self):
841        return self.pathway_attributes().get("number")
842   
843    @property   
844    def title(self):
845        return self.pathway_attributes().get("title")
846   
847    @property
848    def image(self):
849        return self.pathway_attributes().get("image")
850   
851    @property
852    def link(self):
853        return self.pathway_attributes().get("link")
854   
855    @cached_method
856    @loads("KEGG", lambda self: "kegg_pathways_%s.tar.gz" % self.filename.split("/")[-1][:-9])
857    def pathway_dom(self):
858        return minidom.parse(self.filename).getElementsByTagName("pathway")[0]
859   
860    @cached_method
861    def entrys(self):
862        return [self.entry(e) for e in self.pathway_dom().getElementsByTagName("entry")]
863   
864    @cached_method
865    def reactions(self):
866        return [self.reaction(e) for e in self.pathway_dom().getElementsByTagName("reaction")]
867   
868    @cached_method
869    def relations(self):
870        return [self.relation(e) for e in self.pathway_dom().getElementsByTagName("relation")]
871   
872    @classmethod
873    def split_pathway_id(cls, id):
874        path, id = id.split(":") if ":" in id else ("path", id)
875        org, id = id[:-5], id[-5:]
876        return path, org, id 
877       
878    def __iter__(self):
879        """ Iterate over all elements in the pathway
880        """
881        return iter(self.all_elements())
882   
883    def __contains__(self, element):
884        """ Retrurn true if element in the pathway
885        """
886        return element in self.all_elements()
887   
888    def __getitem__(self, key):
889        return
890   
891    @cached_method
892    def all_elements(self):
893        """ Return all elements
894        """
895        return reduce(list.__add__, [self.genes(), self.compounds(), self.enzmes(), self.reactions()], [])
896   
897    def _get_entrys_by_type(self, type):
898        return reduce(set.union, [entry.name.split() for entry in self.entrys() if entry.type == type], set())
899   
900    @persistent_cached_method
901    def genes(self):
902        """ Return all genes on the pathway
903        """
904        return self._get_entrys_by_type("gene")
905   
906    @persistent_cached_method
907    def compounds(self):
908        """ Return all compounds on the pathway
909        """
910        return self._get_entrys_by_type("compound")
911   
912    @persistent_cached_method
913    def enzymes(self):
914        """ Return all enzymes on the pathway
915        """
916        return self._get_entrys_by_type("enzyme")
917   
918    @persistent_cached_method
919    def orthologs(self):
920        """ Return all orthologs on the pathway
921        """
922        return self._get_entrys_by_type("ortholog")
923   
924    @persistent_cached_method
925    def maps(self):
926        """ Return all linked maps on the pathway
927        """
928        return self._get_entrys_by_type("map")
929   
930    @persistent_cached_method
931    def groups(self):
932        """ Return all groups on the pathway
933        """
934        return self._get_entrys_by_type("ortholog")
935
936    def get_image(self):
937        """ Return an image of the pathway
938        """
939        return self.filename_png(self.org, self.number) % dict(path=DEFAULT_DATABASE_PATH)
940   
941    @persistent_cached_method
942    def get_bounding_box_dict(self):
943        return dict([(element.id, element.graphics) for element in self.entrys() if element.graphics])
944   
945    @persistent_cached_method
946    def graphics(self, item):
947        return [entry.graphics for entry in self.entrys() if item in entry.name and entry.graphics]
948       
949    @classmethod
950    @partial(cached_method, cache_name="_cls_cached_method_cache_")
951    @loads("KEGG", lambda cls, org: "kegg_pathways_%s.tar.gz" % org)
952    def list(cls, org):
953        file = cls.directory_png(org) + org + ".list"
954        data = [line.split() for line in open(file, "rb").read().splitlines()]
955        return reduce(lambda dict, line: dict[line[0]].update(line[1:]) or dict, data, defaultdict(set))
956   
957    @classmethod
958    @defaultpath
959    def filename_kgml(cls, org, map_id, path=DEFAULT_DATABASE_PATH):
960        path = "%(path)s" if path is None else path
961        if org in ["map", "ec"]:
962            return "%(path)s/xml/kgml/metabolic/ec/ec%(map_id)s.xml" % dict(org=org, map_id=map_id, path=path)
963        elif org == "ko":
964            return "%(path)s/xml/kgml/metabolic/ko/ko%(map_id)s.xml" % dict(org=org, map_id=map_id, path=path)
965        else:
966            return "%(path)s/xml/kgml/metabolic/organisms/%(org)s/%(org)s%(map_id)s.xml" % dict(org=org, map_id=map_id, path=path)
967       
968    @classmethod
969    @defaultpath
970    def filename_png(cls, org, map_id, path=DEFAULT_DATABASE_PATH):
971        path = "%(path)s" if path is None else path
972        if org in ["map", "ec", "ko", "rn"]:
973            return "%(path)s/pathway/%(org)s/%(org)s%(map_id)s.png" % dict(org=org, map_id=map_id, path=path)
974        else:
975            return "%(path)s/pathway/organisms/%(org)s/%(org)s%(map_id)s.png" % dict(org=org, map_id=map_id, path=path)
976       
977    @classmethod
978    @defaultpath
979    def directory_png(cls, org, path=DEFAULT_DATABASE_PATH):
980        return cls.filename_png(org, "", path=path).rsplit("/", 1)[0] + "/" 
981   
982    @classmethod
983    @defaultpath
984    def directory_kgml(cls, org, path=DEFAULT_DATABASE_PATH):
985        return cls.filename_kgml(org, "", path=path).rsplit("/", 1)[0] + "/"
986       
987    @classmethod
988    def pathways(cls, org):
989        file = cls.directory_png(org) + org + ".list"
990        pathways = [line.split()[0] for line in open(file, "rb").read().splitlines() if line.strip()]
991        return sorted(set(pathways))
992       
993    @classmethod
994    def download(cls, pathway, target=None):
995        """ Download the pathway (xml and png files) to target directory
996        """
997        org = pathway[:3]
998        import urllib2
999        xml_data = urllib2.urlopen("ftp://ftp.genome.jp/pub/kegg/xml/kgml/metabolic/organisms/%s/%s.xml" % (org, pathway)).read()
1000        open(os.path.join(target, pathway + ".xml"), "wb").write(xml_data)
1001        png_data = urllib2.urlopen("ftp://ftp.genome.jp/pub/kegg/pathway/organisms/%s/%s.png" % (org, pathway)).read()
1002        open(os.path.join(target, pathway + ".png"), "wb").write(png_data)
1003       
1004    @classmethod
1005    @downloader
1006    def download_pathways(cls, org):
1007        png_path = cls.directory_png(org, path=None).replace("%(path)s/", "")
1008        xml_path = cls.directory_kgml(org, path=None).replace("%(path)s/", "")
1009        data = urllib2.urlopen("ftp://ftp.genome.jp/pub/kegg/" + png_path + org + ".list").read()
1010        pathways = sorted(set([line.split()[0][5:] for line in data.splitlines() if line]))
1011        from obiData import FtpDownloader
1012        ftp = FtpDownloader("ftp.genome.jp", _join(), "pub/kegg/", numOfThreads=15)
1013        ftp.massRetrieve([png_path + pathway + ".png" for pathway in pathways])
1014        if org != "map" :
1015            ftp.massRetrieve([xml_path + pathway + ".xml" for pathway in pathways])
1016            ftp.massRetrieve([xml_path.replace("metabolic", "non-metabolic") + pathway + ".xml" for pathway in pathways]) 
1017        ftp.massRetrieve([png_path + org + ".list"])
1018        return []
1019   
1020persistent_cached_class(KEGGPathway)
1021       
1022def organism_name_search(name):
1023    return KEGGOrganism.organism_name_search(name)
1024
1025def pathways(org):
1026    return KEGGPathway.list(org)
1027
1028def organisms():
1029    return KEGGOrganism.organisms()
1030
1031def to_taxid(name):
1032    genome = KEGGGenome()
1033    names = genome.search(name)
1034    if genome[names[0]].taxonomy:
1035        return re.findall(r"TAX:\s*(\d+)", genome[names[0]].taxonomy)[0]
1036    else:
1037        return None #Should this raise an error?
1038
1039def from_taxid(taxid):
1040    return KEGGGenome().search(taxid)[0]
1041   
1042def test():
1043    p = KEGGPathway("sce00010.xml")
1044    print p.genes
1045    print p.reactions
1046    print p.compounds
1047    print p.image
1048    g = KEGGGenome()
1049    org = KEGGOrganism("Homo sapiens")
1050    print list(org.genes)[:10]
1051    org.gene_aliases
1052    print org.pathways(with_ids=org.genes.keys()[:5])
1053    print org.enzymes()
1054    print org.enriched_pathways(org.genes.keys()[:10])
1055    print org.genematcher
1056
1057if __name__=="__main__":
1058    test()
1059#    org1 = KEGGOrganism("ddi")
1060#    org2 = KEGGOrganism("ddi")
1061#    org2.api = KEGGInterface()
1062#    tests = [("get_genes", ()),
1063#             ("get_genes_by_enzyme", ("ec:1.1.1.1",)),
1064#             ("get_genes_by_pathway", ("path:ddi00010",)),
1065#             ("get_pathways_by_genes", (["ddi:DDB_0191256"],)),
1066#             ("get_pathways_by_enzymes", (["ec:1.1.1.1"],)),
1067#             ("get_pathways_by_compounds", (["cpd:C00001"],)),
1068#             ("get_linked_pathways", ("path:ddi00010",)),
1069#             ("list_pathways", ()),
1070#             ("get_compounds_by_enzyme", ("ec:1.1.1.1",)),
1071#             ("get_compounds_by_pathway", ("path:ddi00010",)),
1072#             ("get_enzymes_by_compound", ("cpd:C00001",)),
1073#             ("get_enzymes_by_pathway", ("path:ddi00010",)),
1074#             ("get_enzymes_by_gene", ("ddi:DDB_0191256",))]
1075#    for name, args in tests:
1076#        s1 = set(getattr(org1, name)(*args))
1077#        s2 = set(getattr(org2, name)(*args))
1078#        if s1 and s2:
1079#            print name
1080#            print s1-s2
1081#            print s2-s1
1082#        else:
1083#            print name
1084#            print "both empty"
Note: See TracBrowser for help on using the repository browser.