[Checkins] SVN: Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/ refactoring of kirbifetch

Luciano Ramalho luciano at ramalho.org
Sun Aug 5 22:29:54 EDT 2007


Log message for revision 78613:
  refactoring of kirbifetch
  

Changed:
  D   Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/amazon_fetch.py
  D   Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/amazon_parse.py
  A   Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/fetch.py
  A   Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/interfaces.py
  A   Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/source_amazon.py
  A   Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/source_amazon_config.py
  D   Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/tests/comp-sci-10.txt
  U   Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/tests/dummy_server.py

-=-
Deleted: Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/amazon_fetch.py
===================================================================
--- Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/amazon_fetch.py	2007-08-05 22:44:43 UTC (rev 78612)
+++ Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/amazon_fetch.py	2007-08-06 02:29:53 UTC (rev 78613)
@@ -1,188 +0,0 @@
-#!/usr/bin/env python
-# encoding: utf-8
-
-from lxml import etree
-from twisted.internet import reactor
-from twisted.web import xmlrpc, client
-
-from urllib import quote
-from time import sleep
-import sys
-from StringIO import StringIO
-
-"""
-Structure of the AmazonECS XML response:
-
-ItemLookupResponse
-    OperationRequest
-        (...)
-    Items
-        Request
-            IsValid
-            ItemLookupRequest
-                ItemId
-                ResponseGroup
-            (Errors)
-                (Error)
-                    (Code)
-                    (Message)
-        (Item)
-            (ItemAttributes)
-                (Author)
-                (Creator Role=...)
-
-Notes:
-- Errors element occurs when ISBN is non-existent;
-        in that case, Code contains the string "AWS.InvalidParameterValue"
-- Author element is not always present
-- Author element may be duplicated with the same content,
-        except for whitespace; for example: ISBN=0141000511
-"""
-
-FIELD_MAP = [
-    # Book schema -> Amazon ECS element
-    ('title', 'Title'),
-    ('isbn13', 'EAN'),
-    ('edition', 'Edition'),
-    ('publisher', 'Publisher'),
-    ('issued', 'PublicationDate'),
-    ('subject', 'DeweyDecimalNumber'),
-    ]
-
-CREATOR_TAGS = ['Author', 'Creator']
-
-AMAZON_CODE_NO_MATCH = 'AWS.ECommerceService.NoExactMatches'
-
-# if True, processed XML files will be saved
-KEEP_FILES = True
-# directory where XML files will be saved (include trailing slash)
-SAVE_DIR = 'amazon_xml/'
-
-ITEMS_PER_REQUEST = 3  # maximum from Amazon is 10
-
-
-def test_parse(xml):
-    xml = file(sys.argv[1])
-    dic = parse(xml)
-    pprint(dic)
-
-class AmazonECS(object):
-
-    base_url = """http://ecs.amazonaws.com/onca/xml"""
-
-    def __init__(self, AWSAccessKeyId, AssociateTag=None):
-        self.base_params = { 'Service':'AWSECommerceService',
-                             'AWSAccessKeyId':AWSAccessKeyId, }
-        if AssociateTag:
-            self.base_params['AssociateTag'] = AssociateTag
-        self.xml = ''
-        self.http_response = {}
-
-    def buildURL(self, **kw):
-        query = []
-        kw.update(self.base_params)
-        for key, val in kw.items():
-            query.append('%s=%s' % (key,quote(val)))
-        return self.base_url + '?' + '&'.join(query)
-
-    def itemLookup(self,itemId,response='ItemAttributes'):
-        params = {  'Operation':'ItemLookup',
-                    'ItemId':itemId,
-                    'ResponseGroup':response
-                 }
-        return self.buildURL(**params)
-
-    def itemSearch(self,query,response='ItemAttributes'):
-        params = {  'Operation':'ItemSearch',
-                    'SearchIndex':'Books',
-                    'Power':query,
-                    'ResponseGroup':response
-                 }
-        return self.buildURL(**params)
-    
-    def isbnSearch(self, isbns):
-        query = 'isbn:' + ' or '.join(isbns)
-        return self.itemSearch(query)
-
-    def nsPath(self, path):
-        parts = path.split('/')
-        return '/'.join([self.ns+part for part in parts])
-    
-    def parse(self):
-        xml = StringIO(self.xml)
-        tree = etree.parse(xml)
-        root = tree.getroot()
-        # get the XML namespace from the root tag
-        self.ns = root.tag.split('}')[0] + '}'
-        request = root.find(self.nsPath('Items/Request'))
-        error_code = request.findtext(self.nsPath('Errors/Error/Code'))
-        if error_code is None:
-            book_list = []
-            for item in root.findall(self.nsPath('Items/Item/ItemAttributes')):
-                book_dic = {}
-                for field, tag in FIELD_MAP:
-                    elem = item.find(self.ns+tag)
-                    if elem is not None:
-                        book_dic[field] = elem.text
-                creators = []
-                for tag in CREATOR_TAGS:
-                    for elem in item.findall(self.ns+tag):
-                        if elem is None: continue
-                        role = elem.attrib.get('Role')
-                        if role:
-                            creator = '%s (%s)' % (elem.text, role)
-                        else:
-                            creator = elem.text
-                        creators.append(creator)
-                if creators:
-                    book_dic['creators'] = creators
-                book_list.append(book_dic)
-            return book_list
-    
-        elif error_code == AMAZON_CODE_NO_MATCH:
-            return []
-        else:
-            raise EnvironmentError, error_code
-        
-def getPending(pac):
-    return pac.callRemote('list_pending_isbns').addCallback(gotPending)
-
-def gotPending(isbns):
-    print 'get: ', ' '.join(isbns)
-    i = 0
-    if isbns:
-        # fetch at most 10 isbns per request, and one request per second
-        for i, start in enumerate(range(0,len(isbns),ITEMS_PER_REQUEST)):
-            end = start + ITEMS_PER_REQUEST
-            reactor.callLater(i, getAmazonXml, isbns[start:end])
-    reactor.callLater(i+1, getPending, pac)
-
-def getAmazonXml(isbns):
-    print 'fetch:', ' '.join(isbns)
-
-
-    
-def gotAmazonXml(xml):
-    
-    pac.callRemote('del_pending_isbns',isbns).addCallback(deletedPending)
-    if KEEP_FILES:
-        name = '_'.join(isbns)+'.xml'
-        out = file(SAVE_DIR+name,'w')
-        out.write(response.replace('><','>\n<'))
-        out.close()
-    return response
-
-def deletedPending(n):
-    print 'deleted:', n
-
-
-if __name__ == '__main__':
-    from amazon_config import ACCESS_KEY_ID, ASSOCIATE_TAG
-    
-    pac = xmlrpc.Proxy('http://localhost:8080/RPC2')
-
-
-    reactor.callLater(1, checkPending, pac)
-    print 'reactor start'
-    reactor.run()
-    print 'reactor stop'

Deleted: Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/amazon_parse.py
===================================================================
--- Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/amazon_parse.py	2007-08-05 22:44:43 UTC (rev 78612)
+++ Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/amazon_parse.py	2007-08-06 02:29:53 UTC (rev 78613)
@@ -1,103 +0,0 @@
-#!/usr/bin/env python
-# encoding: utf-8
-
-"""
-Structure of the AmazonECS XML response:
-
-ItemLookupResponse
-    OperationRequest
-        (...)
-    Items
-        Request
-            IsValid
-            ItemLookupRequest
-                ItemId
-                ResponseGroup
-            (Errors)
-                (Error)
-                    (Code)
-                    (Message)
-        (Item)
-            (ItemAttributes)
-                (Author)
-                (Creator Role=...)
-
-Notes:
-- Errors element occurs when ISBN is non-existent;
-        in that case, Code contains the string "AWS.InvalidParameterValue"
-- Author element is not always present
-- Author element may be duplicated with the same content,
-        except for whitespace; for example: ISBN=0141000511
-"""
-
-try:
-    from lxml import etree
-except ImportError:
-    try:
-        import cElementTree as etree
-    except ImportError:
-        try:
-            import elementtree.ElementTree as etree
-        except ImportError:
-            print "Failed to import ElementTree from any known place"
-
-FIELD_MAP = [
-    # Book schema -> Amazon ECS element
-    ('title', 'Title'),
-    ('isbn13', 'EAN'),
-    ('edition', 'Edition'),
-    ('publisher', 'Publisher'),
-    ('issued', 'PublicationDate'),
-    ]
-
-CREATOR_TAGS = ['Author', 'Creator']
-
-
-AMAZON_INVALID_PARAM = 'AWS.InvalidParameterValueXX'
-
-
-def nsPath(ns, path):
-    parts = path.split('/')
-    return '/'.join([ns+part for part in parts])
-
-def parse(xml):
-    tree = etree.parse(xml)
-    raiz = tree.getroot()
-    # get the XML namespace from the root tag
-    ns = raiz.tag.split('}')[0] + '}'
-    request = raiz.find(nsPath(ns,'Items/Request'))
-    error_code = request.findtext(nsPath(ns,'Errors/Error/Code'))
-    if error_code is None:
-        items = raiz.findall(nsPath(ns,'Items/Item'))
-        #TODO: treat multiple Item elements in Items
-        item = items[0].find(ns+'ItemAttributes')
-        book_dic = {}
-        for field, tag in FIELD_MAP:
-            elem = item.find(ns+tag)
-            if elem is not None:
-                book_dic[field] = elem.text
-        creators = []
-        for tag in CREATOR_TAGS:
-            for elem in item.findall(ns+tag):
-                if elem is None: continue
-                role = elem.attrib.get('Role')
-                if role:
-                    creator = '%s (%s)' % (elem.text, role)
-                else:
-                    creator = elem.text
-                creators.append(creator)
-        if creators:
-            book_dic['creators'] = creators
-        return book_dic
-
-    elif error_code == AMAZON_INVALID_PARAM:
-        return None
-    else:
-        raise LookupError, error_code
-
-if __name__ == '__main__':
-    import sys
-    from pprint import pprint
-    xml = file(sys.argv[1])
-    dic = parse(xml)
-    pprint(dic)

Copied: Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/fetch.py (from rev 78596, Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/amazon_fetch.py)
===================================================================
--- Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/fetch.py	                        (rev 0)
+++ Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/fetch.py	2007-08-06 02:29:53 UTC (rev 78613)
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+from lxml import etree
+from twisted.internet import reactor
+from twisted.web import xmlrpc, client
+from os import path
+
+import source_amazon as amazon
+
+from pprint import pprint
+
+KEEP_FILES = True
+# directory where XML files will be saved (include trailing slash)
+
+POLL_INTERVAL = 1 # minimum seconds to wait between calls to fetch.poll
+
+class Fetch(object):
+    
+    def __init__(self, xmlrpc_url, poll, callback, source):
+        self.pollServer = xmlrpc.Proxy(xmlrpc_url)
+        self.pollMethod = poll
+        self.callback = callback
+        self.source = source
+        
+    def poll(self):
+        deferred = self.pollServer.callRemote(self.pollMethod)
+        deferred.addCallback(self.polled).addErrback(self.pollError)
+    
+    def polled(self, isbns):
+        print 'polled: ', ' '.join(isbns)
+        i = 0
+        if isbns:
+            # fetch max_ids_per_request, and one request per second
+            for i, start in enumerate(range(0,len(isbns),
+                                            self.source.max_ids_per_request)):
+                end = start + self.source.max_ids_per_request
+                reactor.callLater(i, self.downloadItemsPage, isbns[start:end])
+        reactor.callLater(i+POLL_INTERVAL, self.poll)
+            
+    def pollError(self, error):
+        print 'Error in deferred poll call:', error
+        # if there was an error, wait a bit longer to try again
+        reactor.callLater(POLL_INTERVAL*4, self.poll)
+            
+    def downloadItemsPage(self, isbns):
+        url = self.source.buildMultipleBookDetailsURL(isbns)
+        deferred = client.getPage(url)
+        deferred.addCallback(self.downloadedItemsPage, isbns)
+        deferred.addErrback(self.downloadError, url)
+          
+    def downloadedItemsPage(self, xml, isbns):
+        book_list = self.source.parseMultipleBookDetails(xml)
+        delay = 1
+        for book in book_list:
+            url = book.get('image_url')
+            if url:
+                filename = book.get('isbn13',book['source_item_id'])
+                filename += '.' + url.split('.')[-1]
+                deferred = client.getPage(url)
+                deferred.addCallback(self.downloadedImage, filename)
+                deferred.addErrback(self.downloadError, url)
+                
+        if KEEP_FILES:
+            filename = '_'.join(isbns)+'.xml'
+            out = file(path.join(self.source.name,filename), 'w')
+            out.write(xml.replace('><','>\n<'))
+            out.close()
+        pprint(book_list)
+    
+    def downloadedImage(self, bytes, filename):
+        # XXX: find a proper way to calculate the static image dir
+        dest = '../../..'
+        dest = path.join(dest,'src','kirbi','static','covers','large'
+                            ,filename)
+        print 'saving: ', dest
+        out = file(dest, 'wb')
+        out.write(bytes)
+        out.close()
+
+    def downloadError(self, error, url):
+        print 'Error in deferred download (url=%s): %s' % (url, error)
+
+    def deletedPending(n):
+        print 'deleted:', n
+
+
+if __name__ == '__main__':
+    xmlrpc_url = 'http://localhost:8080/RPC2'
+    poll_method = 'dump_pending_isbns'
+    callback = 'add_book'
+    fetcher = Fetch(xmlrpc_url, poll_method, callback, amazon.Source())
+    reactor.callLater(0, fetcher.poll)
+    print 'reactor start'
+    reactor.run()
+    print 'reactor stop'
+

Added: Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/interfaces.py
===================================================================
--- Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/interfaces.py	                        (rev 0)
+++ Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/interfaces.py	2007-08-06 02:29:53 UTC (rev 78613)
@@ -0,0 +1,43 @@
+from zope.interface import interface
+from zope.schema import TextTline, Int, ASCII
+
+# XXX This interface is currently not used.
+# It's a draft for future componentization of kirbifetch
+
+class IMetadataSource(Interface):
+    """An online book metadata source."""
+    
+    name = DottedName(
+            title = u"Name to identify source in metadata records",
+            required = 1,
+        )
+    
+    max_ids_per_request = Int(
+            title = u"Maximum number of ids per detail request",
+            default = 1,
+        )
+
+    def buildBbookSearchURL(query):
+            """Return the URL to be used to search for book ids."""
+
+    def buildBookDetailsURL(book_id):
+            """Return the URL to be used to get book details."""
+            
+    def buildMultipleBookDetailsURL(isbns):
+            """Return the URL to be used to get details for multiple books."""
+
+    def parseBookSearch(xml):
+        """Extract the book ids referenced in a page."""
+               
+    def parseBookDetails(xml):
+        """Extract book metadata from a book details page."""
+
+    def parseMultipleBookDetails(xml):        
+        """Extract book metadata from a response containing multiple records.
+           This is only implemented when max_ids_per_request > 1.
+           (Currently Amazon.com is the only source we know where this happens)
+        """
+    
+    
+
+

Added: Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/source_amazon.py
===================================================================
--- Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/source_amazon.py	                        (rev 0)
+++ Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/source_amazon.py	2007-08-06 02:29:53 UTC (rev 78613)
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+from lxml import etree
+from twisted.internet import reactor
+from twisted.web import xmlrpc, client
+
+from urllib import quote
+from time import sleep
+import sys
+from StringIO import StringIO
+
+from source_amazon_config import ACCESS_KEY_ID, ASSOCIATE_TAG
+
+"""
+Structure of the AmazonECS XML response:
+
+ItemLookupResponse
+    OperationRequest
+        (...)
+    Items
+        Request
+            IsValid
+            ItemLookupRequest
+                ItemId
+                ResponseGroup
+            (Errors)
+                (Error)
+                    (Code)
+                    (Message)
+        (Item)
+            (ItemAttributes)
+                (Author)
+                (Creator Role=...)
+
+Notes:
+- Errors element occurs when ISBN is non-existent;
+        in that case, Code contains the string "AWS.InvalidParameterValue"
+- Author element is not always present
+- Author element may be duplicated with the same content,
+        except for whitespace; for example: ISBN=0141000511
+"""
+
+FIELD_MAP = [
+    # Book schema -> Amazon ECS element path, relative to Item element
+    ('title', 'ItemAttributes/Title'),
+    ('isbn13', 'ItemAttributes/EAN'),
+    ('edition', 'ItemAttributes/Edition'),
+    ('publisher', 'ItemAttributes/Publisher'),
+    ('issued', 'ItemAttributes/PublicationDate'),
+    ('subject', 'ItemAttributes/DeweyDecimalNumber'),
+    ('image_url', 'LargeImage/URL'),
+    ('source_url', 'DetailPageURL'),
+    ('source_item_id', 'ASIN'),
+    ]
+
+CREATOR_TAGS = ['ItemAttributes/Author', 'ItemAttributes/Creator']
+
+AMAZON_CODE_NO_MATCH = 'AWS.ECommerceService.NoExactMatches'
+
+class Source(object):
+
+    name = 'amazon.com'
+    max_ids_per_request = 3
+
+
+    base_url = """http://ecs.amazonaws.com/onca/xml"""
+
+    def __init__(self):
+        self.base_params = { 'Service':'AWSECommerceService',
+                             'AWSAccessKeyId':ACCESS_KEY_ID,
+                             'AssociateTag': ASSOCIATE_TAG
+                           }
+        self.xml = ''
+        self.http_response = {}
+
+    def buildURL(self, **kw):
+        query = []
+        kw.update(self.base_params)
+        for key, val in kw.items():
+            query.append('%s=%s' % (key,quote(val)))
+        return self.base_url + '?' + '&'.join(query)
+
+    def buildItemLookupURL(self,itemId,response='ItemAttributes'):
+        params = {  'Operation':'ItemLookup',
+                    'ItemId':itemId,
+                    'ResponseGroup':response
+                 }
+        return self.buildURL(**params)
+
+    def buildItemSearchURL(self,query,response='ItemAttributes,Images'):
+        params = {  'Operation':'ItemSearch',
+                    'SearchIndex':'Books',
+                    'Power':query,
+                    'ResponseGroup':response
+                 }
+        return self.buildURL(**params)
+    
+    def buildMultipleBookDetailsURL(self, isbns):
+        query = 'isbn:' + ' or '.join(isbns)
+        return self.buildItemSearchURL(query)
+
+    def nsPath(self, *paths):
+        parts = []
+        for path in paths:
+            parts.extend(path.split('/'))
+        return '/'.join([self.ns+part for part in parts])
+    
+    def parseMultipleBookDetails(self, xml):
+        xml = StringIO(xml)
+        tree = etree.parse(xml)
+        root = tree.getroot()
+        # get the XML namespace from the root tag
+        self.ns = root.tag.split('}')[0] + '}'
+        request = root.find(self.nsPath('Items/Request'))
+        error_code = request.findtext(self.nsPath('Errors/Error/Code'))
+        if error_code is None:
+            book_list = []
+            for item in root.findall(self.nsPath('Items/Item')):
+                book_dic = {'source':self.name}
+                for field, tag in FIELD_MAP:
+                    elem = item.find(self.nsPath(tag))
+                    if elem is not None:
+                        book_dic[field] = elem.text
+                creators = []
+                for tag in CREATOR_TAGS:
+                    for elem in item.findall(self.nsPath(tag)):
+                        if elem is None: continue
+                        role = elem.attrib.get('Role')
+                        if role:
+                            creator = '%s (%s)' % (elem.text, role)
+                        else:
+                            creator = elem.text
+                        creators.append(creator)
+                if creators:
+                    book_dic['creators'] = creators
+                book_list.append(book_dic)
+            return book_list
+    
+        elif error_code == AMAZON_CODE_NO_MATCH:
+            return []
+        else:
+            raise EnvironmentError, error_code
+        

Added: Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/source_amazon_config.py
===================================================================
--- Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/source_amazon_config.py	                        (rev 0)
+++ Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/source_amazon_config.py	2007-08-06 02:29:53 UTC (rev 78613)
@@ -0,0 +1,9 @@
+ASSOCIATE_TAG = 'circulante-20'
+ACCESS_KEY_ID = '13W2MMDG65QJJK9GG402'
+
+#UK
+#ASSOCIATE_TAG = 'circulante-21'
+#DE
+#ASSOCIATE_TAG = 'circulante0f-21'
+#FR
+#ASSOCIATE_TAG = 'circulante07-21'

Deleted: Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/tests/comp-sci-10.txt
===================================================================
--- Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/tests/comp-sci-10.txt	2007-08-05 22:44:43 UTC (rev 78612)
+++ Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/tests/comp-sci-10.txt	2007-08-06 02:29:53 UTC (rev 78613)
@@ -1,11 +0,0 @@
-9780471311980
-9781558607743
-9780471135340
-9780471195726
-9780201400090
-9780471923046
-9780471936084
-9780471942436
-9780471624639
-9780471601753
-

Modified: Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/tests/dummy_server.py
===================================================================
--- Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/tests/dummy_server.py	2007-08-05 22:44:43 UTC (rev 78612)
+++ Sandbox/luciano/kirbi/kirbifetch/src/kirbifetch/tests/dummy_server.py	2007-08-06 02:29:53 UTC (rev 78613)
@@ -2,36 +2,71 @@
 
 import sys
 from SimpleXMLRPCServer import SimpleXMLRPCServer
+from pprint import pprint
 
 PORT = 8080
 
-def list_pending_isbns():
-    return pending
+class Server(object):
+    # pending will behave like an ordered set (no duplicates)
+    pending = []
 
-def del_pending_isbns(isbns):
-    deleted = 0
-    for isbn in isbns:
-        if isbn in pending:
-            pending.remove(isbn)
-            deleted += 1
-    return deleted
+    def __init__(self, pending = None):
+        if pending is not None:
+            self.pending = pending
 
+    def add_pending_isbns(self, isbns):
+        print 'add%s' % isbns
+        added = 0
+        for isbn in isbns:
+            if isbn not in self.pending:
+                self.pending.append(isbn)
+                added += 1
+        return added
+    
+    def del_pending_isbns(self, isbns):
+        print 'del%s' % isbns
+        deleted = 0
+        for isbn in isbns:
+            if isbn in self.pending:
+                self.pending.remove(isbn)
+                deleted += 1
+        return deleted
+    
+    def dump_pending_isbns(self, max=0):
+        print 'dump%s' % self.pending
+        if max == 0:
+            max = len(self.pending)
+        dump = self.pending[:max]
+        self.pending = self.pending[max:]
+        return dump
+    
+    def show_pending_isbns(self):
+        print 'list%s' % self.pending
+        return self.pending
+    
+    def add_book(self, book):
+        pprint(book)
+
+
 if __name__=='__main__':
-    if len(sys.argv) != 2:
-        print 'usage: %s <filename>' % sys.argv[0]
+    if len(sys.argv) not in [2,3]:
+        print 'usage: %s <filename> [<n>]' % sys.argv[0]
         print '  <filename> names a text file containing' 
         print '             ISBN-13 numbers, one per line'
+        print '  <n> (optional) max ISBN numbers loaded'
         sys.exit()
+        
+    pending = file(sys.argv[1]).read().split()    
+    if len(sys.argv) == 3:
+        pending = pending[:int(sys.argv[2])]
 
-    pending = file(sys.argv[1]).read().split()
+    srv = Server(pending)
 
-    server = SimpleXMLRPCServer(("localhost", PORT))
-    server.register_introspection_functions()
+    xmlrpc = SimpleXMLRPCServer(("localhost", PORT))
 
-    server.register_function(list_pending_isbns)
-    server.register_function(del_pending_isbns)
+    xmlrpc.register_instance(srv)
 
     print 'SimpleXMLRPCServer running on port %s...', PORT    
-    server.serve_forever()
+    xmlrpc.serve_forever()
 
 



More information about the Checkins mailing list