Python で Stack Stock Books API モジュール的なもの

読書管理に Stack Stock Books を利用しています。まあ読書管理してても、積まれてる本の数は全然減らないんですが…


で、なんか面白そうな本ないかなーって時に、ここのつぶやき(参加ユーザーの読書メモ的なもの)をザザーっと眺めたりするのが好きなんです。このつぶやき一覧を Stack Stock Books の API で取得できるので、bit.ly の機能限定 API モジュール に続いて、Stack Stock Books APIPython モジュールを書き散らかしてみました。


今のところの機能としては

  • つぶやき一覧取得(include_authors は未実装)
  • 利用者情報

だけ。


例によって simplejson を使ってます。


ssb.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import urllib
import urllib2
from datetime import datetime
import logging
import simplejson as json

#logging.basicConfig(level=logging.DEBUG)

class SSBError(Exception):
    """Base class for SSB errors
    """
    pass

class SSBRequestError(SSBError):
    """SSB API request error class
    """
    def __init__(self, code, msg):
        self.code = code
        self.msg = msg
    def __str__(self):
        return "SSB API request error: %s (code: %s)" % (self.msg, self.code)

class SSBResponseError(SSBError):
    """SSB API response error class
    """
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return "SSB API response error: %s" % self.msg

class Mumble(object):
    """つぶやきクラス
    """
    def __init__(self,
                 mumble_id=None,
                 body=None,
                 time=None,
                 user_id=None,
                 book_id=None,
                 book=None):
        self.mumble_id = mumble_id
        self.body = body
        self.time = time
        self.user_id = user_id
        self.book_id = book_id
        self.book = book

    @property
    def uri(self):
        return "http://stack.nayutaya.jp/book/%(isbn10)s/mumble/%(mumble_id)s" \
               % dict(isbn10=self.book.isbn10, mumble_id=self.mumble_id)

    def get_mumble_id(self):
        return self._mumble_id
    def set_mumble_id(self, val):
        self._mumble_id = val
    mumble_id = property(get_mumble_id, set_mumble_id,
                         doc="The unique id of this mumble.")

    def get_body(self):
        return self._body
    def set_body(self, val):
        self._body = val
    body = property(get_body, set_body,
                    doc="The text of this mumble.")

    def get_time(self):
        return self._time
    def set_time(self, val):
        try:
            sdate, stime = val.split()
            args = [int(arg) for arg in sdate.split("-") + stime.split(":")]
            self._time = datetime(*args)
        except:
            self._time = None
    time = property(get_time, set_time,
                    doc="The time this mumble was posted.")

    def get_user_id(self):
        return self._user_id
    def set_user_id(self, val):
        self._user_id = val
    user_id = property(get_user_id, set_user_id,
                       doc="The unique id of the user who posted this mumble.")

    def get_book_id(self):
        return self._book_id
    def set_book_id(self, val):
        self._book_id = val
    book_id = property(get_book_id, set_book_id,
                       doc="The unique id of the book mentioned in this mumble.")

    def get_book(self):
        return self._book
    def set_book(self, val):
        self._book = val
    book = property(get_book, set_book,
                    doc="The book object which mentioned in this mumble.")

    def as_dict(self):
        """Mumble インスタンスを辞書として返す
        """
        data = {}
        if self.mumble_id:
            data['mumble_id'] = self.mumble_id
        if self.body:
            data['body'] = self.body
        if self.time:
            data['time'] = self.time.strftime('%Y-%m-%d %H:%M:%S')
        if self.user_id:
            data['user_id'] = self.user_id
        if self.book_id:
            data['book_id'] = self.book_id
        if self.book:
            data['book'] = self.book.as_dict()
        return data

    def as_json_string(self):
        """Mumble インスタンスを JSON 文字列として返す
        """
        return json.dumps(self.as_dict(), sort_keys=True)

    def __str__(self):
        return self.as_json_string()

    @staticmethod
    def new_from_json_dict(data):
        if 'book' in data:
            book = Book.new_from_json_dict(data['book'])
        else:
            book = None
        return Mumble(mumble_id=data.get('mumble_id'),
                      body=data.get('body'),
                      time=data.get('time'),
                      user_id=data.get('user_id'),
                      book_id=data.get('book_id'),
                      book=book)

class Book(object):
    """書籍蔵書クラス"""
    def __init__(self,
                 book_id=None,
                 isbn10=None,
                 isbn13=None,
                 title=None,
                 release_date=None,
                 binding=None,
                 publisher=None,
                 uri=None,
                 image_uri=None):
        self.book_id = book_id
        self.isbn10 = isbn10
        self.isbn13 = isbn13
        self.title = title
        self.release_date = release_date
        self.binding = binding
        self.publisher = publisher
        self.uri = uri
        self.image_uri = image_uri

    def get_book_id(self):
        return self._book_id
    def set_book_id(self, val):
        self._book_id = val
    book_id = property(get_book_id, set_book_id,
                       doc="The unique id of this book.")

    def get_isbn10(self):
        return self._isbn10
    def set_isbn10(self, val):
        self._isbn10 = val
    isbn10 = property(get_isbn10, set_isbn10,
                      doc="The isbn 10 of this book.")
    
    def get_isbn13(self):
        return self._isbn13
    def set_isbn13(self, val):
        self._isbn13 = val
    isbn13 = property(get_isbn13, set_isbn13,
                      doc="The isbn 13 of this book.")
    
    def get_title(self):
        return self._title
    def set_title(self, val):
        self._title = val
    title = property(get_title, set_title,
                     doc="The title of this book.")

    def get_release_date(self):
        return self._release_date
    def set_release_date(self, val):
        """
        try:
            sdate, stime = val.split()
            args = [int(arg) for arg in sdate.split("-") + stime.split(":")]
            self._release_date = datetime(*args)
        except:
            self._release_date = None
        """
        self._release_date = val
    release_date = property(get_release_date, set_release_date,
                            doc="Date when this book was released.")

    def get_binding(self):
        return self._binding
    def set_binding(self, val):
        self._binding = val
    binding = property(get_binding, set_binding,
                       doc="The binding of this book.")

    def get_publisher(self):
        return self._publisher
    def set_publisher(self, val):
        self._publisher = val
    publisher = property(get_publisher, set_publisher,
                         doc="The publisher of this book.")

    def get_uri(self):
        return self._uri
    def set_uri(self, val):
        self._uri = val
    uri = property(get_uri, set_uri,
                   doc="The uri of this book.")

    def get_image_uri(self):
        return self._image_uri
    def set_image_uri(self, val):
        self._image_uri = val
    image_uri = property(get_image_uri, set_image_uri,
                         doc="The image uri of this book.")

    def as_dict(self):
        """Book インスタンスを辞書として返す
        """
        data = {}
        if self.book_id:
            data['book_id'] = self.book_id
        if self.isbn10:
            data['isbn10'] = self.isbn10
        if self.isbn13:
            data['isbn13'] = self.isbn13
        if self.title:
            data['title'] = self.title
        if self.release_date:
            data['release_date'] = self.release_date
        if self.binding:
            data['binding'] = self.binding
        if self.publisher:
            data['publisher'] = self.publisher
        if self.uri:
            data['uri'] = self.uri
        if self.image_uri:
            data['image_uri'] = self.image_uri
        return data

    def as_json_string(self):
        """Book インスタンスを JSON 文字列として返す
        """
        return json.dumps(self.as_dict(), sort_keys=True)

    def __str__(self):
        return self.as_json_string()

    def __eq__(self, other):
        return str(self) == str(other)

    def __ne__(self, other):
        return not self.__eq__(other)

    @staticmethod
    def new_from_json_dict(data):
        """
        """
        return Book(book_id=data.get('book_id'),
                    isbn10=data.get('isbn10'),
                    isbn13=data.get('isbn13'),
                    title=data.get('title'),
                    release_date=data.get('release_date'),
                    binding=data.get('binding'),
                    publisher=data.get('publisher'),
                    uri=data.get('uri'),
                    image_uri=data.get('image_uri'))

class User(object):
    """ユーザークラス"""
    def __init__(self,
                 user_id=None,
                 name=None,
                 nick=None,
                 uri=None,
                 icon_uri=None):
        self.user_id = user_id
        self.name = name
        self.nick = nick
        self.uri = uri
        self.icon_uri = icon_uri

    def get_user_id(self):
        return self._user_id
    def set_user_id(self, val):
        self._user_id = val
    user_id = property(get_user_id, set_user_id,
                       doc="The unique id of this user.")

    def get_name(self):
        return self._name
    def set_name(self, val):
        self._name = val
    name = property(get_name, set_name,
                       doc="The name of this user.")

    def get_nick(self):
        return self._nick
    def set_nick(self, val):
        self._nick = val
    nick = property(get_nick, set_nick,
                       doc="The nickname of this user.")

    def get_uri(self):
        return self._uri
    def set_uri(self, val):
        self._uri = val
    uri = property(get_uri, set_uri,
                       doc="The uri of this user.")

    def get_icon_uri(self):
        return self._icon_uri
    def set_icon_uri(self, val):
        self._icon_uri = val
    icon_uri = property(get_icon_uri, set_icon_uri,
                       doc="The icon uri of this user.")
    
    def as_dict(self):
        """User インスタンスを辞書として返す
        """
        data = {}
        if self.user_id:
            data['user_id'] = self.user_id
        if self.name:
            data['name'] = self.name
        if self.nick:
            data['nick'] = self.nick
        if self.uri:
            data['uri'] = self.uri
        if self.icon_uri:
            data['icon_uri'] = self.icon_uri
        return data

    def as_json_string(self):
        """User インスタンスを JSON 文字列として返す
        """
        return json.dumps(self.as_dict(), sort_keys=True)

    def __str__(self):
        return self.as_json_string()

    def __eq__(self, other):
        return str(self) == str(other)

    def __nq__(self, other):
        return not self.__eq__(other)

    @staticmethod
    def new_from_json_dict(data):
        return User(user_id=data.get('user_id'),
                    name=data.get('name'),
                    nick=data.get('nick'),
                    uri=data.get('uri'),
                    icon_uri=data.get('icon_uri'))


class Api(object):
    """Stack Stock Books API クラス
    """
    USER_ID_TYPE_ID = "id"
    USER_ID_TYPE_NAME = "name"
    
    ORDER_ASC = "mumble_id_asc"
    ORDER_DESC = "mumble_id_desc"
    
    def __init__(self):
        self.base_url = "http://stack.nayutaya.jp/api"

    def get_mumbles(self, include_books=True, include_authors=False,
                    order=ORDER_DESC, page=1):
        """つぶやき一覧取得
        TODO: include_authors は現在未実装
        """
        api_name = 'get_mumbles'

        query_dict = {}
        if include_books:
            query_dict['include_books'] = 'true'
        if order != self.ORDER_DESC:
            query_dict['order'] = self.ORDER_ASC
        if page > 1:
            query_dict['page'] = page
        query = urllib.urlencode(query_dict)
 
        url = "%s/mumbles.json?%s" % (self.base_url, query)
        logging.debug("[%s]: url: %s", api_name, url)

        data = self._get_data(api_name, url)
        return [Mumble.new_from_json_dict(m) for m in data['response']['mumbles']]

    def get_user(self, user_id, user_id_type=USER_ID_TYPE_ID):
        """利用者情報取得
        """
        api_name = 'get_user'
        url = "%s/user/%s/%s.json" % (self.base_url, user_id_type, user_id)
        logging.debug("[%s]: url: %s", api_name, url)

        data = self._get_data(api_name, url)
        logging.debug("[%s]: %s", api_name, data['response']['user'])

        return User.new_from_json_dict(data['response']['user'])

    def _get_data(self, api_name, url):
        """指定 API を実行して結果を返す
        """
        data = {}
        f = urllib2.urlopen(url)
        try:
            if not (200 <= f.code < 300):
                e = SSBRequestError(f.code, f.msg)
                logging.error("[%s] SSBRequestError: %s", api_name, e)
                raise e
            data = json.load(f)
        finally:
            f.close()
        self._check_api_success(api_name, data)
        return data

    def _check_api_success(self, api_name, data):
        """API 処理が失敗したら例外を発生させる
        """
        if data.get('success', True):
            return
        e = SSBResponseError(data.get('success', True))
        raise e


使い方は、

>>> import ssb
>>> api = ssb.Api()
>>> m = api.get_mumbles()[0]
>>> print m.book.title
Python ポケットリファレンス (Pocket Reference)
>>> print m.body
結構いい本
>>> u = api.get_user(m.user_id)
>>> print u.nick
namaco

こんな感じです。


http://bitbucket.org/ae35/python-ssb/