source: orange-bioinformatics/_bioinformatics/obiBioMart.py @ 1682:2e2c671e4300

Revision 1682:2e2c671e4300, 38.0 KB checked in by Lan Zagar <lan.zagar@…>, 22 months ago (diff)

Replaced split(\n) with splitlines (closes #1160).

RevLine 
[1302]1""" A python module for accessing BioMart MartService
2
[1133]3Example::
4    >>> connection = BioMartConnection("http://www.biomart.org/biomart/martservice")
5    >>> reg = BioMartRegistry(connection)
6    >>> for mart in reg.marts():
[1302]7    ...    print mart.name
[1133]8    ...
[1302]9    ensembl...
10    >>> dataset = BioMartDataset(mart="ensembl", internalName="hsapiens_gene_ensembl", virtualSchema="default", connection=connection)
11    >>> for attr in dataset.attributes()[:10]:
[1133]12    ...    print attr.name
13    ...
[1302]14    Ensembl Gene ID...
[1133]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()
[1045]23"""
[1038]24
[1133]25import sys, os
[1038]26import urllib2
27import traceback
28import posixpath
29import xml.dom.minidom as minidom
[1133]30import shelve
[1302]31import itertools
[1133]32
[1306]33import warnings
34
[1310]35from functools import partial
36
[1632]37from Orange.orng import orngEnviron
[1038]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):
[1045]56    """ A class representing an attribute in a BioMart dataset (returned by BioMartDataset.attributes())
[1302]57    Attributes:
[1133]58        - *internalName*    - Internal attribute name
[1045]59        - *name*    - Human readable name of the attribute
60        - *description*    - Human readable description of the attribute
61    """
[1133]62    __slots__ = ["internalName", "name", "description", "_4", "format", "table", "_7"]
[1038]63    __init__ = _init__slots_from_line
64   
65    def __repr__(self):
[1045]66        return "Attribute('%s')" % "\t".join(getattr(self, name, "") for name in self.__slots__)
67     
[1038]68           
69class Filter(object):
[1133]70    """ A class representing a filter for a BioMart dataset (returned by BioMartDataset.filters())
[1302]71    Attributes:
[1133]72        - *internalName* - Internal filter name
[1045]73        - *name*    - Filter name
74        - *values*    - Lists possible filter values
75        - *description*    - Filter description
76    """
[1133]77    __slots__ =  ["internalName", "name", "values", "description", "type", "qualifiers", "_6", "attribute_table"]
[1038]78    __init__ = _init__slots_from_line
79   
80    def __repr__(self):
[1045]81        return "Filter('%s')" % "\t".join(getattr(self, name, "") for name in self.__slots__)
[1038]82
[1302]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           
[1038]106        for child in self.children:
[1302]107            for el in child.elements(tag, **kwargs):
[1038]108                yield el
109               
[1302]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               
[1038]132    def __repr__(self):
[1302]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", {}))
[1038]147       
[1302]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   
[1038]165
166def de_tab(text, sep="\t"):
[1682]167    return [line.split(sep) for line in text.splitlines() if line.strip()]
[1038]168
[1310]169
[1038]170def cached(func):
171    from functools import wraps
[1045]172    @wraps(func)
[1038]173    def f(self):
[1045]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__)
[1038]177    return f
[1310]178
[1038]179   
[1310]180def drop_iter(generator):
181    """ Drop all elements from the iterator that raise an exception 
182    """
[1038]183    def _iter(generator):
184        while True:
185            try:
186                yield generator.next()
187            except StopIteration:
188                raise StopIteration
189            except Exception, ex:
[1306]190                warnings.warn("An error occured during iteration:\n%s" % str(ex), UserWarning, stacklevel=2)
[1038]191    return list(_iter(generator))
[1302]192
193   
194DEFAULT_ADDRESS = "http://www.biomart.org/biomart/martservice"
[1310]195
[1302]196try:
[1310]197    DEFAULT_DATA_CACHE = shelve.open(os.path.join(orngEnviron.bufferDir, "BioMartDataCache.pck"))
[1302]198except Exception, ex:
[1310]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 = {}
[1302]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)
[1306]214    elif response.startswith("non-BioMart die():"):
215        raise BioMartServerError(response)
[1302]216
217
[1038]218class BioMartConnection(object):
[1302]219    """ A connection to a BioMart martservice server.
[1045]220   
221    Example::
[1310]222        >>> connection = BioMartConnection("http://www.biomart.org/biomart/martservice")
223        >>> response = connection.registry()
224        >>> response = connection.datasets(mart="ensembl")
[1038]225    """
[1302]226    FOLLOW_REDIRECTS = False
[1045]227   
[1310]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       
[1302]233        self.address = address if address is not None else DEFAULT_ADDRESS
[1310]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
[1306]237        self.errorCache = {}
[1310]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
[1038]244       
[1133]245    def request_url(self, **kwargs):
[1310]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")
[1133]249        return url.replace(" ", "%20")
250   
[1038]251    def request(self, **kwargs):
[1133]252        url = self.request_url(**kwargs)
[1310]253       
254        cache = self._get_cache_for_args(kwargs)
255       
[1306]256        if url in self.errorCache:
257            raise self.errorCache[url]
[1310]258       
259        if str(url) not in cache:
[1306]260            try:
261                response = urllib2.urlopen(url).read()
262                checkBioMartServerError(response)
263            except Exception, ex:
264                self.errorCache[url] = ex
265                raise ex
266           
[1310]267            cache[str(url)] = response
268            if hasattr(cache, "sync"):
269                cache.sync()
270               
[1302]271        from StringIO import StringIO
[1310]272        return StringIO(cache[str(url)])
[1038]273   
274    def registry(self, **kwargs):
[1302]275        return self.request(type="registry")
[1038]276   
[1302]277    def datasets(self, mart="ensembl", **kwargs):
278        return self.request(type="datasets", mart=mart, **kwargs)
[1038]279   
[1302]280    def attributes(self, dataset="oanatinus_gene_ensembl", **kwargs):
281        return self.request(type="attributes", dataset=dataset, **kwargs)
[1038]282   
[1302]283    def filters(self, dataset="oanatinus_gene_ensembl", **kwargs):
284        return self.request(type="filters", dataset=dataset, **kwargs)
[1038]285   
[1302]286    def configuration(self, dataset="oanatinus_gene_ensembl", **kwargs):
287        return self.request(type="configuration", dataset=dataset, **kwargs)
[1310]288   
289    def clearCache(self):
290        self.dataCache.clear()
291        self.metaCache.clear()
292        self.errorCache.clear()   
[1302]293       
294       
[1038]295class BioMartRegistry(object):
[1302]296    """ A class representing a BioMart registry
297    Arguments:
298        - stream: A file like object with xml registry or a BioMartConnection instance
[1045]299   
300    Example::
[1302]301    >>> registry = BioMartRegistry(connection)
[1045]302    >>> for schema in registry.virtual_schemas():
303    ...    print schema.name
304    ...
[1302]305    default
[1045]306    """
[1302]307    def __init__(self, stream):
308        self.connection = stream if isinstance(stream, BioMartConnection) else None
[1038]309        if self.connection:
[1302]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
[1038]316        else:
[1302]317            stream = open(stream, "rb") if isinstance(stream, basestring) else stream
318        self.registry = self.parse(stream)
319       
[1038]320   
[1045]321    @cached
[1038]322    def virtual_schemas(self):
[1045]323        """ Return a list of BioMartVirtualSchema instances representing each schema
324        """
[1038]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:
[1045]329            schemas = [BiomartVirtualSchema(schema, name=schema.attributes.get("name", "default"),
330                            connection=self.connection) for schema in self.registry.elements("virtualSchema")]
[1038]331        return schemas
[1302]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)
[1038]340       
[1045]341    @cached
342    def marts(self):
343        """ Return a list off all 'mart' instances (BioMartDatabase instances) regardless of their virtual schemas 
344        """
[1310]345        return reduce(list.__add__, drop_iter(schema.marts() for schema in self.virtual_schemas()), [])
[1045]346   
[1302]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   
[1038]355    def databases(self):
356        """ Same as marts()
357        """
358        return self.marts()
359   
[1045]360    def datasets(self):
361        """ Return a list of all datasets (BioMartDataset instances) from all marts regardless of their virtual schemas
[1038]362        """
[1310]363        return reduce(list.__add__, drop_iter(mart.datasets() for mart in self.marts()), [])
[1038]364   
[1302]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   
[1045]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        """
[1133]382        return BioMartQuery(self, **kwargs)
[1045]383   
[1038]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   
[1133]390    def get_path(self, exporting, importing):
[1302]391        raise NotImplementedError
[1133]392   
[1038]393    def __iter__(self):
394        return iter(self.marts())
395   
[1048]396    def _find_mart(self, name):
397        try:
398            return [mart for mart in self.marts() if name in [getattr(mart, "name", None),
[1133]399                                    getattr(mart, "internalName", None)]][0]
[1048]400        except IndexError, ex:
401            raise ValueError(name)
402       
403    def __getitem__(self, name):
404        return self._find_mart(name)
405   
[1133]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   
[1302]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
[1038]425class BioMartVirtualSchema(object):
[1045]426    """ A class representation of a virtual schema.
427    """
[1038]428    def __init__(self, locations=None, name="default", connection=None):
429        self.locations = locations
430        self.name = name
431        self.connection = connection
432       
[1045]433    @cached
[1038]434    def marts(self):
[1045]435        """ Return a list off all 'mart' instances (BioMartDatabase instances) in this schema
[1038]436        """
[1310]437        return drop_iter(BioMartDatabase(connection=self.connection, **dict((str(key), val) \
[1045]438                for key, val in loc.attributes.items())) for loc in self.locations.elements("MartURLLocation"))
[1038]439   
440    def databases(self):
441        """ Same as marts()
442        """
443        return self.marts()
444   
[1045]445    @cached
[1038]446    def datasets(self):
[1045]447        """ Return a list of all datasets (BioMartDataset instances) from all marts in this schema
[1038]448        """
[1310]449        return reduce(list.__add__, drop_iter(mart.datasets() for mart in self.marts()), [])
[1038]450   
[1302]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
[1310]458        raise ValueError("Unknown data set name '%s'" % internalName)
[1302]459   
460   
[1038]461    def links_between(self, exporting, importing):
[1045]462        """ Return a list of link names from exporting dataset to importing dataset
463        """
[1038]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
[1045]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)
[1038]483
484    def __iter__(self):
485        return iter(self.marts())
486   
487    def __getitem__(self, key):
488        try:
[1302]489            return self.dataset(key)
[1038]490        except ValueError:
491            raise KeyError(key)
492       
493   
494class BioMartDatabase(object):
495    """ An object representing a BioMart 'mart' instance.
496    Arguments::
[1045]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
[1302]500       
[1038]501    """
[1045]502    host = "www.biomart.org"
503    path = "/biomart/martservice"
[1302]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       
[1038]510        self.name = name
511        self.virtualSchema = virtualSchema
[1302]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
[1038]522        self.__dict__.update(kwargs.items())
[1306]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
[1045]539
540    @cached   
541    def _datasets_index(self):
[1302]542        keys = ["datasetType", "internalName", "displayName", "visible", "assembly", "_5", "_6", "virtualSchema", "date"]
543       
[1133]544        try:
[1302]545            datasets = self.connection.datasets(mart=self.name, virtualSchema=self.virtualSchema).read()
[1133]546        except BioMartError, ex:
547            if self.virtualSchema == "default":
[1302]548                datasets = self.connection.datasets(mart=self.name).read()
[1133]549            else:
550                raise
[1302]551        datasets = de_tab(datasets)
[1133]552        return [dict(zip(keys, line)) for line in datasets]
[1038]553   
[1045]554    @cached
[1038]555    def datasets(self):
[1045]556        """ Return a list of all datasets (BioMartDataset instances) in this database
[1038]557        """
[1310]558        return drop_iter(BioMartDataset(mart=self.name, connection=self.connection, **dataset) for dataset in self._datasets_index())
[1038]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       
[1302]578    def _find_dataset(self, internalName):
579        for dataset in self.datasets():
580            if dataset.internalName == internalName:
581                return dataset
[1310]582        raise ValueError("Uknown dataset name '%s'" % internalName)
[1302]583   
[1038]584       
585    def __getitem__(self, name):
[1302]586        try:
587            return self._find_dataset(name)
588        except ValueError:
589            raise KeyError(name)
590   
[1133]591   
592    def search(self, string, relevance=False):
593        strings = string.lower().split()
594        results = []
[1310]595        for dataset, conf in drop_iter((dataset, dataset.configuration()) for dataset in self.datasets()):
[1133]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
[1038]604       
605class BioMartDataset(object):
606    """ An object representing a BioMart dataset (returned by BioMartDatabase)
607    """
[1302]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()
[1133]615        self.mart = mart
[1204]616        self.internalName = internalName
[1133]617        self.virtualSchema = virtualSchema
[1302]618        self.datasetType = datasetType
619        self.displayName = displayName
620        self.visible = visible
621        self.assembly = assembly
622        self.date = date
[1133]623        self.__dict__.update(kwargs)
[1038]624        self._attributes = None
625        self._filters = None
626       
[1302]627       
628    @cached
[1038]629    def attributes(self):
[1045]630        """ Return a list of available attributes for this dataset (Attribute instances)
[1038]631        """
[1302]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       
[1038]637   
[1302]638    @cached
[1038]639    def filters(self):
[1045]640        """ Return a list of available filters for this dataset (Filter instances)
[1038]641        """
[1302]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()]
[1038]646   
[1302]647   
648    @cached
649    def configuration(self, parser=None):
[1310]650        """ Return the configuration tree for this dataset (DatasetConfig instance)
[1038]651        """
[1302]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)
[1310]657       
658       
[1038]659    def get_data(self, attributes=[], filters=[], unique=False):
[1045]660        """ Constructs and runs a BioMartQuery returning its results
[1038]661        """ 
[1302]662        return BioMartQuery(self.connection, dataset=self, attributes=attributes, filters=filters,
[1133]663                             uniqueRows=unique, virtualSchema=self.virtualSchema).run()
[1038]664   
[1310]665   
[1038]666    def count(self, filters=[], unique=False):
[1045]667        """ Constructs and runs a BioMartQuery to count the number of returned lines
[1038]668        """
[1302]669        return BioMartQuery(self.connection, dataset=self, filters=filters, uniqueRows=unique,
[1133]670                            virtualSchema=self.virtualSchema).get_count()
[1310]671   
[1133]672                           
673    def get_example_table(self, attributes=[], filters=[], unique=False):
[1310]674        query = BioMartQuery(self.connection, dataset=self, attributes=attributes, filters=filters, uniqueRows=unique,
675                            virtualSchema=self.virtualSchema)
676        return query.get_example_table()
[1133]677       
[1038]678
679class BioMartQuery(object):
680    """ A class for constructing a query to run on a BioMart server
[1302]681   
[1038]682    Example::
[1302]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
[1038]695    """
[1133]696    class XMLQuery(object):
697        XML = """<?xml version="1.0" encoding="UTF-8"?>
[1038]698<!DOCTYPE Query>
[1180]699<Query virtualSchemaName = "%(virtualSchemaName)s" formatter = "%(formatter)s" header = "%(header)s" uniqueRows = "%(uniqueRows)s" count = "%(count)s" datasetConfigVersion = "%(version)s" > 
[1038]700%(datasets_xml)s 
[1133]701</Query>"""
[1302]702        version = "0.6"
[1133]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
[1302]729                elif isinstance(attr, AttributeDescription):
[1133]730                    return attr.internalName
731                else:
732                    return str(attr)
733               
[1302]734            xml = "\n".join('\t\t<Attribute name = "%s" />' % name(attr).lower() for attr in attributes)
[1133]735            return xml
736       
737        def query_filter(self, filters):
738            def name(filter):
739                if isinstance(filter, Filter):
740                    return filter.internalName
[1302]741                elif isinstance(filter, FilterDescription):
[1133]742                    return getattr(filter, "field", getattr(filter, "internalName"))
743                else:
744                    return str(filter)
745               
[1302]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)
[1133]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
[1302]786                elif isinstance(filter, FilterDescription):
[1133]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       
[1045]795    def __init__(self, registry, virtualSchema="default", dataset=None, attributes=[], filters=[], count=False, uniqueRows=False,
796                 format="TSV"):
797        if isinstance(registry, BioMartConnection):
[1302]798            self.registry = BioMartRegistry(registry)
[1045]799            self.virtualSchema = virtualSchema
800        elif isinstance(registry, BioMartVirtualSchema):
[1302]801            self.registry = registry.connection
[1045]802            self.virtualSchema = registry.name
803        else:
804            self.registry = registry
[1133]805            self.virtualSchema = virtualSchema
[1045]806           
[1133]807        self._query = []
[1045]808        if dataset:
809            self.set_dataset(dataset)
810            for attr in attributes:
811                self.add_attribute(attr)
812            for filter, value in filters:
[1133]813                self.add_filter(filter, value)
[1038]814        self.count = count
[1045]815        self.uniqueRows = uniqueRows
[1038]816        self.format = format
817       
818    def set_dataset(self, dataset):
[1045]819        self._query.append((dataset, [], []))
[1038]820   
821    def add_filter(self, filter, value):
[1045]822        self._query[-1][2].append((filter, value))
[1038]823   
824    def add_attribute(self, attribute):
[1045]825        self._query[-1][1].append(attribute)
[1038]826   
827    def get_count(self):
[1045]828        count = self.run(count=True)
[1133]829        if count.strip():
830            count = int(count.strip())
831        else:
832            count = 0
[1038]833        return count
834   
[1133]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", ""))
[1038]837        stream = stream.read()
838        if stream.startswith("Query ERROR:"):
839            raise BioMartQueryError(stream)
840        return stream
841   
[1045]842    def set_unique(self, unique=False):
843        self.uniqueRows = unique
[1038]844   
[1133]845    def xml_query(self, count=None, header=False): 
[1180]846        self.version = version = "0.4"
[1133]847         
[1180]848        schema = self.virtualSchema
849       
850        dataset = self._query[0][0]
851        dataset = dataset.internalName if isinstance(dataset, BioMartDataset) else dataset
852       
[1302]853        conf = self.registry.dataset(dataset, virtualSchema=self.virtualSchema).configuration()
[1180]854        self.version = version = getattr(conf, "softwareVersion", "0.4")
855       
[1133]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)
[1180]865       
[1133]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)
[1180]869            data = [line.split("\t") for line in data.split("\n") if line.strip()]
870            return orange.ExampleTable(domain, data) if data else None
[1133]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
[1302]876            for seq in SeqIO.parse(StringIO(data), "fasta"):
877                examples.append([seq.id, str(seq.seq)])
[1133]878            return orange.ExampleTable(domain, examples)
[1180]879        else:
880            raise BioMartError("Unsupported format: %" % self.format)
881
882
[1302]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"
[1038]890       
[1302]891        dataset = self.registry.dataset(pointerDataset)
[1306]892       
[1302]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)
[1038]933   
[1302]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)
[1133]938   
[1302]939    def __repr__(self):
940        return '%s("%s", %s)' % (self.__class__.__name__, self.tag, repr(self.attributes))
[1038]941
[1302]942   
943class DatasetConfig(ConfigurationNode):
944    pass
[1038]945
[1302]946class Exportable(ConfigurationNode):
947    pass
[1038]948
[1302]949class Importable(ConfigurationNode):
[1038]950    pass
951
[1302]952class FilterPage(ConfigurationNode):
[1038]953    pass
954
[1302]955class FilterGroup(ConfigurationNode):
[1038]956    pass
957
[1302]958class FilterCollection(ConfigurationNode):
[1038]959    pass
960
[1302]961class FilterDescription(ConfigurationNode):
962
963    is_pointer = is_pointer
964    get_pointed = get_pointed
965   
966class AttributePage(ConfigurationNode):
[1038]967    pass
968
[1302]969class AttributeGroup(ConfigurationNode):
[1038]970    pass
971
[1302]972class AttributeCollection(ConfigurationNode):
973    pass
[1133]974
[1302]975class AttributeDescription(ConfigurationNode):
976   
977    is_pointer = is_pointer
978    get_pointed = get_pointed
979   
980class Option(ConfigurationNode):
981    pass
[1133]982
[1302]983class PushAction(ConfigurationNode):
984    pass
[1038]985
[1302]986class MainTable(ConfigurationNode):
[1038]987    pass
988
[1302]989class Key(ConfigurationNode):
[1038]990    pass
991
992   
993
994if __name__ == "__main__":
995    con = BioMartConnection("http://www.biomart.org/biomart/martservice")
[1302]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)
[1038]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()
[1302]1010    print attr
[1038]1011    filters = dataset.filters()
[1302]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()
[1045]1021    print query.run()
[1038]1022   
[1302]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.