Adventures in lightweight service composition

Three years after I started the LibraryLookup project, people are still regularly discovering and enjoying the ability to automatically broker a connection between Amazon (or another book site) and their local libraries. In a screencast entitled Content, services, and the yin-yang of intermediation I showed a more advanced version of the conventional bookmarklet: a Greasemonkey script that modifies an Amazon page to include a notice about the book's availability in my local library. The screencast ends with a demonstration of another kind of connection brokering. If a book isn't available at the library, I add it to my Amazon wishlist. Then, when it becomes available at the library, it shows up in a special RSS feed that watches my Amazon wishlist.

It's a great example of lightweight service composition. Recently I was reminded that I'd never published the code, so I've included it below. This small Python script orchestrates three different services. First, it queries the Amazon wishlist by way of Amazon's API. Second, it queries the OCLC's xISBN service [1, 2] to convert the single ISBN into a cluster of related ISBNs. Third, it queries my library's OPAC system for each of those related ISBNs.

The use of xISBN is actually gratuitous here, because I could also just query the OPAC for the book's author and title. And in fact, the link that's produced for the RSS file is an example of a title query. But I wanted to demonstrate the use of xISBN. It's important in situations where the author/title information is not readily available.

Other things to note about this example:

This is obviously way beyond the reach of a normal library patron. But forward-thinking libraries could pretty easily wrap all this up into another web-based service and make it available to their patrons. Or, a library superpatron could do that on the library's behalf.


import amazon, re, urllib
  
amazon.setLicense(YOUR_AMAZON_API_KEY) 
wishes        = amazon.searchByWishlist(YOUR_AMAZON_WISHLIST_ID)
isbnRE        = '\\d{7,9}[\\d|X]'
libISBNQuery  = 'http://ksclib.keene.edu/search/i=%s'
libTitleQuery = 'http://ksclib.keene.edu/search/t?SEARCH=%s'
libAvailRE    = '(DUE\\s+\\d{2,2}\\-\\d{2,2}\\-\\d{2,2}|AVAILABLE)'
xisbnQuery    = 'http://labs.oclc.org/xisbn/%s'
 
def rss(items):
  return """<rss version="0.91">
<channel>
<title> LibraryLookup reminders </title>
<link> http://www.amazon.com </link>
<description>
Remind me when books on my Amazon wishlist become 
available at the library
</description>
%s
</channel>
</rss> """ % items
   
def availability (isbn):
  url = libISBNQuery % isbn
  page = urllib.urlopen(url).read()
  m = re.search(libAvailRE, page)
  if m is not None:
    return m.group(0)
  else:
    return None
  
def xisbn(isbn):
  url = xisbnQuery % isbn
  result = urllib.urlopen(url).read()
  isbns = re.findall(isbnRE, result)
  return isbns
  
items = ''
   
for wish in wishes:
  isbn = wish.Asin
  if re.match(isbnRE,isbn) is None:
    continue
  avail = None
  for relatedISBN in xisbn(isbn):
    avail = availability(relatedISBN)
    if avail is not None:
      break
  if avail is not None:
    items += """
<item>
<title>%s</title>
<link>%s</link>
<description>%s</description>
</item>  """ %  ( wish.ProductName, 
  libTitleQuery % urllib.quote(wish.ProductName), avail )
      
print rss ( items )

Former URL: http://weblog.infoworld.com/udell/2006/01/26.html#a1377