2010-10-19

Blogger Glass に Google Data Python Library を組み込む #2

今回は、実際に Google Data Python Library (以下、GData ライブラリ) を組み込んでみる。feed/{core,parse,post}.py の代わりに GData ライブラリを使うわけだ。

試してみてわかったことだが、Blogger で作った公開ブログの記事を取得するだけであれば、GAE アプリで GData のための認証は必要ない。

準備

ライブラリのソースをコピーする

GData ライブラリのパッケージを展開すると、src 以下に atomgdata の 2 つのディレクトリが見つかる。これがライブラリ本体。GAE アプリで使う場合は、この 2 つを丸ごとアプリのディレクトリ(app.yaml が置かれているところ)にコピーする。こんな感じ(↓)。

[imac] mnbi% tar xzf ~/Downloads/gdata-2.0.12.tar.gz
[imac] mnbi% cd gdata-2.0.12/src
[imac] mnbi% ls
atom/ gdata/
[imac] mnbi% cp -R atom gdata ~/projects/bloggerglass/src

このようにコピーしたら、リクエストハンドラのコードなどから以下のように import できる。

(GData ライブラリの使い方)
import gdata.blogger.client
クライアントとサービス、どちらを使うべきか?

GData ライブラリの内容を見ていると、XxxxClientXxxxService という 2 系統のクラスが存在していることに気付く。ドキュメントやサンプルにも、Client系を使っているものとService系を使っているものの両方がある。

(Client 系)
atom.client.AtomPubClient
+--- gdata.client.GDClient
     +--- gdata.blogger.client.BloggerClient
(Service 系)
atom.service.AtomService
+--- gdata.service.GDataService
     +--- gdata.blogger.service.BloggerService

実装されている機能(メソッド)を見ると、どちらも同じ機能を実装しており、混在させて使う必要はない。XxxxClient を使うなら XxxxService は使わないし、逆もまたしかり。なぜ 2 系統、用意されているのだろう? 公式ドキュメントにはこの理由は書かれていない(少なくともわたしは見つけられなかった)。

BloggerClientBloggerService の機能比較 (抜粋)
BloggerClient BloggerService 機能
get_blogs GetBlogFeed ブログ一覧を取得する。
get_posts GetBlogPostFeed ブログの記事を取得する。
get_post_comments GetPostCommentFeed ブログの記事へのコメントを取得する

ライブラリのソース中のコメントなどから推測すると、どうやら XxxxService は古いライブラリのようだ。いずれ deprecated 扱いになるようで、これは GData のプロトコルのバージョン(1 と 2 がある)と関係があるらしい。

簡単に言えば、XxxxClient を使え、ってことだ。Blogger Data API の場合、gdata.blogger.client.BloggerClient を使うことになる。

リクエストハンドラの変更

一覧表示

基本は gdata.blogger.client.BloggerClient をインスタンス化して、そのメソッド get_posts を呼び出せば OK。Blogger Glass では記事の一覧を一定数ごとにページに分けるので、目的のページに合わせて start_index を調整し、クエリパラメータとして、このメソッドにわたす。

(src/main.py より)
import gdata.blogger.client
[...snip...]
PAGESIZE = 25

class MainHandler(webapp.RequestHandler):
    [...snip...]
    def fill_viewinfo(self, page):
        client = gdata.blogger.client.BloggerClient()
        settings = get_settings()
        start_index = page * PAGESIZE + 1
        q = gdata.blogger.client.Query(start_index=start_index,
                                      max_results=PAGESIZE)
        feed = client.get_posts(settings.get('blog_id'), query=q)
        if feed:
            [...snip...]
            self.viewinfo.content = feed.entry
            [...snip...]

ページごとの記事数をデフォルト値(25)から変更しない限り、max_results を指定する必要はない。ここでは start_index を計算するために記事数(PAGESIZE)を使っているので、そのまま max_results としてわたしている。こうしておけば、PAGESIZE の値を変えることで取得する記事数も変わるようになる。さらに一歩進めて、ページごとの記事数も settings から参照できるようにすれば、実行時に記事数を変えることもできる。ま、それはまた別の機会に。

クエリパラメータとして指定できる項目については、Protocol Reference - Google Data Protocol にまとめられている。実際に、コードでどう指定するかについては、gdata.client.Query のソースに書かれたコメントを参照すること。

ただし、Blogger API では qauthor はサポートされていない、とのこと(→ Blogger query parameters reference - Reference Guide - Blogger APIs)。また、updated-minupdated-max を指定する際には、orderby に "updated" を指定しなければならない、とのこと。

上記の get_posts の戻り値は gdata.blogger.data.BlogPostFeed のインスタンスになる。このインスタンスの entry 属性は gdata.blogger.data.BlogPost のインスタンスのリストになっていて、これが個々の記事の内容になっている。このため、self.viewinfo.contentgdata.blogger.data.BlogPost のリストを指すことになる。

(src/listview.html より)
<div id="content">
<ol>
{% for entry in view.content %}
  <li><a href="/post/?id={{ entry.get_post_id }}">{{ entry.title.text }}</a></li>
{% endfor %}
</ol>
</div>

よって、テンプレート中の記述で entryself.viewinfo.contentgdata.blogger.data.BlogPost のインスタンスを指している。つまり、entry.get_post_identry.title.textgdata.blogger.data.BlogPost の属性参照(とメソッド呼び出し)になっている。このような記述のしかたは、ある意味、バックエンドの仕組みがプレゼンテーション層に漏らしていることになるので、ちょっと気になる。とはいえ、データを別のオブジェクトに詰め直すのもムダだし、GData ライブラリはスタンダード的なものでもあるから、あえて独自のオブジェクトを作る必要はないと判断した。

内容表示

こちらの変更では少し苦労した。というのも、gdata.blogger.client.BloggerClient には、「Post ID」を指定して個別の記事の内容を取得するためのメソッドが用意されていなかったからだ。

そこで、gdata.blogger.client.BloggerClient を継承した BloggerPostClient を作り、そこに「Post ID」で記事の内容を取得するメソッド get_one_post を定義した。肝になるのは get_feed の呼び出しで desired_classgdata.blogger.data.BlogPost を指定している部分。GData プロトコルで「Post ID」を指定したときに返ってくるデータは、単純にブログ記事を要求したときとは違う形式になっている。GData ライブラリを使うときにはどういうデータが返ってくるのかに合わせて、どのデータクラスを使うかを変えなければならない。ちなみに、これらのデータクラスは gdata.blogger.data モジュールで定義されている。

(src/postview.py より; #1)
class BloggerPostClient(gdata.blogger.client.BloggerClient):
    def get_one_post(self, blog_id, post_id):
        """Get one post data specified with post_id,
        then return it as an instance of BlogPost.
        """
        if post_id != 0:
            url = (gdata.blogger.client.BLOG_POST_URL % blog_id) + ('/%s' % post_id)
            return self.get_feed(url, auth_token=None,
                                 desired_class=gdata.blogger.data.BlogPost,
                                 query=None)
        else:
            # when post_id was not specified, get the latest post.
            q = gdata.blogger.client.Query(max_results=1)
            feed = self.get_posts(blog_id, query=q)
            # Since 'get_posts' returns an instance of BlogPostFeed,
            # the 1st item (an instance of BlogPost) of BlogPostFeed.entry
            # must be return.
            if feed:
                return feed.entry[0]
            else:
                return None   

    GetOnePost = get_one_post

リクエストハンドラ(PostViewHandler)の方は、↑のクラスをインスタンス化して、get_one_post を呼び出すだけ。

(src/postview.py より; #2)
class PostViewHandler(webapp.RequestHandler):
    [...snip...]
    def fill_viewinfo(self, post_id):
        client = BloggerPostClient()
        settings = get_settings()
        entry = client.get_one_post(settings.get('blog_id'), post_id)
        if entry:
            self.viewinfo.title = entry.title.text
            self.viewinfo.permalink = entry.get_html_link().href
            self.viewinfo.content = entry.content.text

ここでは、テンプレートにわたす情報として gdata.blogger.data.BlogPost そのものではなく、PostViewInfo (のインスタンス)に「詰め直し」をしている。こうすることでテンプレート側は修正する必要がなくなったが、一方で、一覧表示の方との実装方針(上述)に齟齬をきたしているとも言える。ま、些細なことなんだがね。

おまけ: GAE アプリで favicon.ico を指定する

GAE の管理コンソールでは、エラーの発生したリクエスト(の URI)を見ることができる。Blogger Glass を稼動させて以来、ずっと /favicon.ico がエラーになっていた。これが気になったので、favicon を置くことにした。ちなみに、→ がそのデザイン(意匠)だ。

ただし、単に favicon.ico をアプリのトップディレクトリに置いてもダメ。ググってみたところ、GAE 公式ドキュメントの FAQ 「Google App Engine の一般的質問」の中に favicon.ico の置き方が書かれていた。

Blogger Glass では、favicon.icoimages ディレクトリに置き、app.yaml に以下の記述を追加した。

(src/app.yaml より)
- url: /favicon.ico
  static_files: images/favicon.ico
  upload: images/favicon.ico

関連リンク

関連記事

0 件のコメント:

コメントを投稿