source: orange-bioinformatics/_bioinformatics/obiKEGG/databases.py @ 1741:1c2feb7cd8d2

Revision 1741:1c2feb7cd8d2, 16.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

Added 'prefix' parameter for KEGG Pathway database interface.

Line 
1"""
2DBGET Database Interface
3========================
4
5"""
6from __future__ import absolute_import
7
8import re
9
10from . import entry
11from .entry import fields
12from . import api
13
14
15def iter_take(source_iter, n):
16    """
17    Return a list of the first `n` items in `source_iter`.
18    """
19    source_iter = iter(source_iter)
20    return [item for _, item in zip(range(n), source_iter)]
21
22
23def batch_iter(source_iter, n):
24    """
25    Split the `source_iter` into batches of size `n`.
26    """
27    source_iter = iter(source_iter)
28    while True:
29        batch = iter_take(source_iter, n)
30        if batch:
31            yield batch
32        else:
33            break
34
35
36def chain_iter(chains_iter):
37    for iter in chains_iter:
38        for element in iter:
39            yield element
40
41
42# TODO: DBDataBase should be able to be constructed from a flat text
43# entry file. The precache etc. should be moved in caching api, that creates
44# simple file system hierarchy where the flat database is saved (with db
45# release string), e.g.
46# genes/hsa.dbget
47# genes/hsa.release
48# genes/sce.dbget
49# path.dbget
50# module.dbget
51# ligand/compound.dbget
52
53
54class DBDataBase(object):
55    """
56    Base class for a DBGET database interface.
57
58    """
59    #: ENTRY_TYPE constructor (a :class:`~.entry.DBEntry` subclass). This
60    #: should be redefined in subclasses.
61    ENTRY_TYPE = entry.DBEntry
62
63    #: A database name/abbreviation (e.g. 'pathway'). Needs to be set in a
64    #: subclass or object instance's constructor before calling the base.
65    #: __init__
66    DB = None
67
68    def __init__(self, **kwargs):
69        if not self.DB:
70            raise TypeError("Cannot make an instance of abstract base "
71                            "class %r." % type(self).__name__)
72
73        self.api = api.CachedKeggApi()
74        self.info = self.api.info(self.DB)
75        release = self.info.release
76        self.api.set_default_release(release)
77        self._keys = []
78
79    def keys(self):
80        """
81        Return a list of database keys. These are unique KEGG identifiers
82        that can be used to query the database.
83
84        """
85        return list(self._keys)
86
87    def iterkeys(self):
88        """
89        Return an iterator over the `keys`.
90        """
91        return iter(self._keys)
92
93    def items(self):
94        """
95        Return a list of all (key, :obj:`DBDataBase.ENTRY_TYPE` instance)
96        tuples.
97
98        """
99        return list(zip(self.keys(), self.batch_get(self.keys())))
100
101    def iteritems(self):
102        """
103        Return an iterator over the `items`.
104        """
105        batch_size = 100
106        iterkeys = self.iterkeys()
107        return chain_iter(zip(batch, self.batch_get(batch))
108                          for batch in batch_iter(iterkeys, batch_size))
109
110    def values(self):
111        """
112        Return a list of all :obj:`DBDataBase.ENTRY_TYPE` instances.
113        """
114        return self.batch_get(self.keys())
115
116    def itervalues(self):
117        """
118        Return an iterator over all :obj:`DBDataBase.ENTRY_TYPE` instances.
119        """
120        batch_size = 100
121        iterkeys = self.iterkeys()
122        return chain_iter(self.batch_get(batch)
123                          for batch in batch_iter(iterkeys, batch_size))
124
125    def get(self, key, default=None):
126        """
127        Return an :obj:`DBDataBase.ENTRY_TYPE` instance for the `key`.
128        Raises :class:`KeyError` if not found.
129
130        """
131        try:
132            return self.__getitem__(key)
133        except KeyError:
134            return default
135
136    def has_key(self, key):
137        return self.__contains__(key)
138
139    def __getitem__(self, key):
140        e = self.get_entry(key)
141        if e is None:
142            raise KeyError(key)
143        else:
144            return e
145
146    def __contains__(self, key):
147        return key in set(self.keys())
148
149    def __len__(self):
150        return len(self.keys())
151
152    def __iter__(self):
153        return iter(self.keys())
154
155    def get_text(self, key):
156        """
157        Return the database entry for `key` as plain text.
158        """
159        key = self._add_db(key)
160        return self.api.get([key])
161
162    def get_entry(self, key):
163        """
164        Return the database entry for `key` as an instance of `ENTRY_TYPE`.
165        """
166        text = self.get_text(key)
167        if not text or text == "None":
168            return None
169        else:
170            return self.ENTRY_TYPE(text)
171
172    def find(self, name):
173        """
174        Find `name` using kegg `find` api.
175        """
176        res = self.api.find(self.DB, name).splitlines()
177        return [r.split(" ", 1)[0] for r in res]
178
179    def pre_cache(self, keys=None, batch_size=10, progress_callback=None):
180        """
181        Retrieve all the entries for `keys` and cache them locally for faster
182        subsequent retrieval. If `keys` is ``None`` then all entries will be
183        retrieved.
184
185        """
186        if not isinstance(self.api, api.CachedKeggApi):
187            raise TypeError("Not an instance of api.CachedKeggApi")
188
189        if batch_size > 10 or batch_size < 1:
190            raise ValueError("Invalid batch_size")
191
192        if keys is None:
193            keys = self.keys()
194
195        keys = list(keys)
196        start = 0
197        while start < len(keys):
198            batch = keys[start: start + batch_size]
199            batch = map(self._add_db, batch)
200
201            self.api.get(batch)
202
203            if progress_callback:
204                progress_callback(100.0 * start / len(keys))
205
206            start += batch_size
207
208    def batch_get(self, keys):
209        """
210        Batch retrieve all entries for keys. This can be significantly
211        faster then getting each entry separately especially if entries
212        are not yet cached.
213
214        """
215        entries = []
216        batch_size = 10
217        keys = list(keys)
218        start = 0
219        while start < len(keys):
220            batch = keys[start: start + batch_size]
221            batch = map(self._add_db, batch)
222            batch_entries = self.api.get(batch)
223            if batch_entries is not None:
224                batch_entries = batch_entries.split("///\n")
225                # Remove possible empty last line
226                batch_entries = [e for e in batch_entries if e.strip()]
227                entries.extend(map(self.ENTRY_TYPE, batch_entries))
228            start += batch_size
229
230        return entries
231
232    def _add_db(self, key):
233        """
234        Prefix the key with '%(DB)s:' string if not already prefixed.
235        """
236        if not key.startswith(self.DB + ":"):
237            return self.DB + ":" + key
238        else:
239            return key
240
241
242@entry.entry_decorate
243class GenomeEntry(entry.DBEntry):
244    """
245    Entry for a KEGG Genome database.
246    """
247    FIELDS = [
248        ("ENTRY", fields.DBEntryField),
249        ("NAME", fields.DBNameField),
250        ("DEFINITION", fields.DBDefinitionField),
251        ("ANNOTATION", fields.DBSimpleField),
252        ("TAXONOMY", fields.DBTaxonomyField),
253        ("DATA_SOURCE", fields.DBSimpleField),
254        ("ORIGINAL_DB", fields.DBSimpleField),
255        ("KEYWORDS", fields.DBSimpleField),
256        ("DISEASE", fields.DBSimpleField),
257        ("COMMENT", fields.DBSimpleField),
258        ("CHROMOSOME", fields.DBFieldWithSubsections),
259        ("PLASMID", fields.DBSimpleField),
260        ("STATISTICS", fields.DBSimpleField),
261        ("REFERENCE", fields.DBReference)
262    ]
263
264    MULTIPLE_FIELDS = ["REFERENCE"]
265
266    def __init__(self, text):
267        entry.DBEntry.__init__(self, text)
268
269    @property
270    def organism_code(self):
271        """
272        A three or four letter KEGG organism code (e.g. 'hsa', 'sce', ...)
273        """
274        return self.name.split(",", 1)[0]
275
276    @property
277    def taxid(self):
278        """
279        Organism NCBI taxonomy id.
280        """
281        return self.TAXONOMY.taxid
282
283    def org_code(self):
284        # for backwards compatibility; return the `organism_code`
285        return self.organism_code
286
287
288class Genome(DBDataBase):
289    """
290    An interface to the A KEGG GENOME database.
291    """
292    DB = "genome"
293    ENTRY_TYPE = GenomeEntry
294
295    # For obiTaxonomy.common_taxids mapping
296    TAXID_MAP = {
297        "562": "511145",   # Escherichia coli K-12 MG1655
298        "2104": "272634",  # Mycoplasma pneumoniae M129
299        "4530": "39947",   # Oryza sativa ssp. japonica cultivar Nipponbare (Japanese rice)
300        "4932": "559292",  # Saccharomyces cerevisiae S288C
301        "4896": "284812",  # Schizosaccharomyces pombe 972h-
302    }
303
304    def __init__(self):
305        DBDataBase.__init__(self)
306        self._org_list = self.api.list_organisms()
307        self._keys = [org.entry_id for org in self._org_list]
308
309    def _key_to_gn_entry_id(self, key):
310        res = self.find(key)
311        if len(res) == 0:
312            raise KeyError("Unknown key")
313        elif len(res) > 1:
314            raise ValueError("Not a unique key")
315        else:
316            return res[0]
317
318    @classmethod
319    def common_organisms(cls):
320        return ['ath', 'bta', 'cel', 'cre', 'dre', 'ddi',
321                'dme', 'eco', 'hsa', 'mmu', 'mpn', 'osa',
322                'pfa', 'rno', 'sce', 'spo', 'zma', 'xla']
323
324    @classmethod
325    def essential_organisms(cls):
326        return ['ddi', 'dme', 'hsa', 'mmu', 'sce']
327
328    def org_code_to_entry_key(self, code):
329        """
330        Map an organism code ('hsa', 'sce', ...) to the corresponding kegg
331        identifier (T + 5 digit number).
332
333        """
334        for org in self._org_list:
335            if org.org_code == code:
336                return org.entry_id
337        else:
338            raise ValueError("Unknown organism code '%s'" % code)
339
340    def search(self, string, relevance=False):
341        """
342        Search the genome database for string using ``bfind``.
343        """
344        if relevance:
345            raise NotImplementedError("relevance is no longer supported")
346
347        if string in self.TAXID_MAP:
348            string = self.TAXID_MAP[string]
349
350        res = self.api.find(self.DB, string)
351        if not res:
352            return []
353
354        res = res.splitlines()
355        res = [r.split(",", 1)[0] for r in res]
356        res = [r.split(None, 1)[1] for r in res]
357        return res
358
359
360@entry.entry_decorate
361class GeneEntry(entry.DBEntry):
362    FIELDS = [
363        ("ENTRY", fields.DBEntryField),
364        ("NAME", fields.DBNameField),
365        ("DEFINITION", fields.DBDefinitionField),
366        ("ORTHOLOGY", fields.DBSimpleField),
367        ("ORGANISM", fields.DBSimpleField),
368        ("PATHWAY", fields.DBPathway),
369        ("MODULE", fields.DBSimpleField),
370        ("DISEASE", fields.DBSimpleField),
371        ("DRUG_TARGET", fields.DBSimpleField),
372        ("CLASS", fields.DBSimpleField),
373        ("MOTIF", fields.DBSimpleField),
374        ("DBLINKS", fields.DBDBLinks),
375        ("STRUCTURE", fields.DBSimpleField),
376        ("POSITION", fields.DBSimpleField),
377        ("AASEQ", fields.DBAASeq),
378        ("NTSEQ", fields.DBNTSeq)
379    ]
380
381    def aliases(self):
382        return [self.entry_key] + \
383               (self.name.split(",") if self.name else []) + \
384               ([link[1][0] for link in self.dblinks.items()]
385                if self.dblinks else [])
386
387    @property
388    def alt_names(self):
389        """
390        For backwards compatibility.
391        """
392        return self.aliases()
393
394
395class Genes(DBDataBase):
396    """
397    Interface to the KEGG Genes database.
398
399    :param str org_code: KEGG organism code (e.g. 'hsa').
400
401    """
402    DB = None  # Needs to be set in __init__
403    ENTRY_TYPE = GeneEntry
404
405    def __init__(self, org_code):
406        # TODO: Map to org code from kegg id (T + 5 digits)
407        self.DB = org_code
408        self.org_code = org_code
409        DBDataBase.__init__(self)
410        self._keys = self.api.get_genes_by_organism(org_code)
411
412    def gene_aliases(self):
413        aliases = {}
414        for entry in self.itervalues():
415            aliases.update(
416                dict.fromkeys(entry.aliases(),
417                              self.org_code + ":" + entry.entry_key)
418            )
419
420        return aliases
421
422
423@entry.entry_decorate
424class CompoundEntry(entry.DBEntry):
425    FIELDS = [
426        ("ENTRY", fields.DBEntryField),
427        ("NAME", fields.DBNameField),
428        ("FORMULA", fields.DBSimpleField),
429        ("EXACT_MASS", fields.DBSimpleField),
430        ("MOL_WEIGHT", fields.DBSimpleField),
431        ("REMARK", fields.DBSimpleField),
432        ("COMMENT", fields.DBSimpleField),
433        ("REACTION", fields.DBSimpleField),
434        ("PATHWAY", fields.DBPathway),
435        ("ENZYME", fields.DBSimpleField),
436        ("BRITE", fields.DBSimpleField),
437        ("REFERENCE", fields.DBSimpleField),
438        ("DBLINKS", fields.DBDBLinks),
439        ("ATOM", fields.DBSimpleField),
440        ("BOND", fields.DBSimpleField)
441    ]
442
443
444class Compound(DBDataBase):
445    DB = "cpd"
446    ENTRY_TYPE = CompoundEntry
447
448    def __init__(self):
449        DBDataBase.__init__(self)
450        self._keys = [d.entry_id for d in self.api.list("cpd")]
451
452
453@entry.entry_decorate
454class ReactionEntry(entry.DBEntry):
455    FIELDS = [
456        ("ENTRY", fields.DBEntryField),
457        ("NAME", fields.DBNameField),
458        ("DEFINITION", fields.DBDefinitionField),
459        ("EQUATION", fields.DBSimpleField),
460        ("ENZYME", fields.DBSimpleField)
461    ]
462
463
464class Reaction(DBDataBase):
465    DB = "rn"
466    ENTRY_TYPE = ReactionEntry
467
468    def __init__(self):
469        DBDataBase.__init__(self)
470        self._keys = [d.entry_id for d in self.api.list("rn")]
471
472
473class Brite(DBDataBase):
474    DB = "br"
475
476
477class Disease(DBDataBase):
478    DB = "ds"
479
480
481class Drug(DBDataBase):
482    DB = "dr"
483
484
485@entry.entry_decorate
486class EnzymeEntry(entry.DBEntry):
487    FIELDS = [
488        ("ENTRY", fields.DBEntryField),
489        ("NAME", fields.DBNameField),
490        ("CLASS", fields.DBSimpleField),
491        ("SYSNAME", fields.DBSimpleField),
492        ("REACTION", fields.DBSimpleField),
493        ("ALL_REAC", fields.DBSimpleField),
494        ("SUBSTRATE", fields.DBSimpleField),
495        ("PRODUCT", fields.DBSimpleField),
496        ("COMMENT", fields.DBSimpleField),
497        ("REFERENCE", fields.DBReference),
498        ("PATHWAY", fields.DBPathway),
499        ("ORTHOLOGY", fields.DBSimpleField),
500        ("GENES", fields.DBSimpleField),
501        ("DBLINKS", fields.DBDBLinks)
502    ]
503
504    MULTIPLE_FIELDS = ["REFERENCE"]
505
506
507class Enzyme(DBDataBase):
508    DB = "ec"
509    ENTRY_TYPE = EnzymeEntry
510
511    def __init__(self):
512        DBDataBase.__init__(self)
513        self._keys = [d.entry_id for d in self.api.list("ec")]
514
515
516@entry.entry_decorate
517class OrthologyEntry(entry.DBEntry):
518    FIELDS = [
519        ("ENTRY", fields.DBEntryField),
520        ("NAME", fields.DBNameField),
521        ("CLASS", fields.DBSimpleField),
522        ("DBLINKS", fields.DBDBLinks),
523        ("GENES", fields.DBSimpleField),
524    ]
525
526
527class Orthology(DBDataBase):
528    DB = "ko"
529    ENTRY_TYPE = OrthologyEntry
530
531    def __init__(self):
532        DBDataBase.__init__(self)
533        self._keys = [d.entry_id for d in self.api.list("ko")]
534
535
536@entry.entry_decorate
537class PathwayEntry(entry.DBEntry):
538    FIELDS = [
539        ("ENTRY", fields.DBEntryField),
540        ("NAME", fields.DBNameField),
541        ("DESCRIPTION", fields.DBSimpleField),
542        ("CLASS", fields.DBSimpleField),
543        ("PATHWAY_MAP", fields.DBPathwayMapField),
544        ("MODULE", fields.DBSimpleField),
545        ("DISEASE", fields.DBSimpleField),
546        ("DRUG", fields.DBSimpleField),
547        ("DBLINKS", fields.DBDBLinks),
548        ("ORGANISM", fields.DBSimpleField),
549        ("GENE", fields.DBGeneField),
550        ("ENZYME", fields.DBEnzymeField),
551        ("COMPOUND", fields.DBCompoundField),
552        ("REFERENCE", fields.DBReference),
553        ("REL_PATHWAY", fields.DBSimpleField),
554        ("KO_PATHWAY", fields.DBSimpleField),
555    ]
556
557    MULTIPLE_FIELDS = ["REFERENCE"]
558
559    @property
560    def gene(self):
561        if hasattr(self, "GENE"):
562            genes = self.GENE._convert()
563        else:
564            return None
565
566        org = self.organism
567        org_prefix = ""
568        if org:
569            match = re.findall(r"\[GN:([a-z]+)\]", org)
570            if match:
571                org_prefix = match[0] + ":"
572        genes = [org_prefix + g for g in genes]
573        return genes
574
575
576class Pathway(DBDataBase):
577    """
578    KEGG Pathway database
579
580    :param str prefix:
581        KEGG Organism code ('hsa', ...) or 'map', 'ko', 'ec' or 'rn'
582
583    """
584    DB = "path"
585    ENTRY_TYPE = PathwayEntry
586
587    def __init__(self, prefix="map"):
588        DBDataBase.__init__(self)
589        self.prefix = prefix
590        valid = [d.org_code for d in self.api.list_organisms()] + \
591                ["map", "ko", "ec", "rn"]
592
593        if prefix not in valid:
594            raise ValueError("Invalid prefix %r" % prefix)
595
596        self._keys = [d.entry_id for d in self.api.list("pathway/" + prefix)]
Note: See TracBrowser for help on using the repository browser.