source: orange-bioinformatics/orangecontrib/bio/obiBioMart.py @ 1873:0810c5708cc5

Revision 1873:0810c5708cc5, 38.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 7 months ago (diff)

Moved '_bioinformatics' into orangecontrib namespace.

Line 
1""" A python module for accessing BioMart MartService
2
3Example::
4    >>> connection = BioMartConnection("http://www.biomart.org/biomart/martservice")
5    >>> reg = BioMartRegistry(connection)
6    >>> for mart in reg.marts():
7    ...    print mart.name
8    ...
9    ensembl...
10    >>> dataset = BioMartDataset(mart="ensembl", internalName="hsapiens_gene_ensembl", virtualSchema="default", connection=connection)
11    >>> for attr in dataset.attributes()[:10]:
12    ...    print attr.name
13    ...
14    Ensembl Gene ID...
15    >>> data = dataset.get_data(attributes=["ensembl_gene_id", "ensembl_peptide_id"], filters=[("chromosome_name", "1")])
16   
17    >>> query = BioMartQuery(reg.connection, virtualSchema="default")
18    >>> query.set_dataset("hsapiens_gene_ensembl")
19    >>> query.add_attribute("ensembl_gene_id")
20    >>> query.add_attribute("ensembl_peptide_id")
21    >>> query.add_filter("chromosome_name", "1")
22    >>> count = query.get_count()
23"""
24
25import sys, os
26import urllib2
27import traceback
28import posixpath
29import xml.dom.minidom as minidom
30import shelve
31import itertools
32
33import warnings
34
35from functools import partial
36
37from Orange.orng import orngEnviron
38
39class BioMartError(Exception):
40    pass
41
42class BioMartServerError(BioMartError):
43    pass
44
45class DatabaseNameConflictError(BioMartError):
46    pass
47
48class BioMartQueryError(BioMartError):
49    pass
50
51def _init__slots_from_line(self, line):
52    for attr, val in zip(self.__slots__, line.split("\t")):
53        setattr(self, attr, val)
54       
55class Attribute(object):
56    """ A class representing an attribute in a BioMart dataset (returned by BioMartDataset.attributes())
57    Attributes:
58        - *internalName*    - Internal attribute name
59        - *name*    - Human readable name of the attribute
60        - *description*    - Human readable description of the attribute
61    """
62    __slots__ = ["internalName", "name", "description", "_4", "format", "table", "_7"]
63    __init__ = _init__slots_from_line
64   
65    def __repr__(self):
66        return "Attribute('%s')" % "\t".join(getattr(self, name, "") for name in self.__slots__)
67     
68           
69class Filter(object):
70    """ A class representing a filter for a BioMart dataset (returned by BioMartDataset.filters())
71    Attributes:
72        - *internalName* - Internal filter name
73        - *name*    - Filter name
74        - *values*    - Lists possible filter values
75        - *description*    - Filter description
76    """
77    __slots__ =  ["internalName", "name", "values", "description", "type", "qualifiers", "_6", "attribute_table"]
78    __init__ = _init__slots_from_line
79   
80    def __repr__(self):
81        return "Filter('%s')" % "\t".join(getattr(self, name, "") for name in self.__slots__)
82
83
84class XMLNode(object):
85    def __init__(self, tag, attributes, children=[], data=""):
86        self.tag = tag
87        self.attributes = attributes
88        self.children = children if children else []
89        self.data = data if data else ""
90       
91    @classmethod   
92    def fromDOMNode(cls, node):
93        return XMLNode(node.tagName, dict(node.attributes.items()))
94       
95    def _match(self, tag=None, **kwargs):
96        match = True
97        if tag is not None and self.tag != tag:
98            match = False
99       
100        return match and all(self.attributes.get(key, None) == value for key, value in kwargs.iteritems())
101                   
102    def elements(self, tag=None, **kwargs):
103        if self._match(tag, **kwargs):
104            yield self
105           
106        for child in self.children:
107            for el in child.elements(tag, **kwargs):
108                yield el
109               
110    def elements_top(self, tag=None, **kwargs):
111        """ Return only the top elements (do not return matching children
112        of an already matching element)."
113        """
114        if self._match(tag, **kwargs):
115            yield self
116        else:
117            for child in self.children:
118                for el in child.elements_top(tag, **kwargs):
119                    yield el
120                   
121    def subelements(self, *args, **kwargs):
122        return itertools.chain(*[c.elements(*args, **kwargs) for c in self.children])
123   
124    def subelements_top(self, *args, **kwargs):
125        return itertools.chain(*[c.elements_top(*args, **kwargs) for c in self.children])
126               
127    def __iter__(self):
128        """ Iterate over all sub elements.
129        """
130        return itertools.chain(*[iter(c) for c in self.children])
131               
132    def __repr__(self):
133        return 'XMLNode("%s", %s)' % (self.tag, repr(self.attributes))
134               
135def parseXML(stream, parser=None):
136    import xml.dom.pulldom as pulldom
137    if isinstance(stream, basestring):
138        events = pulldom.parseString(stream, parser)
139    else:
140        events = pulldom.parse(stream, parser)
141   
142    document = None
143    chain = []
144    for event, node in events:
145        if event == "START_DOCUMENT":
146            chain.append(XMLNode("DOCUMENT", {}))
147       
148        elif event == "START_ELEMENT":
149           
150            node = XMLNode.fromDOMNode(node)
151            if chain:
152                chain[-1].children.append(node)
153            chain.append(node)
154           
155        elif event == "END_ELEMENT":
156            chain.pop(-1)
157           
158        elif event == "CHARACTERS":
159            chain[-1].data += node.data
160           
161        elif event == "END_DOCUMENT":
162            document = chain.pop(-1)
163    return document or chain[0]
164   
165
166def de_tab(text, sep="\t"):
167    return [line.split(sep) for line in text.splitlines() if line.strip()]
168
169
170def cached(func):
171    from functools import wraps
172    @wraps(func)
173    def f(self):
174        if getattr(self, "_cache_" + func.__name__, None) is None:
175            setattr(self, "_cache_" + func.__name__, func(self))
176        return getattr(self, "_cache_" + func.__name__)
177    return f
178
179   
180def drop_iter(generator):
181    """ Drop all elements from the iterator that raise an exception 
182    """
183    def _iter(generator):
184        while True:
185            try:
186                yield generator.next()
187            except StopIteration:
188                raise StopIteration
189            except Exception, ex:
190                warnings.warn("An error occured during iteration:\n%s" % str(ex), UserWarning, stacklevel=2)
191    return list(_iter(generator))
192
193   
194DEFAULT_ADDRESS = "http://www.biomart.org/biomart/martservice"
195
196try:
197    DEFAULT_DATA_CACHE = shelve.open(os.path.join(orngEnviron.bufferDir, "BioMartDataCache.pck"))
198except Exception, ex:
199    warnings.warn("Could not open BioMart data cache! %s" % str(ex))
200    DEFAULT_DATA_CACHE = {}
201   
202try:
203    DEFAULT_META_CACHE = shelve.open(os.path.join(orngEnviron.bufferDir, "BioMartMetaCache.pck"))
204except Exception, ex:
205    warnings.warn("Could not open BioMart meta cache! %s" % str(ex))
206    DEFAULT_META_CACHE = {}
207
208
209def checkBioMartServerError(response):
210    if response.strip().startswith("Mart name conflict"):
211        raise DatabaseNameConflictError(response)
212    elif response.strip().startswith("Problem retrieving datasets"):
213        raise BioMartError(response)
214    elif response.startswith("non-BioMart die():"):
215        raise BioMartServerError(response)
216
217
218class BioMartConnection(object):
219    """ A connection to a BioMart martservice server.
220   
221    Example::
222        >>> connection = BioMartConnection("http://www.biomart.org/biomart/martservice")
223        >>> response = connection.registry()
224        >>> response = connection.datasets(mart="ensembl")
225    """
226    FOLLOW_REDIRECTS = False
227   
228   
229    def __init__(self, address=None, cache=None, metaCache=None, dataCache=None):
230        metaCache = cache if metaCache is None else metaCache
231        dataCache = cache if dataCache is None else dataCache
232       
233        self.address = address if address is not None else DEFAULT_ADDRESS
234#        self.cache = cache if cache is not None else DEFAULT_CACHE
235        self.metaCache = metaCache if metaCache is not None else DEFAULT_META_CACHE
236        self.dataCache = dataCache if dataCache is not None else DEFAULT_DATA_CACHE
237        self.errorCache = {}
238   
239    def _get_cache_for_args(self, kwargs):
240        if "type" in kwargs:
241            return self.metaCache
242        elif "query" in kwargs:
243            return self.dataCache
244       
245    def request_url(self, **kwargs):
246        order = ["type", "dataset", "mart", "virtualSchema", "query"]
247        items = sorted(kwargs.iteritems(), key=lambda item: order.index(item[0]) if item[0] in order else 10)
248        url = self.address + "?" + "&".join("%s=%s" % item for item in items if item[0] != "POST")
249        return url.replace(" ", "%20")
250   
251    def request(self, **kwargs):
252        url = self.request_url(**kwargs)
253       
254        cache = self._get_cache_for_args(kwargs)
255       
256        if url in self.errorCache:
257            raise self.errorCache[url]
258       
259        if str(url) not in cache:
260            try:
261                response = urllib2.urlopen(url).read()
262                checkBioMartServerError(response)
263            except Exception, ex:
264                self.errorCache[url] = ex
265                raise ex
266           
267            cache[str(url)] = response
268            if hasattr(cache, "sync"):
269                cache.sync()
270               
271        from StringIO import StringIO
272        return StringIO(cache[str(url)])
273   
274    def registry(self, **kwargs):
275        return self.request(type="registry")
276   
277    def datasets(self, mart="ensembl", **kwargs):
278        return self.request(type="datasets", mart=mart, **kwargs)
279   
280    def attributes(self, dataset="oanatinus_gene_ensembl", **kwargs):
281        return self.request(type="attributes", dataset=dataset, **kwargs)
282   
283    def filters(self, dataset="oanatinus_gene_ensembl", **kwargs):
284        return self.request(type="filters", dataset=dataset, **kwargs)
285   
286    def configuration(self, dataset="oanatinus_gene_ensembl", **kwargs):
287        return self.request(type="configuration", dataset=dataset, **kwargs)
288   
289    def clearCache(self):
290        self.dataCache.clear()
291        self.metaCache.clear()
292        self.errorCache.clear()   
293       
294       
295class BioMartRegistry(object):
296    """ A class representing a BioMart registry
297    Arguments:
298        - stream: A file like object with xml registry or a BioMartConnection instance
299   
300    Example::
301    >>> registry = BioMartRegistry(connection)
302    >>> for schema in registry.virtual_schemas():
303    ...    print schema.name
304    ...
305    default
306    """
307    def __init__(self, stream):
308        self.connection = stream if isinstance(stream, BioMartConnection) else None
309        if self.connection:
310            if hasattr(self.connection, "_registry"):
311                self.__dict__ = self.connection._registry.__dict__
312                return
313            else:
314                stream = self.connection.registry()
315                self.connection._registry = self
316        else:
317            stream = open(stream, "rb") if isinstance(stream, basestring) else stream
318        self.registry = self.parse(stream)
319       
320   
321    @cached
322    def virtual_schemas(self):
323        """ Return a list of BioMartVirtualSchema instances representing each schema
324        """
325        schemas = [schema.attributes.get(name, "default") for name in self.registry.elements("virtualSchema")]
326        if not schemas:
327            schemas = [BioMartVirtualSchema(self.registry, name="default", connection=self.connection)]
328        else:
329            schemas = [BiomartVirtualSchema(schema, name=schema.attributes.get("name", "default"),
330                            connection=self.connection) for schema in self.registry.elements("virtualSchema")]
331        return schemas
332   
333    def virtual_schema(self, name):
334        """ Return a named virtual schema 
335        """
336        for schema in self.virtual_schemas():
337            if schema.name == name:
338                return schema
339        raise ValueError("Unknown schema name '%s'" % name)
340       
341    @cached
342    def marts(self):
343        """ Return a list off all 'mart' instances (BioMartDatabase instances) regardless of their virtual schemas 
344        """
345        return reduce(list.__add__, drop_iter(schema.marts() for schema in self.virtual_schemas()), [])
346   
347    def mart(self, name):
348        """ Return a named mart.
349        """
350        for mart in self.marts():
351            if mart.name == name:
352                return mart
353        raise ValueError("Unknown mart name '%s'" % name)
354   
355    def databases(self):
356        """ Same as marts()
357        """
358        return self.marts()
359   
360    def datasets(self):
361        """ Return a list of all datasets (BioMartDataset instances) from all marts regardless of their virtual schemas
362        """
363        return reduce(list.__add__, drop_iter(mart.datasets() for mart in self.marts()), [])
364   
365    def dataset(self, internalName, virtualSchema=None):
366        """ Return a BioMartDataset instance that matches the internalName
367        """
368        if virtualSchema is not None:
369            schema = self.virtual_schema(virtualSchema)
370            return schema.dataset(internalName)
371        else:
372            for d in self.datasets():
373                if d.internalName == internalName:
374                    return d
375            raise ValueError("Unknown dataset name: '%s'" % internalName)
376           
377   
378    def query(self, **kwargs):
379        """ Return an initialized BioMartQuery object with registry set to self.
380        Pass additional arguments to BioMartQuery.__init__ with keyword arguments
381        """
382        return BioMartQuery(self, **kwargs)
383   
384    def links_between(self, exporting, importing, virtualSchema="default"):
385        """ Return all links between exporting and importing datasets in the virtualSchema
386        """
387        schema = [schema for schema in self.virtual_schemas() if schema.name == virtualSchema][0]
388        return schema.links_between(exporting, importing)
389   
390    def get_path(self, exporting, importing):
391        raise NotImplementedError
392   
393    def __iter__(self):
394        return iter(self.marts())
395   
396    def _find_mart(self, name):
397        try:
398            return [mart for mart in self.marts() if name in [getattr(mart, "name", None),
399                                    getattr(mart, "internalName", None)]][0]
400        except IndexError, ex:
401            raise ValueError(name)
402       
403    def __getitem__(self, name):
404        return self._find_mart(name)
405   
406    def search(self, string, relevance=False):
407        results = []
408        for mart in self.marts():
409            results.extend([(rel, mart, dataset) for rel, dataset in  mart.search(string, True)])
410        results = sorted(results, reverse=True)
411        if not relevance:
412            results = [(mart, dataset) for _, mart, dataset in results]
413        return results
414   
415    @classmethod
416    def parse(cls, stream, parser=None):
417        """ Parse the registry file like object and return a DOM like description (XMLNode instance)
418        """
419        xml = stream.read()
420        doc = parseXML(xml, parser)
421        return doc
422         
423parseRegistry = BioMartRegistry.parse
424
425class BioMartVirtualSchema(object):
426    """ A class representation of a virtual schema.
427    """
428    def __init__(self, locations=None, name="default", connection=None):
429        self.locations = locations
430        self.name = name
431        self.connection = connection
432       
433    @cached
434    def marts(self):
435        """ Return a list off all 'mart' instances (BioMartDatabase instances) in this schema
436        """
437        return drop_iter(BioMartDatabase(connection=self.connection, **dict((str(key), val) \
438                for key, val in loc.attributes.items())) for loc in self.locations.elements("MartURLLocation"))
439   
440    def databases(self):
441        """ Same as marts()
442        """
443        return self.marts()
444   
445    @cached
446    def datasets(self):
447        """ Return a list of all datasets (BioMartDataset instances) from all marts in this schema
448        """
449        return reduce(list.__add__, drop_iter(mart.datasets() for mart in self.marts()), [])
450   
451   
452    def dataset(self, internalName):
453        """ Return a dataset with matching internalName
454        """
455        for dataset in self.datasets():
456            if dataset.internalName == internalName:
457                return dataset
458        raise ValueError("Unknown data set name '%s'" % internalName)
459   
460   
461    def links_between(self, exporting, importing):
462        """ Return a list of link names from exporting dataset to importing dataset
463        """
464        exporting = self[exporting]
465        importing = self[importing]
466        exporting = exporting.configuration().exportables()
467        importing = importing.configuration().importables()
468        exporting = set([(ex.linkName, getattr(ex, "linkVersion", "")) for ex in exporting])
469        importing = set([(im.linkName, getattr(ex, "linkVersion", "")) for im in importing])
470        links = exporting.intersection(importing)
471        return [link[0] for link in links]
472   
473    def links(self):
474        """ Return a list of (linkName, linkVersion) tuples defined by datasets in the schema
475        """
476        pass
477   
478    def query(self, **kwargs):
479        """ Return an initialized BioMartQuery object with registry and virtualSchema set to self.
480        Pass additional arguments to BioMartQuery.__init__ with keyword arguments
481        """
482        return BioMartQuery(self, virtualSchema=self.name, **kwargs)
483
484    def __iter__(self):
485        return iter(self.marts())
486   
487    def __getitem__(self, key):
488        try:
489            return self.dataset(key)
490        except ValueError:
491            raise KeyError(key)
492       
493   
494class BioMartDatabase(object):
495    """ An object representing a BioMart 'mart' instance.
496    Arguments::
497        - *name*   - Name of the mart instance ('ensembl' by default)
498        - *virtualSchema*    - Name of the virtualSchema this dataset belongs to ("default" by default)
499        - *connection*    - An optional BioMartConnection instance
500       
501    """
502    host = "www.biomart.org"
503    path = "/biomart/martservice"
504    def __init__(self, name="ensembl", virtualSchema="default", connection=None,
505                 database="ensembl_mart_60", default="1",
506                 displayName="ENSEMBL GENES 60 (SANGER UK)", host="www.biomart.org",
507                 includeDatasets="", martUser="", path="/biomart/martservice",
508                 port="80", serverVirtualSchema="default", visible="1", **kwargs):
509       
510        self.name = name
511        self.virtualSchema = virtualSchema
512        self.database = database,
513        self.default = default
514        self.displayName = displayName
515        self.host = host
516        self.includeDatasets = includeDatasets
517        self.martUser = martUser 
518        self.path = path
519        self.port = port
520        self.serverVirtualSchema = serverVirtualSchema
521        self.visible = visible
522        self.__dict__.update(kwargs.items())
523       
524        if connection is None:
525            connection = BioMartConnection()
526           
527        if kwargs.get("redirect", None) == "1" and BioMartConnection.FOLLOW_REDIRECTS:
528            redirect = BioMartConnection("http://" + self.host + ":" + self.port + self.path, cache=connection.cache)
529            try:
530                registry = redirect.registry()
531                connection = redirect
532            except urllib2.HTTPError, ex:
533                warnings.warn("'%s' is not responding!, using the default original connection. %s" % (redirect.address, str(ex)))
534           
535        self.connection = connection
536       
537#        self.connection = BioMartConnection("http://" + self.host + ":" + self.port + self.path) if connection is None \
538#                            or (kwargs.get("redirect", None) == "1" and BioMartConnection.FOLLOW_REDIRECTS) else connection
539
540    @cached   
541    def _datasets_index(self):
542        keys = ["datasetType", "internalName", "displayName", "visible", "assembly", "_5", "_6", "virtualSchema", "date"]
543       
544        try:
545            datasets = self.connection.datasets(mart=self.name, virtualSchema=self.virtualSchema).read()
546        except BioMartError, ex:
547            if self.virtualSchema == "default":
548                datasets = self.connection.datasets(mart=self.name).read()
549            else:
550                raise
551        datasets = de_tab(datasets)
552        return [dict(zip(keys, line)) for line in datasets]
553   
554    @cached
555    def datasets(self):
556        """ Return a list of all datasets (BioMartDataset instances) in this database
557        """
558        return drop_iter(BioMartDataset(mart=self.name, connection=self.connection, **dataset) for dataset in self._datasets_index())
559   
560    def dataset_attributes(self, dataset, **kwargs):
561        """ Return a list of dataset attributes
562        """
563        dataset = self._find_dataset(dataset)
564        return dataset.attributes()
565   
566    def dataset_filters(self, dataset, **kwargs):
567        """ Return a list of dataset filters
568        """
569        dataset = self._find_dataset(dataset)
570        return dataset.filters()
571   
572    def dataset_query(self, dataset, filters=[], attributes=[]):
573        """ Return an dataset query based on dataset, filters and attributes
574        """
575        dataset = self._find_dataset(dataset)
576        return BioMartQuery(self.con, [dataset], filters, attributes).run()
577       
578    def _find_dataset(self, internalName):
579        for dataset in self.datasets():
580            if dataset.internalName == internalName:
581                return dataset
582        raise ValueError("Uknown dataset name '%s'" % internalName)
583   
584       
585    def __getitem__(self, name):
586        try:
587            return self._find_dataset(name)
588        except ValueError:
589            raise KeyError(name)
590   
591   
592    def search(self, string, relevance=False):
593        strings = string.lower().split()
594        results = []
595        for dataset, conf in drop_iter((dataset, dataset.configuration()) for dataset in self.datasets()):
596            trees = conf.attribute_pages() + conf.attribute_groups() + \
597                    conf.attribute_collections() + conf.attributes()
598                   
599            names = " ".join([getattr(tree, "displayName", "") for tree in trees]).lower()
600            count = sum([names.count(s) for s in strings])
601            if count:
602                results.append((count ,dataset))
603        return results
604       
605class BioMartDataset(object):
606    """ An object representing a BioMart dataset (returned by BioMartDatabase)
607    """
608   
609    # these attributes store the result of ".../martservice?type=datasets&mart=..." query 
610    FIELDS = ["datasetType", "internalName", "displayName", "visible", "assembly", "_5", "_6", "virtualSchema", "date"]
611   
612    def __init__(self, mart="ensembl", internalName="hsapiens_gene_ensembl", virtualSchema="default", connection=None, 
613                 datasetType="TableSet", displayName="", visible="1", assembly="", date="", **kwargs):
614        self.connection = connection if connection is not None else BioMartConnection()
615        self.mart = mart
616        self.internalName = internalName
617        self.virtualSchema = virtualSchema
618        self.datasetType = datasetType
619        self.displayName = displayName
620        self.visible = visible
621        self.assembly = assembly
622        self.date = date
623        self.__dict__.update(kwargs)
624        self._attributes = None
625        self._filters = None
626       
627       
628    @cached
629    def attributes(self):
630        """ Return a list of available attributes for this dataset (Attribute instances)
631        """
632        stream = self.connection.attributes(dataset=self.internalName, virtualSchema=self.virtualSchema)
633        response = stream.read()
634        lines = response.splitlines()
635        return [Attribute(line) for line in lines if line.strip()]
636       
637   
638    @cached
639    def filters(self):
640        """ Return a list of available filters for this dataset (Filter instances)
641        """
642        stream = self.connection.filters(dataset=self.internalName, virtualSchema=self.virtualSchema)
643        response = stream.read()
644        lines = response.splitlines()
645        return [Filter(line) for line in lines if line.strip()]
646   
647   
648    @cached
649    def configuration(self, parser=None):
650        """ Return the configuration tree for this dataset (DatasetConfig instance)
651        """
652        stream = self.connection.configuration(dataset=self.internalName, virtualSchema=self.virtualSchema)
653        response = stream.read()
654        doc = parseXML(response, parser)
655        config = list(doc.elements("DatasetConfig"))[0]
656        return DatasetConfig(BioMartRegistry(self.connection), config.tag, config.attributes, config.children)
657       
658       
659    def get_data(self, attributes=[], filters=[], unique=False):
660        """ Constructs and runs a BioMartQuery returning its results
661        """ 
662        return BioMartQuery(self.connection, dataset=self, attributes=attributes, filters=filters,
663                             uniqueRows=unique, virtualSchema=self.virtualSchema).run()
664   
665   
666    def count(self, filters=[], unique=False):
667        """ Constructs and runs a BioMartQuery to count the number of returned lines
668        """
669        return BioMartQuery(self.connection, dataset=self, filters=filters, uniqueRows=unique,
670                            virtualSchema=self.virtualSchema).get_count()
671   
672                           
673    def get_example_table(self, attributes=[], filters=[], unique=False):
674        query = BioMartQuery(self.connection, dataset=self, attributes=attributes, filters=filters, uniqueRows=unique,
675                            virtualSchema=self.virtualSchema)
676        return query.get_example_table()
677       
678
679class BioMartQuery(object):
680    """ A class for constructing a query to run on a BioMart server
681   
682    Example::
683        >>> BioMartQuery(connection, dataset="hsapiens_gene_ensembl", attributes=["ensembl_transcript_id",
684        ...     "chromosome_name"], filters=[("chromosome_name", ["22"])]).get_count()
685        1221
686        >>> #Equivalent to
687        ...
688        >>> query = BioMartQuery(connection)
689        >>> query.set_dataset("hsapiens_gene_ensembl")
690        >>> query.add_filter("chromosome_name", "22")
691        >>> query.add_attribute("ensembl_transcript_id")
692        >>> query.add_attribute("chromosome_name")
693        >>> query.get_count()
694        1221
695    """
696    class XMLQuery(object):
697        XML = """<?xml version="1.0" encoding="UTF-8"?>
698<!DOCTYPE Query>
699<Query virtualSchemaName = "%(virtualSchemaName)s" formatter = "%(formatter)s" header = "%(header)s" uniqueRows = "%(uniqueRows)s" count = "%(count)s" datasetConfigVersion = "%(version)s" > 
700%(datasets_xml)s 
701</Query>"""
702        version = "0.6"
703        def __init__(self, query):
704            self.query = query
705            self._query = query._query
706            self.__dict__.update(query.__dict__) 
707       
708        def get_xml(self, count=None, header=False):
709            datasets_xml = "\n".join(self.query_dataset(*query) for query in self._query)
710           
711            links_xml = self.query_links(self._query)
712            datasets_xml += "\n" + links_xml
713           
714            count = self.count if count is None else count
715            args = dict(datasets_xml=datasets_xml,
716                        uniqueRows="1" if self.uniqueRows else "0",
717                        count="1" if count else "",
718                        header= "1" if header else "0",
719                        virtualSchemaName=self.virtualSchema,
720                        formatter=self.format,
721                        version=self.version)
722            xml = self.XML % args
723            return xml
724       
725        def query_attributes(self, attributes):
726            def name(attr):
727                if isinstance(attr, Attribute):
728                    return attr.internalName
729                elif isinstance(attr, AttributeDescription):
730                    return attr.internalName
731                else:
732                    return str(attr)
733               
734            xml = "\n".join('\t\t<Attribute name = "%s" />' % name(attr).lower() for attr in attributes)
735            return xml
736       
737        def query_filter(self, filters):
738            def name(filter):
739                if isinstance(filter, Filter):
740                    return filter.internalName
741                elif isinstance(filter, FilterDescription):
742                    return getattr(filter, "field", getattr(filter, "internalName"))
743                else:
744                    return str(filter)
745               
746            def args(args):
747                if isinstance(args, list):
748                    return 'value="%s"' % ",".join(args)
749                elif isinstance(args, basestring):
750                    return 'value="%s"' % args
751                elif isinstance(args, dict):
752                    return " ".join(['%s="%s"' % (key, value) for key, value in args.iteritems()])
753                   
754#            value = lambda value: str(value) if not isinstance(value, list) else ",".join([str(v) for v in value])
755#            xml = "\n".join('\t\t<Filter name = "%s" value="%s" />' % (name(fil), value(val)) for fil, val in filters)
756            xml = "\n".join('\t\t<Filter name = "%s" %s />' % (name(fil), args(val)) for fil, val in filters)
757            return xml
758       
759        def query_dataset(self, dataset, attributes, filters):
760            name, interface = (dataset.internalName, "default") if \
761                    isinstance(dataset, BioMartDataset) else (dataset, "default")
762            xml = '\t<Dataset name = "%s" interface = "%s" >\n' % (name, interface)
763            xml += self.query_attributes(attributes) + "\n"
764            xml += self.query_filter(filters)
765            xml += '\n\t</Dataset>'
766            return xml
767       
768        def query_links(self, query):
769            def name(dataset):
770                if isinstance(dataset, BioMartDataset):
771                    return getattr(dataset, "internalName", getattr(dataset, "name", None))
772                else:
773                    return str(dataset)
774               
775            if len(query) == 2:
776                return '\t<Links source="%s" target="%s"/>' % (name(query[0][0]), name(query[1][0]))
777            else:
778                return ""
779       
780    class XMLQueryOld(XMLQuery):
781        version = "0.4"
782        def query_filter(self, filters):
783            def name(filter):
784                if isinstance(filter, Filter):
785                    return filter.internalName
786                elif isinstance(filter, FilterDescription):
787                    return getattr(filter, "field", getattr(filter, "internalName"))
788                else:
789                    return str(filter)
790               
791            value = lambda value: str(value) if not isinstance(value, list) else ",".join([str(v) for v in value])
792            xml = "\n".join('\t\t<ValueFilter name = "%s" value="%s" />' % (name(fil), value(val)) for fil, val in filters)
793            return xml
794       
795    def __init__(self, registry, virtualSchema="default", dataset=None, attributes=[], filters=[], count=False, uniqueRows=False,
796                 format="TSV"):
797        if isinstance(registry, BioMartConnection):
798            self.registry = BioMartRegistry(registry)
799            self.virtualSchema = virtualSchema
800        elif isinstance(registry, BioMartVirtualSchema):
801            self.registry = registry.connection
802            self.virtualSchema = registry.name
803        else:
804            self.registry = registry
805            self.virtualSchema = virtualSchema
806           
807        self._query = []
808        if dataset:
809            self.set_dataset(dataset)
810            for attr in attributes:
811                self.add_attribute(attr)
812            for filter, value in filters:
813                self.add_filter(filter, value)
814        self.count = count
815        self.uniqueRows = uniqueRows
816        self.format = format
817       
818    def set_dataset(self, dataset):
819        self._query.append((dataset, [], []))
820   
821    def add_filter(self, filter, value):
822        self._query[-1][2].append((filter, value))
823   
824    def add_attribute(self, attribute):
825        self._query[-1][1].append(attribute)
826   
827    def get_count(self):
828        count = self.run(count=True)
829        if count.strip():
830            count = int(count.strip())
831        else:
832            count = 0
833        return count
834   
835    def run(self, count=None, header=False):
836        stream = self.registry.connection.request(query=self.xml_query(count=count, header=header).replace("\n", "").replace("\t", ""))
837        stream = stream.read()
838        if stream.startswith("Query ERROR:"):
839            raise BioMartQueryError(stream)
840        return stream
841   
842    def set_unique(self, unique=False):
843        self.uniqueRows = unique
844   
845    def xml_query(self, count=None, header=False): 
846        self.version = version = "0.4"
847         
848        schema = self.virtualSchema
849       
850        dataset = self._query[0][0]
851        dataset = dataset.internalName if isinstance(dataset, BioMartDataset) else dataset
852       
853        conf = self.registry.dataset(dataset, virtualSchema=self.virtualSchema).configuration()
854        self.version = version = getattr(conf, "softwareVersion", "0.4")
855       
856        if version > "0.4":
857            xml = self.XMLQuery(self).get_xml(count, header)
858        else:
859            xml = self.XMLQueryOld(self).get_xml(count, header)
860        return xml
861       
862    def get_example_table(self):
863        import orange
864        data = self.run(count=False, header=True)
865       
866        if self.format.lower() == "tsv":
867            header, data = data.split("\n", 1)
868            domain = orange.Domain([orange.StringVariable(name) for name in header.split("\t")], False)
869            data = [line.split("\t") for line in data.split("\n") if line.strip()]
870            return orange.ExampleTable(domain, data) if data else None
871        elif self.format.lower() == "fasta":
872            domain = orange.Domain([orange.StringVariable("id"), orange.StringVariable("sequence")], False) #TODO: meaningful id
873            examples = []
874            from StringIO import StringIO
875            from Bio import SeqIO
876            for seq in SeqIO.parse(StringIO(data), "fasta"):
877                examples.append([seq.id, str(seq.seq)])
878            return orange.ExampleTable(domain, examples)
879        else:
880            raise BioMartError("Unsupported format: %" % self.format)
881
882
883def get_pointed(self):
884    if self.is_pointer():
885        pointerDataset = self.pointerDataset
886        if hasattr(self, "pointerAttribute"):
887            name, getter = self.pointerAttribute, "AttributeDescription"
888        elif hasattr(self, "pointerFilter"):
889            name, getter = self.pointerFilter, "FilterDescription"
890       
891        dataset = self.registry.dataset(pointerDataset)
892       
893        conf = dataset.configuration()
894        desc = list(conf.elements(getter, internalName=name))
895        if desc:
896            return dataset, desc[0]
897        else:
898            warnings.warn("Could not resolve pointer '%s' in '%s'" % (name, pointerDataset), UserWarning, stacklevel=2)
899            return None, None
900       
901       
902def is_pointer(self):
903    return hasattr(self, "pointerAttribute") or hasattr(self, "pointerFilter")
904
905
906class ConfigurationNode(XMLNode):
907    def __init__(self, registry, *args, **kwargs):
908        XMLNode.__init__(self, *args, **kwargs)
909        self.registry = registry
910        self.__dict__.update([(self._name_mangle(name), value) \
911                               for name, value in self.attributes.iteritems()])
912       
913        self.children = [self._factory(child) for child in self.children]
914       
915    def _name_mangle(self, name):
916        if name in self.__dict__:
917            return name + "_"
918        else:
919            return name
920       
921    def _factory(self, node):
922        tag = node.tag
923        class_ = globals().get(tag, None)
924        if not class_:
925            raise ValueError("Unknown node '%s;" % tag)
926        else:
927            return class_(self.registry, tag, node.attributes, node.children)
928       
929    def elements(self, tag=None, **kwargs):
930        if isinstance(tag, type):
931            tag = tag.__name__
932        return XMLNode.elements(self, tag, **kwargs)
933   
934    def elements_top(self, tag=None, **kwargs):
935        if isinstance(tag, type):
936            tag = tag.__name__
937        return XMLNode.elements_top(self, tag, **kwargs)
938   
939    def __repr__(self):
940        return '%s("%s", %s)' % (self.__class__.__name__, self.tag, repr(self.attributes))
941
942   
943class DatasetConfig(ConfigurationNode):
944    pass
945
946class Exportable(ConfigurationNode):
947    pass
948
949class Importable(ConfigurationNode):
950    pass
951
952class FilterPage(ConfigurationNode):
953    pass
954
955class FilterGroup(ConfigurationNode):
956    pass
957
958class FilterCollection(ConfigurationNode):
959    pass
960
961class FilterDescription(ConfigurationNode):
962
963    is_pointer = is_pointer
964    get_pointed = get_pointed
965   
966class AttributePage(ConfigurationNode):
967    pass
968
969class AttributeGroup(ConfigurationNode):
970    pass
971
972class AttributeCollection(ConfigurationNode):
973    pass
974
975class AttributeDescription(ConfigurationNode):
976   
977    is_pointer = is_pointer
978    get_pointed = get_pointed
979   
980class Option(ConfigurationNode):
981    pass
982
983class PushAction(ConfigurationNode):
984    pass
985
986class MainTable(ConfigurationNode):
987    pass
988
989class Key(ConfigurationNode):
990    pass
991
992   
993
994if __name__ == "__main__":
995    con = BioMartConnection("http://www.biomart.org/biomart/martservice")
996    registry = BioMartRegistry(con)
997    for schema in  registry.virtual_schemas():
998        print "Virtual schema '%s'" % schema.name
999        for mart in schema.databases():
1000            print "\tMart: '%s' ('%s'):" % (mart.name, mart.displayName)
1001            for dataset in mart.datasets():
1002                print "\t\t Dataset '%s' %s' '%s'" % (dataset.datasetType, dataset.internalName, dataset.displayName)
1003 
1004    database = BioMartDatabase(name="dicty", connection=con)
1005    datasets = database.datasets()
1006    print datasets
1007    dataset = datasets[2]
1008    configuration =  dataset.configuration()
1009    attr = dataset.attributes()
1010    print attr
1011    filters = dataset.filters()
1012    print filters
1013    reg = BioMartRegistry(con)
1014   
1015    dataset = reg.dataset("scerevisiae_gene_ensembl")
1016    query = BioMartQuery(con, dataset="scerevisiae_gene_ensembl",
1017                         attributes=["ensembl_transcript_id", "transcript_exon_intron"],
1018                         filters = [("chromosome_name", "I"), ("with_wikigene", {"excluded":"1"})],
1019                         format="FASTA")
1020    print query.xml_query()
1021    print query.run()
1022   
1023    data = query.get_example_table()
1024    data.save("seq.tab")
1025   
1026   
1027    import doctest
1028    doctest.testmod(extraglobs={"connection": con, "registry":registry}, optionflags=doctest.ELLIPSIS)
1029   
Note: See TracBrowser for help on using the repository browser.