Google App Engine で dropbox API を使うにあたって
dropbox for developers の このページで公開されている python のライブラリを Google App Engine 上で使うに当たって、いろいろと下準備をしないといけなかった。
他の人も公開されているライブラリを Google App Engine 上で使うかもしれないので、下準備について書いておこうと思う。
パッケージのダウンロード
Google App Engine ではこのドキュメントにあるとおり、必要最低限のPython のパッケージモジュールしか Google App Engine にインストールされていない。
このため、dropbox で公開しているライブラリを使うためには、以下のパッケージが必要になる。
- oauth
- poster
- dropbox client library
ブラウザかコマンドで上記のパッケージをダウンロードしてきましょう。
以下は、wget でダウンロードしてきたもの。
$ wget http://pypi.python.org/packages/source/o/oauth/oauth-1.0.1.tar.gz#md5=30ed3cc8c11d7841a89feab437aabf81
$ wget http://pypi.python.org/packages/2.5/p/poster/poster-0.6.0-py2.5.egg#md5=5153835c7b82cb5f1a7a2197c7f0d2a2
$ wget https://www.dropbox.com/static/developers/dropbox-client-python-BETA.tar.gz
oauth と poster は pypiで公開されている正式パッケージをダウンロードする。
現時点では、oauth のバージョンは 1.0.1、poster のバージョンは 0.6.0 が最新バージョンみたい。
dropbox client library のパッケージは、dropbox 本家で公開されているものをダウンロードする。
zip アーカイブ
次に、ダウンロードしたパッケージを zip アーカイブする。
Google App Engine で pypi などでダウンロードしてきたパッケージを利用するにはいろいろな方法があると思うが、zip 形式でアーカイブされたパッケージを sys.path.insert でインポートする方法がパッケージの管理が楽なためこの方法で行う。
zip アーカイブされたパッケージをインポートする方法は後で説明する。
ダウンロードしてきた、poster のパッケージである poster-0.6.0-py2.5.egg は既に zip アーカイブされているため、これに関してはこれをそのまま利用する。
oauth、dropbox client library はソースファイルであるため、zip アーカイブする必要がある。
oauth の zip アーカイブ
以下のコマンドを実行。
$ tar xvf oauth-1.0.1.tar.gz
x oauth-1.0.1/
x oauth-1.0.1/._LICENSE.txt
x oauth-1.0.1/LICENSE.txt
x oauth-1.0.1/oauth/
x oauth-1.0.1/oauth/__init__.py
x oauth-1.0.1/oauth/example/
x oauth-1.0.1/oauth/example/client.py
x oauth-1.0.1/oauth/example/._server.py
x oauth-1.0.1/oauth/example/server.py
x oauth-1.0.1/oauth/._oauth.py
x oauth-1.0.1/oauth/oauth.py
x oauth-1.0.1/oauth.egg-info/
x oauth-1.0.1/oauth.egg-info/dependency_links.txt
x oauth-1.0.1/oauth.egg-info/PKG-INFO
x oauth-1.0.1/oauth.egg-info/SOURCES.txt
x oauth-1.0.1/oauth.egg-info/top_level.txt
x oauth-1.0.1/oauth.egg-info/zip-safe
x oauth-1.0.1/PKG-INFO
x oauth-1.0.1/setup.cfg
x oauth-1.0.1/._setup.py
x oauth-1.0.1/setup.py
$ cd oauth-1.0.1
$ sudo python setup.py bdist_egg
running bdist_egg
running egg_info
writing oauth.egg-info/PKG-INFO
writing top-level names to oauth.egg-info/top_level.txt
writing dependency_links to oauth.egg-info/dependency_links.txt
reading manifest file 'oauth.egg-info/SOURCES.txt'
writing manifest file 'oauth.egg-info/SOURCES.txt'
installing library code to build/bdist.macosx-10.6-i386/egg
running install_lib
running build_py
creating build
creating build/lib
creating build/lib/oauth
copying oauth/__init__.py -> build/lib/oauth
copying oauth/oauth.py -> build/lib/oauth
creating build/bdist.macosx-10.6-i386
creating build/bdist.macosx-10.6-i386/egg
creating build/bdist.macosx-10.6-i386/egg/oauth
copying build/lib/oauth/__init__.py -> build/bdist.macosx-10.6-i386/egg/oauth
copying build/lib/oauth/oauth.py -> build/bdist.macosx-10.6-i386/egg/oauth
byte-compiling build/bdist.macosx-10.6-i386/egg/oauth/__init__.py to __init__.pyc
byte-compiling build/bdist.macosx-10.6-i386/egg/oauth/oauth.py to oauth.pyc
creating build/bdist.macosx-10.6-i386/egg/EGG-INFO
copying oauth.egg-info/PKG-INFO -> build/bdist.macosx-10.6-i386/egg/EGG-INFO
copying oauth.egg-info/SOURCES.txt -> build/bdist.macosx-10.6-i386/egg/EGG-INFO
copying oauth.egg-info/dependency_links.txt -> build/bdist.macosx-10.6-i386/egg/EGG-INFO
copying oauth.egg-info/top_level.txt -> build/bdist.macosx-10.6-i386/egg/EGG-INFO
copying oauth.egg-info/zip-safe -> build/bdist.macosx-10.6-i386/egg/EGG-INFO
creating dist
creating 'dist/oauth-1.0.1-py2.5.egg' and adding 'build/bdist.macosx-10.6-i386/egg' to it
removing 'build/bdist.macosx-10.6-i386/egg' (and everything under it)
dist/ に zip アーカイブされたパッケージである oauth-1.0.1-py2.5.egg が作られる。
dropbox client library の zip アーカイブ
dropbox client library の場合 egg を作成する前に、ソースをちょっといじらないといけない。
ダウンロードしてきたソースを解凍してみよう。
$ tar xvf dropbox-client-python-BETA.tar.gz
x dropbox-client-python/
x dropbox-client-python/bin/
x dropbox-client-python/bin/cli_client.py
x dropbox-client-python/bin/oauth_diff.py
x dropbox-client-python/config/
x dropbox-client-python/config/testing.ini.example
x dropbox-client-python/config/trusted_testing.ini.example
x dropbox-client-python/dropbox/
x dropbox-client-python/dropbox/__init__.py
x dropbox-client-python/dropbox/auth.py
x dropbox-client-python/dropbox/client.py
x dropbox-client-python/dropbox/rest.py
x dropbox-client-python/LICENSE
x dropbox-client-python/Makefile
x dropbox-client-python/setup.py
x dropbox-client-python/tests/
x dropbox-client-python/tests/dropbox_tests/
x dropbox-client-python/tests/dropbox_tests/__init__.py
x dropbox-client-python/tests/dropbox_tests/auth_tests.py
x dropbox-client-python/tests/dropbox_tests/client_tests.py
x dropbox-client-python/tests/dropbox_tests/helpers.py
x dropbox-client-python/tests/dropbox_tests/rest_tests.py
x dropbox-client-python/tests/dropbox_tests/trusted_client_tests.py
x dropbox-client-python/tests/file with spaces.txt
x dropbox-client-python/tests/sample_photo.jpg
x dropbox-client-python/tests/中文.txt
dropbox-client-python/dropbox の配下にある3つのスクリプトが dropbox client lirbrary で利用するモジュールである。
これら3つのスクリプトでは simplejson が利用されている。しかし、simplejson は Google App Engine では既に django パッケージで利用されているためか、ダウンロートしたソースの状態で利用するとエラーが発生して dropbox client library がまともに動作しない。なので、これら3つのスクリプトで simplejson を利用しているコードを修正する。
修正はこれら3つのスクリプトに記載された、
import simplejson as json
の部分を、
from django.utils import simplejson as json
に書き換えるだけである。
3つのスクリプトを書き換えたら、以下のコマンドを実行する。
$ sudo python setup.py bdist_egg
running bdist_egg
running egg_info
creating dropbox_client.egg-info
writing requirements to dropbox_client.egg-info/requires.txt
writing dropbox_client.egg-info/PKG-INFO
writing top-level names to dropbox_client.egg-info/top_level.txt
writing dependency_links to dropbox_client.egg-info/dependency_links.txt
writing manifest file 'dropbox_client.egg-info/SOURCES.txt'
reading manifest file 'dropbox_client.egg-info/SOURCES.txt'
writing manifest file 'dropbox_client.egg-info/SOURCES.txt'
installing library code to build/bdist.macosx-10.6-i386/egg
running install_lib
running build_py
creating build
creating build/lib
creating build/lib/dropbox
copying dropbox/__init__.py -> build/lib/dropbox
copying dropbox/auth.py -> build/lib/dropbox
copying dropbox/client.py -> build/lib/dropbox
copying dropbox/rest.py -> build/lib/dropbox
creating build/bdist.macosx-10.6-i386
creating build/bdist.macosx-10.6-i386/egg
creating build/bdist.macosx-10.6-i386/egg/dropbox
copying build/lib/dropbox/__init__.py -> build/bdist.macosx-10.6-i386/egg/dropbox
copying build/lib/dropbox/auth.py -> build/bdist.macosx-10.6-i386/egg/dropbox
copying build/lib/dropbox/client.py -> build/bdist.macosx-10.6-i386/egg/dropbox
copying build/lib/dropbox/rest.py -> build/bdist.macosx-10.6-i386/egg/dropbox
byte-compiling build/bdist.macosx-10.6-i386/egg/dropbox/__init__.py to __init__.pyc
byte-compiling build/bdist.macosx-10.6-i386/egg/dropbox/auth.py to auth.pyc
byte-compiling build/bdist.macosx-10.6-i386/egg/dropbox/client.py to client.pyc
byte-compiling build/bdist.macosx-10.6-i386/egg/dropbox/rest.py to rest.pyc
creating build/bdist.macosx-10.6-i386/egg/EGG-INFO
copying dropbox_client.egg-info/PKG-INFO -> build/bdist.macosx-10.6-i386/egg/EGG-INFO
copying dropbox_client.egg-info/SOURCES.txt -> build/bdist.macosx-10.6-i386/egg/EGG-INFO
copying dropbox_client.egg-info/dependency_links.txt -> build/bdist.macosx-10.6-i386/egg/EGG-INFO
copying dropbox_client.egg-info/requires.txt -> build/bdist.macosx-10.6-i386/egg/EGG-INFO
copying dropbox_client.egg-info/top_level.txt -> build/bdist.macosx-10.6-i386/egg/EGG-INFO
zip_safe flag not set; analyzing archive contents...
creating dist
creating 'dist/dropbox_client-1.0-py2.5.egg' and adding 'build/bdist.macosx-10.6-i386/egg' to it
removing 'build/bdist.macosx-10.6-i386/egg' (and everything under it)
zip アーカイブが正常に完了すれば、dist/ に dropbox_client-1.0-py2.5.egg が作られているはず。
zip アーカイブの配置
作成した zip アーカイブのパッケージを、Google App Engine で動作するアプリケーションに配置する。
zip アーカイブのパッケージの配置に関してはいろいろとあると思うが、ここでは、アプリケーションディレクトリの直下に eggs というディレクトリを作成してそこに配置する。
ダウンロードしてきた poster のパッケージと、zip アーカイブを作成した oauth と dropbox client library のパッケージ配置すると以下のようになる。
myapp/eggs
myapp/eggs/oauth-1.0.1-py2.5.egg
myapp/eggs/poster-0.6.0-py2.5.egg
myapp/eggs/dropbox_client-1.0-py2.5.egg
zip アーカイブのインポート
dropbox client library を利用するスクリプトの py ファイルの先頭に以下のコードを記載する。
# import eggs import os import sys for arch in os.listdir('eggs'): sys.path.insert(0, os.path.join('eggs', arch))
このコードは、myapp/eggs にある zip アーカイブされた拡張子 egg ファイルを読み込んで、zip インポートを実行するコードである。
これでこのコード以降、dropbox client library が利用できるようになる。
後は、お好きなように♪
辞書オブジェクト
何かとプログラミングでお世話になる辞書オブジェクトというか連想配列。
もはやなくてはならないデータ型の一つですが、
Windows の COM で実装された辞書オブジェクトの動作にはまって
不可解なバグを作ってしまうという醜態をしてしまったので、
他の方も、自分と同じくはまってしまわないよう、情報展開しておこうと思う。
さてそれは何か。
Windows の COM で実装された辞書オブジェクト、いわゆる Scripting::Dictionary は
登録されていないキーを指定して、このオブジェクトにアクセスすると
要素数が増えてしまうんです。
とりあえず、コードで書いた方が分かると思うので VB6 で書いてみた。
Option Explicit Sub Main() ' 辞書オブジェクトを作成 Dim oDict As Object Set oDict = CreateObject("Scripting.Dictionary") ' VBだとScripting.Dictionaryでオブジェクトを作る ' 自分で定義したPersonクラスのオブジェクトを作成 Dim oPerson As Person Set oPerson = New Person ' 以下 oPerson のプロパティを設定 With oPerson ' .... End With ' ここで辞書オブジェクトにPersonオブジェクトを登録 Call oDict.Add("foo", oPerson) ' ここで辞書オブジェクトに登録されている要素数をCountプロパティでデバッガに出力 ' -> Countプロパティから取得できる値は1になる Debug.Print "Dictionray Count (Before) : " & oDict.Count ' ここで辞書オブジェクトに登録されていないキーを指定して取得してみる Dim oResult As Person Set oResult = GetItem("bar", oDict) ' GetItemメソッド経由で取得する ' ここで辞書オブジェクトに登録されている要素数を出力 ' -> Countプロパティから取得できる値は1のはず。。。 Debug.Print "Dictionray Count (After) : " & oDict.Count ' 辞書オブジェクトに登録されていないキーを指定した場合型は何が取得できるのかな。 Debug.Print "TypeName : " & TypeName(oDict("bar")) End Sub ' アイテムを取得する Private Function GetItem(ByVal strKey As String, ByRef objDict As Object) As Variant On Error GoTo Catch Dim vItem As Variant Set vItem = objDict(strKey) Goto Finally Catch: Debug.Print "Raise Error !! : " & Err.Description Set vItem = Nothing Finally: Set GetItem = vItem End Function
で書いた VB6 のコードを実行させるとこんな感じで出力される。
Dictionray Count (Before) : 1
Raise Error !! : 型が一致しません。
Dictionray Count (After) : 2
TypeName : Empty
なんと、辞書オブジェクトの Count プロパティで取得できる要素数が
1 から 2 に増えているでありませんか。
これは、MS のオンラインの MSDN によると仕様のようです。
Item プロパティの解説にこんな感じで載っていた。
項目を変更するときに引数 key で指定したキーが見つからない場合、newitem で指定した項目と関連付けられた、引数 key で指定した新しいキーが作成されます。
また、既存の項目を取得するときに引数 key で指定したキーが見つからない場合は、空の項目と関連付けられた、引数 key で指定した新しいキーが作成されます。
というわけで、COM の辞書オブジェクトでを使う場合は、
Count プロパティで取得できる要素数は動的に変わってしまう可能性があるということになります。
実装する処理によっては、Exists メソッドでキーに該当するアイテムが
辞書オブジェクトに登録されているかどうかチェックしてから、
Item プロパティや a("key1") みたいにアクセスしないといけないですね。
VBA、WSH 等で COM の辞書オブジェクトを使う場合は気を付けましょう。
ここで、他の場合はどういう動きをするかどうか気になったので確認してみた。
まずは、.NET Framework の Dictionary オブジェクト。
以下は、C#で書いたコード。
using System; using System.Collections.Generic; using System.Text; namespace DictionaryTest { class Program { static void Main(string[] args) { // 辞書オブジェクトを作成 Dictionary<string, Person> dict = new Dictionary<string, Person>(); // 自分で定義したPersonクラスのオブジェクトを作成 Person person = new Person(); // 以下Personオブジェクトのプロパティ設定 // ... // ここで辞書オブジェクトにPersonオブジェクトを登録 dict.Add("foo", person); // ここで辞書オブジェクトに登録されている要素数をCountプロパティでコンソールに出力 // -> Countプロパティから取得できる値は1になる Console.WriteLine("Dictionary Count (Before) : {0}", dict.Count); // ここで辞書オブジェクトに登録されていないキーを指定して取得してみる Person result = GetItem("bar", dict); // GetItemメソッド経由で取得する // ここで辞書オブジェクトに登録されている要素数を出力 // -> Countプロパティから取得できる値は1のはず。。。 Console.WriteLine("Dictionary Count (After) : {0}", dict.Count); } static Person GetItem(string key, Dictionary<string, Person> dict) { Person person = null; try { person = dict["key1"]; } catch(Exception e) { Console.WriteLine("Raise Exception !! : {0}", e); } return person; } } }
実行結果は、以下のとおり。
Dictionary Count (Before) : 1
Raise Exception !! : System.Collections.Generic.KeyNotFoundException: 指定された
キーはディレクトリ内に存在しませんでした。
場所 System.ThrowHelper.ThrowKeyNotFoundException()
場所 System.Collections.Generic.Dictionary`2.get_Item(TKey key)
場所 DictionaryTest.Program.GetItem(String key, Dictionary`2 dict) 場所 C:\Us
ers\xxxx\Desktop\DictionaryTest\DictionaryTest\Program.cs:行 43
Dictionary Count (After) : 1
.NET Framework の辞書オブジェクトは、登録されていないキーでアクセスすると
例外が発生し、要素数は変化しません。
続いて、Python。
こちらは、Mac OS X に MacPorts でインストールした Python 2.5.5 で確認してみました。
macbook-2:Memo kazupon$ python
Python 2.5.5 (r255:77872, Jun 6 2010, 13:45:39)
[GCC 4.2.1 (Apple Inc. build 5659)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> d = { "key1":1 }
>>> len(d)
1
>>> d['key2']
Traceback (most recent call last):
File "", line 1, in
KeyError: 'key2'
>>> len(d)
1
Python の場合も、辞書オブジェクトに登録されていないキーでアクセスすると
例外が発生し、要素数は変化しないみたいです。
最後に、Ruby。
macbook-2:Memo kazupon$ ruby -v
ruby 1.8.7 (2009-06-12 patchlevel 174) [i686-darwin10]
macbook-2:Memo kazupon$ irb
irb(main):001:0> h = { 'key1' => 1 }
=> {"key1"=>1}
irb(main):002:0> h.count
=> 1
irb(main):003:0> h['key2']
=> nil
irb(main):004:0> h.count
=> 1
こちらも、Mac OS X の MacPorts でインストールした Ruby 1.8.7 で動作確認してみました。
Ruby の場合は、辞書オブジェクト(Ruby の場合は連想配列と呼びますが。)に登録されていないキーでアクセスすると
例外が発生せず、nil が取得できるようです。
これは、COM の辞書オブジェクトで Empty が取得できるのと似ています。
ただ、要素数は変化しないようです。
というわけで、言語によって辞書オブジェクトは
いろいろな動作をするみたいです。
八戸
2010年にはいってから、何かと故郷の八戸に何回か帰っています。
なんで八戸にちょくちょく帰っているかというと
今、八戸がホットなんです!!
地元では八戸を変えようと、いろんな動きがでてきています。
こうした活動によって、個々のフリーランスが集まってユニットを立ち上げたり、
Twitter を通じて地元の人たちがつながって写真部が結成されたりと、
八戸で何かが変わりつつあります。
保守的な考えに飲まれやすい環境の中で、
自分から変わろうとする人たちが故郷で増えてきているのは、
大変うれしいし、自分もモチベーションがかなり上がります。
そんなホットな八戸で、いっしょに自分も何かできればいいなあと思い、
まずは、最近本業の仕事とは別にいっしょに活動しているクリフトファーと
いっしょにセミナーをしてきました。
勉強会の様子は Ustream にあります。ご協力頂いた tripod-studio さんには大変感謝しています。
※注意:セミナーは3時間ですので、動画は長いです。
セミナーを主催するというは初めてでしたので、
まあいろいろと変な点というか、問題点はいろいろあったと思っています。
でも、意外と好評だったので、主催してよかったと感じています。
今後は、こういったセミナーの他、ソフトウェアエンジニアとして何か作りながら
関東でも八戸出身者、興味がある方のネットワーキングをやっていきたいと思います。
2009のおわりに
年の瀬が迫ってきました。
というわけで、2009は自分にとってどういう年だったか振り返ってみました。
これまでに体験した出来事や参加したイベント
参加したイベントや起きた出来事の観点で整理してみました。
- 社内テクニカルリーダーデベロップメント研修論文発表
- JTPAシリコンバレーカンファレンス2009
- エンジニアの未来サミット
- おいしいソースのつくり方
- Seasar Conference 2009 Autumn
- amazonEC2ナイトセミナ
- Make: Tokyo Meeting 04
- セキュリティキャラバン東京
- 転職活動
- 青森ねぶたオフ会
- 青森八戸オフ会
- ケイレキConnectオフ会
- 妹の結婚
- 高校の友人が結婚
- 祖母の他界
列挙してみると、たくさんいろんなことを経験したり体験してきたなあと感じています。社内研修で論文発表したり、海外に行ってきたり、勉強会やセミナーに参加したりと。また、オフ会を通じたいろんな人との交流や、おめでたいことや悲しいことまでも。去年と比べるとありえないぐらいの量だと思っています。
中でも一番は、やっぱりシリコンバレーカンファレンス!
シリコンバレーの空気を肌で感じ、そして現地のオフィス環境や現地で働く人の話を通じることで、自分の好きなコード書きながら、現地の人々みたいに幸せに生きていこうと、改めてこれから自分の進む道を再認識させてくれたし、そして、このカンファレンスを通じて知り合った素晴らしい人たちと出会えたし。中でも次のロールモデルになるような人(というより今のロールモデル)と出会えることができてホントよかったです。これを機に、今までにないくらい会社の業務以外で異なるプラットフォームでプログラミングするようになったし、そしてセミナーや交流会といったイベントにも積極的に参加するようになりました。
励んだこと
自発的に励んだことについて整理してみました。
- english
- ruby
- git
- objective-c
英語は、シリコンバレーカンファレンスで自分の英語力のなさを身を持って経験したせいですね。おかげで、会社の英会話研修や、smart.fm や iPhone アプリ、Twitter で英語でつぶやいたりするようになった感じです。(とはいっても英語力はまだまだですが。。。)
ruby は Twitter2MixiVoice という Twitter のつぶやきを mixi ボイスに投稿するという単純なものを作りながら学んだという感じ。いろんな gem に手を出しながらライブラリの使い方を試行錯誤したため、作り終わったのが着手してから2ヶ月後。
時間がかかってしまいましたが、rake といったビルドライブラリ、mechanize といったウェブサイトのアクセスを自動化するライブラリなどなど、いろいろなライブラリについて知ることができたので結果的には良かったと思っています。
ruby を使ってみての感想としては
という感じです。
objective-c は、iPhone アプリに興味があったということもあり、ケイレキ.jp が API を公開したということで、これを機に ケイレキ.jp の iPhoneクライアントを作りながら覚えようという感じで始めました。ここ最近始めたばっかりなのでまだまだですが、来年 2010 のケイレキ Connect のパーティがあるんで、それまでにはなんとかタイムラインを表示できるとこまでモノを作らねばと考えています。
objective-c を使ってみての感想としては、
- ブランケット"[...]"でオブジェクトにメッセージ送信って SmallTalkっぽいなあ
- リファレンスカウンタのメモリ管理は大変
- Rumtime API 使えばいろいろと Hack できそう
- Category で既存フレームワークを簡単に拡張できそう
- Delegate で Event を実現するのね
といった感じです。
git は、やるきっかけはシリコンバレーのカンファレンスで知り合った人たちのフリーなプロジェクトがきっかけです。ソースの管理を github でしてるんだけど、そのためには git が使えないといけないわけで。使っていくうちに git の柔軟さにやられてしまいました。
課題
プログラミング言語を学びながら何かを作っているわけですが、途中ヤクの毛刈りにはまってしまい、モノが完成するまで時間がかかってる。
いかに自分のフリーなプロジェクトとはいえ、時間は有限なのでこれでは駄目だ。 redmine とか導入して管理せねば。
英語も最近ちょっと停滞気味。なんとかしないと。
チャレンジ
- slimetimer の iPhone アプリの作成 (無いなら作ろう)
- android アプリの作成 (今の Java を学ぼう)
- Webサービスの立ち上げ (Amazon EC2 で立ち上げよう)
- プロジェクトへの参加 (とりあえず hirashima さんのプロジェクトに打診してみよう)
- 勉強会での発表 (プレゼン能力を鍛えよう)
- ボランティアへの参加 (せんべい汁研究所のサポーターズになろう)
- 転職 (活動再開しよう)
FeedTweetテスト
twitter に投稿できるかどうかのテストです。
Twitter::RESTError エラー
http://twitter4r.rubyforge.org/rdoc/を元に、
twitter4r API サンプルの動作確認していたら Twitter::RESTError のエラーが発生。
具体的には、
require 'rubygems' gem 'twitter4r', '>=0.3.0' require 'twitter' require 'twitter/console' require 'kconv' # Create Twitter::Client Instance client = Twitter::Client.from_config('twitter.yaml', 'envname') # Get Twitter::User Instance user = client.user('username') # Print Twitter::User Attributes Twitter::User::attributes.each do |attribute| puts "#{attribute} : #{Kconv.kconv(user.send(attribute).to_s, Kconv::UTF8)}" end
のようなユーザ情報を表示するようなコードを実行させたら、
user メソッドで以下のようなエラーが発生した。
/opt/local/lib/ruby/gems/1.8/gems/twitter4r-0.3.1/lib/twitter/client/base.rb:39:in `raise_rest_error': Not Found (Twitter::RESTError)
from /opt/local/lib/ruby/gems/1.8/gems/twitter4r-0.3.1/lib/twitter/client/base.rb:44:in `handle_rest_response'
from /opt/local/lib/ruby/gems/1.8/gems/twitter4r-0.3.1/lib/twitter/client/base.rb:18:in `http_connect'
from /opt/local/lib/ruby/1.8/net/http.rb:543:in `start'
from /opt/local/lib/ruby/gems/1.8/gems/twitter4r-0.3.1/lib/twitter/client/base.rb:14:in `http_connect'
from /opt/local/lib/ruby/gems/1.8/gems/twitter4r-0.3.1/lib/twitter/client/user.rb:37:in `user'
from user_print.rb:11
Not Found という吐き出されたエラー内容から twitter4r が
twitter REST API で URL を正しく設定できていないんではないかと
疑いのもとスタックトレース情報を元に twitter4r のソースをたどっていくと、
user.rb 内で定義されている :info の定数が間違っているのが原因っぽいのが判明。
# twitter4r-0.3.1/lib/twitter/client/user.rb:2 class Twitter::Client @@USER_URIS = { :info => '/users/show', :friends => '/statuses/friends.json', :followers => '/statuses/followers.json', }
:info だけフォーマットが指定されてない。。。。
:info が '/users/show' となっているので、
user.rb の user メソッドにユーザIDもしくはスクリーン名が指定されると、
user メソッド内で呼び出される、 create_http_get_request メソッドに
'/users/show'(@@USER_URIS[action]) と {id=>'username'}(params)
が指定されてしまいます。
# twitter4r-0.3.1/lib/twitter/client/user.rb:37 def user(id, action = :info, options = {}) raise ArgumentError, "Invalid user action: #{action}" unless @@USER_URIS.keys.member?(action) id = id.to_i if id.is_a?(Twitter::User) params = options.merge(:id => id) response = http_connect {|conn| create_http_get_request(@@USER_URIS[action], params) } bless_models(Twitter::User.unmarshal(response.body)) end
create_http_get_request メソッドでは
twitter に HTTP の GET でアクセスするための
Net::HTTP::Get のインスタンスを作成して呼び出し元に返しています。
その際に、path を指定しているが path は create_http_get_request メソッド
に指定されたパラメータにより '/users/show?id=username' となり、
その path が指定された Net::HTTP::Get のインスタンスが呼び出し元に返されてしまっています。
# twitter4r-0.3.1/lib/twitter/client/base.rb:79 def create_http_get_request(uri, params = {}) path = (params.size > 0) ? "#{uri}?#{params.to_http_str}" : uri Net::HTTP::Get.new(path, http_header) end
そんでもって、 そのインスタンスを利用して http_connect メソッドで
twitter にアクセスしてユーザー情報を得ようするみたいですが、
そんな指定された場所はありませんと怒られてしまってエラーとなり、
user メソッドでユーザー情報が取得できないという状況のようです。
というわけで、 @@USER_URIS の :info を '/users/show.json' にしてみたら動きました。
こうすることで、create_http_get_request メソッドで
path に '/users/show.json?id=username' となるおかげで、
twitter にアクセスできるようです。
以下は、その実行結果。
id : 17755912
name : kazuya kawaguchi
description : かずぽんです。仕事はソフト開発してます。よろしく。
location : iPhone: 35.689976,139.697830
screen_name : kazu_pon
url : http://d.hatena.ne.jp/kazu_pon/
protected : false
profile_image_url : http://a1.twimg.com/profile_images/274655366/profile_normal.jpg
profile_background_color : 0099B9
profile_text_color : 3C3940
profile_link_color : 0099B9
profile_sidebar_fill_color : 95E8EC
profile_sidebar_border_color : 5ED4DC
profile_background_image_url : http://s.twimg.com/a/1250809294/images/themes/theme4/bg.gif
profile_background_tile : false
utc_offset : 32400
time_zone : Tokyo
following : false
notifications : false
favourites_count : 123
followers_count : 114
friends_count : 168
statuses_count : 1340
created_at : Sun Nov 30 15:33:02 +0000 2008
ちなみに、 my メソッドもこれで動くようになります。
そんなわけで、とりあえず動作するようになったけでなんですが、
'/users/show.json?id=username' でユーザー情報を取得する方法って、
Twitter API 仕様書 第二十版 (2009年4月16日版) や本家の http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-users%C2%A0show
の例をみるとないんですよね。。。
それとも実はある?というより、自分が知らないだけ?。
もし、この方法が正式な方法ではない場合、twitter4r の作者に
なんとかしてもらいたなぁと思います。
自分がパッチを作るという案もありますが。
rubyで単体テスト
ruby on rails で本格的にアプリ作る前に、ruby の単体テストってどんなもんかごにょごにょと書いてみた。
テストされるコードは以下の適当なコード。
class Foo def foo "foo" end def bar(arg) raise ArgumentError end def hoge throw :hoge_error end def hogehoge(arg) arg end def foo? true end def one 1 end def two 2 end def >(obj) two > obj.one end def calc_delta(arg) arg / 10**6 end end
テストコードはこんな感じで書いてみた。
require "test/unit" require "foo" class TestFoo001 < Test::Unit::TestCase def setup # ごにょごにょと前処理 @obj1 = Foo.new @obj2 = Foo.new end def teardown # ごにょごにょと後処理 end # assert のサンプル def test_assert assert(@obj1.foo?, "失敗メッセージ") end # assert_equal のサンプル def test_assert_equal assert_equal(@obj1.one, @obj1.one, "失敗メッセージ") end # assert_not_equal のサンプル def test_assert_not_equal assert_not_equal(@obj1.one, @obj1.two, "失敗メッセージ") end # assert_instance_of のサンプル def test_assert_instance_of assert_instance_of(Foo, @obj1, "失敗メッセージ") end # assert_nil のサンプル def test_nil assert_nil(nil, "失敗メッセージ") end # assert_not_nil のサンプル def test_not_nil assert_not_nil(@obj1.nil?, "失敗メッセージ") end # assert_kind_of のサンプル def test_assert_kind_of assert_kind_of(Foo, @obj1, "失敗メッセージ") end # assert_respond_to のサンプル def test_assert_respond_to assert_respond_to(@obj1, :foo, "失敗メッセージ") end # assert_match のサンプル def test_assert_match assert_match(/foo/, @obj1.foo, "失敗メッセージ") end # assert_same のサンプル def test_assert_same assert_same(@obj1, @obj1) end # assert_not_same のサンプル def test_assert_not_same assert_not_same(@obj2, @obj1, "失敗メッセージ") end # assert_operator のサンプル def test_assert_operator assert_operator(@obj1, :>, @obj2, "失敗メッセージ") end # assert_raise のサンプル def test_assert_raise assert_raise(ArgumentError, "失敗メッセージ") { @obj1.bar } end # assert_nothing_raised のサンプル def test_assert_nothing_raise assert_nothing_raised(ArgumentError, "失敗メッセージ") { @obj1.foo } end # assert_throws のサンプル def test_assert_thorws assert_throws(:hoge_error, "失敗メッセージ") { @obj1.hoge } end # assert_nothing_thrown のサンプル def test_assert_nothing_thrown assert_nothing_thrown("失敗メッセージ") { @obj1.foo } end # assert_in_delta のサンプル def test_assert_in_delta assert_in_delta(0.05, @obj1.calc_delta(50000.0), 0.00001, "失敗メッセージ") end # assert_send のサンプル def test_assert_send assert_send([@obj1, :hogehoge, 4], "失敗メッセージ") end # assert_block のサンプル def test_assert_block assert_block("失敗メッセージ") { @obj1.foo? } end end
MS な言語で NUnit で単体テストコードを書いてた自分にとっては、
見慣れない assertion 機能があってちょっと困惑気味。
assert_send と assert_block ってどうんな風に使うのかな?
とりあえず、課題としておこう。