source: orange-bioinformatics/_bioinformatics/obiKEGG/databases.py @ 1734:91d14dd2cf0e

Revision 1734:91d14dd2cf0e, 15.3 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

obiKEGG code style fixes.

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