今回は、実際に Google Data Python Library (以下、GData ライブラリ) を組み込んでみる。feed/{core,parse,post}.py の代わりに GData ライブラリを使うわけだ。
試してみてわかったことだが、Blogger で作った公開ブログの記事を取得するだけであれば、GAE アプリで GData のための認証は必要ない。
準備
ライブラリのソースをコピーする
GData ライブラリのパッケージを展開すると、src 以下に atom と gdata の 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 ライブラリの内容を見ていると、XxxxClient
と XxxxService
という 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 系統、用意されているのだろう? 公式ドキュメントにはこの理由は書かれていない(少なくともわたしは見つけられなかった)。
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 では q
と author
はサポートされていない、とのこと(→ Blogger query parameters reference - Reference Guide - Blogger APIs)。また、updated-min
と updated-max
を指定する際には、orderby
に "updated
" を指定しなければならない、とのこと。
上記の get_posts
の戻り値は gdata.blogger.data.BlogPostFeed
のインスタンスになる。このインスタンスの entry
属性は gdata.blogger.data.BlogPost
のインスタンスのリストになっていて、これが個々の記事の内容になっている。このため、self.viewinfo.content
は gdata.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>
よって、テンプレート中の記述で entry
は self.viewinfo.content
は gdata.blogger.data.BlogPost
のインスタンスを指している。つまり、entry.get_post_id
と entry.title.text
は gdata.blogger.data.BlogPost
の属性参照(とメソッド呼び出し)になっている。このような記述のしかたは、ある意味、バックエンドの仕組みがプレゼンテーション層に漏らしていることになるので、ちょっと気になる。とはいえ、データを別のオブジェクトに詰め直すのもムダだし、GData ライブラリはスタンダード的なものでもあるから、あえて独自のオブジェクトを作る必要はないと判断した。
内容表示
こちらの変更では少し苦労した。というのも、gdata.blogger.client.BloggerClient
には、「Post ID」を指定して個別の記事の内容を取得するためのメソッドが用意されていなかったからだ。
そこで、gdata.blogger.client.BloggerClient
を継承した BloggerPostClient
を作り、そこに「Post ID」で記事の内容を取得するメソッド get_one_post
を定義した。肝になるのは get_feed
の呼び出しで desired_class
に gdata.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.ico を images ディレクトリに置き、app.yaml に以下の記述を追加した。
(src/app.yaml より) - url: /favicon.ico static_files: images/favicon.ico upload: images/favicon.ico
関連リンク
- Developer's Guide Overview - Google Data Protocol (Google Code)
→ プロトコルのバージョン 1 と 2 の差についての記述がある - Reference Guide - Blogger APIs (同上)
- Protocol Reference - Google Data Protocol (同上)
- Google App Engine の一般的質問 (GAE 公式ドキュメントより)
0 件のコメント:
コメントを投稿