├── tests ├── __init__.py ├── basetest.py └── shelves.py ├── README.rst ├── .gitignore ├── LICENSE ├── setup.py ├── examples └── worldcat │ └── worldcat-search.py └── goodreads ├── __init__.py └── parser.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | A Python client for accessing the `GoodReads API `_ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/config.py 2 | *.py[co] 3 | 4 | # Packages 5 | *.egg 6 | *.egg-info 7 | dist 8 | build 9 | eggs 10 | parts 11 | bin 12 | var 13 | sdist 14 | develop-eggs 15 | .installed.cfg 16 | 17 | # Installer logs 18 | pip-log.txt 19 | 20 | # Unit test / coverage reports 21 | .coverage 22 | .tox 23 | 24 | #Translations 25 | *.mo 26 | 27 | #Mr Developer 28 | .mr.developer.cfg 29 | 30 | -------------------------------------------------------------------------------- /tests/basetest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from goodreads import GoodReadsClient 4 | import config 5 | 6 | 7 | class BaseTestCase(unittest.TestCase): 8 | 9 | def setUp(self): 10 | self.assertIsNotNone(config.GOODREADS_API_KEY) 11 | self.assertIsNotNone(config.GOODREADS_API_SECRET) 12 | 13 | self.client = GoodReadsClient( 14 | config.GOODREADS_API_KEY, 15 | config.GOODREADS_API_SECRET) 16 | 17 | def tearDown(self): 18 | self.client = None 19 | -------------------------------------------------------------------------------- /tests/shelves.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import basetest 4 | import config 5 | 6 | 7 | class TestShelfs(basetest.BaseTestCase): 8 | 9 | def test_user_shelves(self): 10 | shelfs = self.client.user_shelves(config.TEST_USER_ID) 11 | self.assertIsNotNone(shelfs) 12 | 13 | def test_get_shelf(self): 14 | books = self.client.get_shelf(config.TEST_USER_ID, 15 | config.TEST_SHELF_NAME) 16 | self.assertIsNotNone(books) 17 | 18 | 19 | if __name__ == '__main__': 20 | unittest.main() 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009-2010 Devin Sevilla. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | use this file except in compliance with the License. You may obtain a copy of 5 | the License at http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS,WITHOUT 9 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | License for the specific language governing permissions and limitations under 11 | the License. -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | 4 | with open('README.rst') as fp: 5 | long_description = fp.read() 6 | 7 | 8 | setup( 9 | name='Goodreads API Client', 10 | version='0.1.2', 11 | 12 | description='A simple Python client library for the Goodreads API', 13 | long_description=long_description, 14 | 15 | author='Devin Sevilla', 16 | author_email='dasevilla@gmail.com', 17 | 18 | url='https://github.com/dasevilla/goodreads-python', 19 | download_url='https://github.com/dasevilla/goodreads-python/tarball/master', 20 | 21 | classifiers=[ 22 | 'Development Status :: 3 - Alpha', 23 | 'License :: OSI Approved :: MIT License', 24 | 'Programming Language :: Python :: 2.7', 25 | ], 26 | 27 | install_requires=[ 28 | ], 29 | 30 | packages=find_packages(), 31 | 32 | ) 33 | -------------------------------------------------------------------------------- /examples/worldcat/worldcat-search.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from worldcat.request.xid import xISBNRequest 4 | 5 | from goodreads import GoodReadsClient 6 | 7 | 8 | def get_books(user_id, shelf_name): 9 | key = "GOODREADS_API_KEY" 10 | secret = "GOODREADS_API_SECRET" 11 | 12 | goodreads_client = GoodReadsClient(key, secret) 13 | 14 | return goodreads_client.get_shelf(user_id, shelf_name) 15 | 16 | 17 | def book_search(isbn): 18 | o = xISBNRequest(rec_num=isbn, method='getMetadata') 19 | o.validate() 20 | r = o.get_response() 21 | 22 | return r.data["list"][0] 23 | 24 | 25 | def main(): 26 | goodreads_user_id = "YOUR_USER_ID" 27 | goodreads_shelf = "to-read" 28 | for book in get_books(goodreads_user_id, goodreads_shelf): 29 | isbn = book["isbn"] 30 | title = book["title"] 31 | goodreads_url = book["link"] 32 | 33 | result = book_search(isbn) 34 | worldcat_url = result["url"][0] 35 | 36 | print "%s\n\tISBN:%s\n\t%s\n\t%s" % (title, isbn, goodreads_url, 37 | worldcat_url) 38 | 39 | 40 | if __name__ == "__main__": 41 | logging.basicConfig(level=logging.DEBUG) 42 | main() 43 | -------------------------------------------------------------------------------- /goodreads/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import urllib 3 | 4 | from goodreads.parser import GoodReadsParser 5 | 6 | 7 | class GoodReadsClient(object): 8 | 9 | BASE_URL = "http://www.goodreads.com/" 10 | DEFAULT_PAGE_SIZE = 20 11 | 12 | def __init__(self, key, secret): 13 | self.key = key 14 | self.secret = secret 15 | self.parser = GoodReadsParser() 16 | 17 | def unauthorized_request(self, base_url, query_params): 18 | if "key" not in query_params: 19 | query_params["key"] = self.key 20 | if "per_page" not in query_params: 21 | query_params["per_page"] = self.DEFAULT_PAGE_SIZE 22 | 23 | params = [] 24 | for k, v in query_params.iteritems(): 25 | if v is not None: 26 | params.append("%s=%s" % (k, v)) 27 | url = "%s?%s" % (base_url, "&".join(params)) 28 | logging.info("Making request to %s" % url) 29 | url_handler = urllib.urlopen(url) 30 | return url_handler 31 | 32 | def parse_result(self, url_handler): 33 | return self.parser.parse_result(url_handler) 34 | 35 | def user_shelves(self, user_id): 36 | url = "%sshelf/list.xml" % self.BASE_URL 37 | query_params = { 38 | "user_id": user_id, 39 | } 40 | 41 | url_handler = self.unauthorized_request(url, query_params) 42 | return self.parser.parse_shelfs(url_handler) 43 | 44 | def get_shelf(self, user_id, shelf_name): 45 | url = "%sreview/list.xml" % self.BASE_URL 46 | query_params = { 47 | "id": user_id, 48 | "shelf": shelf_name, 49 | "v": 2, 50 | } 51 | 52 | url_handler = self.unauthorized_request(url, query_params) 53 | return self.parser.parse_books(url_handler) 54 | -------------------------------------------------------------------------------- /goodreads/parser.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from xml.dom import minidom, Node 3 | from xml.parsers.expat import ExpatError, ErrorString 4 | 5 | 6 | class GoodReadsParser(object): 7 | 8 | def parse_result(self, url_handler): 9 | try: 10 | goodreads_dom = minidom.parse(url_handler) 11 | return goodreads_dom 12 | except ExpatError, e: 13 | logging.error("XML Error: %s line: %d offset: %d" % ( 14 | ErrorString(e.code), e.lineno, e.offset)) 15 | return None 16 | 17 | def get_text(self, element): 18 | value = "" 19 | 20 | for text in element.childNodes: 21 | try: 22 | value += text.data 23 | except AttributeError: 24 | logging.error("get_text() error: " + text.toxml()) 25 | raise 26 | 27 | if value: 28 | return value.strip() 29 | else: 30 | return None 31 | 32 | def parse_books(self, url_handler): 33 | goodreads_dom = self.parse_result(url_handler) 34 | books = [] 35 | for book_element in goodreads_dom.getElementsByTagName("book"): 36 | book = self.handle_book(book_element) 37 | if book: 38 | books.append(book) 39 | return books 40 | 41 | def handle_book(self, book_element): 42 | book = {} 43 | for child_node in book_element.childNodes: 44 | value = "" 45 | 46 | if child_node.nodeType == Node.TEXT_NODE: 47 | continue 48 | elif child_node.nodeName == "authors": 49 | value = self.handle_authors(child_node) 50 | elif child_node.nodeName == "shelves": 51 | logging.info("found shelves") 52 | continue 53 | else: 54 | value = self.get_text(child_node) 55 | 56 | book[child_node.nodeName] = value 57 | return book 58 | 59 | def parse_shelfs(self, url_handler): 60 | goodreads_dom = self.parse_result(url_handler) 61 | shelfs = [] 62 | for shelf_element in goodreads_dom.getElementsByTagName("user_shelf"): 63 | shelf = self.handle_shelf(shelf_element) 64 | if shelf: 65 | shelfs.append(shelf) 66 | return shelfs 67 | 68 | def handle_shelf(self, shelf_element): 69 | shelf = {} 70 | for child_node in shelf_element.childNodes: 71 | value = "" 72 | 73 | if child_node.nodeType == Node.TEXT_NODE: 74 | continue 75 | else: 76 | value = self.get_text(child_node) 77 | 78 | shelf[child_node.nodeName] = value 79 | return shelf 80 | 81 | def handle_authors(self, authors_element): 82 | authors = [] 83 | for child_node in authors_element.childNodes: 84 | author = self.handle_author(child_node) 85 | if author: 86 | authors.append(author) 87 | return authors 88 | 89 | def handle_author(self, author_element): 90 | author = {} 91 | for child_node in author_element.childNodes: 92 | value = "" 93 | 94 | if child_node.nodeType == Node.TEXT_NODE: 95 | continue 96 | else: 97 | value = self.get_text(child_node) 98 | 99 | author[child_node.nodeName] = value 100 | if author: 101 | return author 102 | else: 103 | return None 104 | --------------------------------------------------------------------------------