[BioPython] Fwd: NCBI Abuse Activity with BioPython

Andrew Dalke dalke at dalkescientific.com
Wed Jun 25 21:15:50 EDT 2008


Hi Chris,

   I'm no longer part of the Biopython dev team, but I read at least  
the subject line on the mailing list.

   I wrote the Biopython EUtils package around December 2002 and  
according to the CVS logs it was added to Biopython in June 2003, so  
more then 5 years ago.  Looking at the commit logs there haven't been  
any change to the relevant code since 2004, and that was a minor patch.

   I thought I put a rate limiter into the code, but looking at it  
now I see I didn't.  The documentation clearly states that users must  
follow NCBI's recommendations, but who actually reads documentation?

>> *  Send E-utilities requests to http://eutils.ncbi.nlm.nih.gov
>> <http://eutils.ncbi.nlm.nih.gov/> , not the standard NCBI Web  
>> address.

That change was announced on May 21, 2003, and most likely no one on  
the Biopython dev group tracks the EUtils mailing list.  It was also  
after I wrote the code, but to be fair I was subscribed to the  
utilities list at the time and should have caught the change.

I think the correct fix is to this code in ThinClient.py:

     def __init__(self,
                  opener = None,
                  tool = TOOL,
                  email = EMAIL,
                  baseurl = "http://www.ncbi.nlm.nih.gov/entrez/ 
eutils/"):

Change the baseurl to "http://eutils.ncbi.nlm.nih.gov/entrez/ 
eutils/".  I have not tested this.


>> *  Make no more than one request every 3 seconds.

There's a couple of points here.  The quickest and most direct way to  
force/fix the code is to change the "def _get()" in ThinClient.py .   
The current code is

     def _get(self, program, query):
         """Internal function: send the query string to the program  
as GET"""
         # NOTE: epost uses a different interface

         q = self._fixup_query(query)
         url = self.baseurl + program + "?" + q
         if DUMP_URL:
             print "Opening with GET:", url
         if DUMP_RESULT:
             print " ================== Results ============= "
             s = self.opener.open(url).read()
             print s
             print " ================== Finished ============ "
             return cStringIO.StringIO(s)
         return self.opener.open(url)

Here's one possible fix: add the following two lines to module scope:

import time
_prev_time = 0


and insert four lines in the _get function.

     def _get(self, program, query):
         """Internal function: send the query string to the program  
as GET"""
         # NOTE: epost uses a different interface
         global _prev_time
         q = self._fixup_query(query)
         url = self.baseurl + program + "?" + q
         if DUMP_URL:
             print "Opening with GET:", url

	# Follow NCBI's 3 second restriction
         if time.time() - _prev_time < 3:
             time.sleep(time.time()-_prev_time)
         _prev_time = time.time()

         if DUMP_RESULT:
             print " ================== Results ============= "
             s = self.opener.open(url).read()
             print s
             print " ================== Finished ============ "
             return cStringIO.StringIO(s)
         return self.opener.open(url)


(I recall that I had something like that, and it made my unit tests -  
which I did during the off hours - interminable.)


When I wrote this module I think I assumed that whoever would use the  
library would use the code correctly.  Using it correctly means a few  
things:
   - obey the restrictions set by NCBI
   - change the 'tool' and 'email' settings, so NCBI complains the  
right person.
      (The default is to say 'EUtils_Python_client' and 'biopython- 
dev at biopython.org')

This isn't happening.  The patch above force-fixes the first.  Should  
Biopython do a better job of the second?  It's not easy to figure out  
the correct email.  I couldn't then and can't now think of a better  
solution.  Perhaps use the result of getpass.getuser()?  But that  
doesn't get the rest of the domain for a proper email.  Though NCBI  
should be able to guess the site from the IP address.

The reason I made this assumption is that I meant EUtils to be used  
by contentious developers.  I've since learned that that's seldom the  
case, and because it was imported into Biopython it's been exposed to  
a wider audience.


>> Also, there is the problem of huge searches in order to build local
>> databases. With you package it seems that if one were so inclined you
>> would send a search for all human sequences (over 10,000,000  
>> sequences)
>> and you program would then retrieve these one ID at a time.  
>> Regardless
>> of the fact that this is an extreme example, we would much prefer if
>> your program could webenv from the Esearch  and  use the search  
>> history
>> and webenv to retrieve sets of sequences at 200 - 200 at a time.

It does exactly that.  There's an entire interface for handling  
search history - and it took some non-trivial work and questions to  
NCBI to get things working right.  Rather, there are two layers.  One  
is for the low-level protocol ("ThinClient") that EUtils offers, and  
another wraps around the history mechanism ("HistoryClient").

 >>> from Bio import EUtils
 >>> from Bio.EUtils import HistoryClient
 >>> client = HistoryClient.HistoryClient()
 >>> result = client.search("polio AND picornavirus")
 >>> len(result)
3437
 >>> f = result.efetch()
 >>> print f.read(1000)
<?xml version="1.0"?>
<!DOCTYPE PubmedArticleSet PUBLIC "-//NLM//DTD PubMedArticle, 1st  
January 2008//EN" "http://www.ncbi.nlm.nih.gov/entrez/query/DTD/ 
pubmed_080101.dtd">
<PubmedArticleSet>
<PubmedArticle>
     <MedlineCitation Owner="NLM" Status="In-Process">
         <PMID>18540199</PMID>
         <DateCreated>
             <Year>2008</Year>
             <Month>06</Month>
             <Day>10</Day>
         </DateCreated>
         <Article PubModel="Print">
             <Journal>
                 <ISSN IssnType="Print">0041-3771</ISSN>
                 <JournalIssue CitedMedium="Print">
                     <Volume>50</Volume>
                     <Issue>2</Issue>
                     <PubDate>
                         <Year>2008</Year>
                     </PubDate>
                 </JournalIssue>
                 <Title>Tsitologiia</Title>
                 <ISOAbbreviation>Tsitologiia</ISOAbbreviation>
             </Journal>
             <ArticleTitle>[The enter of viruses family  
Picornaviridae in

and there's a way to populate the history with a list of records,  
then fetch those records in a block:

 >>> result = client.from_dbids(EUtils.DBIds("pubmed",  
["100","200","300","400","500"]))
 >>> f = result.efetch("text", "brief")
 >>> print f.read()

1: Jolly RD et al. Bovine mannosidosis--a model ...[PMID: 100]

2: El Halawani ME et al. The relative importance of mo...[PMID: 200]

3: Amdur MA. Alcohol-related problems in a...[PMID: 300]

4: Regitz G et al. Trypsin-sensitive photosynthe...[PMID: 400]

5: Nourse ES. The regional workshops on pri...[PMID: 500]



If I had to guess, likely more people find the ThinClient code easier  
to understand, because the NCBI interface has a simple way to get the  
result for a single record, without using the history interface.  The  
NCBI interface doesn't guide people to the right way to use it  
effectively.

I started working on an update to EUtils which improved the API to  
include a few helper functions, like "EUtils.search()" instead of  
having to create a HistoryClient.  That might help guide people to  
using it better.  I wrote up something about it a few years ago:
   http://www.dalkescientific.com/writings/diary/archive/2005/09/30/ 
using_eutils.html

But a problem in completing that is that I never got any sort of  
funding or user feedback on how people were using the software, and  
as I moved over to chemistry it became lower and lower on my list.   
That's still the problem with me working on this again.

I don't know about this next point, but there might also be a lack of  
documentation on how to use the Biopython interface effectively?  The  
NCBI documentation isn't mean for non-programmers (it's more of a  
bytes-on-the-wire document) so perhaps people are pattern matching on  
what looks right and going with what works, vs. what works well.   
Then because there was no 3 second limit, they had no incentive to  
find a better/faster solution.

				Andrew
				dalke at dalkescientific.com




More information about the BioPython mailing list