2011年11月11日金曜日

AWSのSDKを使ってみた(PHP編)

アマゾンのEC2を使う機会がありましたのでSDKを使ってみたメモを記述します。

セットアップ:
 AWSサイトからPHP用のSDKをダウンロードします。
 私の場合、バージョン1.4.7でした。
 その他セットアップはAWSサイトを参照してください。

サンプル作成:
 ダウンロードしたSDKのフォルダにある[config-sample.inc.php]を
 名前変更し[config.inc.php]とします。
 [config.inc.php]の中にある以下の部分を変更します。
  define('AWS_KEY', '<自分のAWS_KEY>');
  define('AWS_SECRET_KEY', '<自分のAWS_SECRET_KEY>');
  (AWS_KEYとAWS_SECRET_KEYはAWSサイトから取得します)

 以下、インスタンス起動のサンプルです。


  <?php
  require_once('sdk.class.php');
  $ec2 = new AmazonEC2();

  // リージョンは東京とする
  // 「AmazonEC2::REGION_APAC_NE1」と指定すると、
  // ホスト名が見つからないエラーが発生した。
  // SDKのソースを調べると、ホスト名設定(ec2.class.php)は以下となっていた。
  // $this--->set_hostname('http://ec2.'. $region .'.amazonaws.com');
  // リージョン定数は「ec2.ap-northeast-1.amazonaws.com」
  // なので、ホスト名が以下となってしまう。
  // 「http://ec2.ec2.ap-northeast-1.amazonaws.com.amazonaws.com」
  //$ec2->set_region(AmazonEC2::REGION_APAC_NE1);
  $ec2->set_region("ap-northeast-1");

  // インスタンス起動
  $rc = $ec2->start_instances("<インスタンスID>");
  if(!$rc->isOK()) var_dump($rc);



投稿者:島田

2011年9月1日木曜日

「生きている」APIドキュメント:MasheryのI/O Docs

ちょっと関心した面白いアイデア。
APIのドキュメントと、コードの実行環境を一体にしたような新サービスです。
これが期待通りに動作すれば、「知っているのと知らないのでは」開発効率に大きな差がつくでしょうね。

InfoQ: これまでのAPIドキュメントを変えるMasheryのI/O Docs

投稿者:てらだ

2011年8月31日水曜日

Android で RTP

気づいたのでメモ:
Android API リファレンスによると、AndroidでRTPが実装されているようです。ただしAPI Levelは12。つまり、Android 3.1.x 以降のサポートとなるようです。

投稿者:てらだ

2011年8月26日金曜日

GAEを単純な静的Webサーバーとして使う。


いま手がけているプロジェクトで、ちょこっと公開Webサーバーが必要になりました。
あるアプリからHTTPプロトコルで静的なファイルにアクセスするテストです。

簡単なテストではありますが、万一のことを考えると、他の重要な目的で運用しているサーバーを「兼用」したり、といったことはやりたくないので、新規にサーバーを用意することにしました。

コストを掛けるのも癪なので、無料サーバーを探してみます。いわゆる無料Webホストを幾つか試してみましたが、勝手に広告が挿入されたりして、目的の機能を達成できるものが 意外と見つかりません。

そうこうするうちに、GAEの静的ファイル配信機能が使えるのではないかと思いつきました。やってみるととても簡単。このブログ記事 等を参考にやればすぐにできました。

投稿者:てらだ

2011年8月5日金曜日

赤外線TVリモコン for Android

先日、SH-12Cを入手しました。これで私もスマフォユーザです。
早速、何か作ろうと思い考えました。SH-12Cには赤外線が付いています。
赤外線といえば・・・やはりリモコンですね。

さて、赤外線リモコンについて、いろいろGOOGLEで調べました。
なんだか難しいです。参考

私のテレビはAQUOSです。赤外線リモコンを作るには、
送信するデータが必要なのですが、公式に公開されていないようです。
そんな中、赤外線リモコンの赤外線の具合を解析して公開してくださっているサイト
発見しました。(感謝です)

これで材料は揃いましたので、SHARPのサイトからSDKをダウンロードしてきました。
後は、上記サイトのサンプルを元にアプリを作りました。
実際にAQUOSで試すと・・・うまく動いています。

家中の赤外線リモコンをAndroid端末1つで置き換えられる日が
いつか来ることを信じて今回の記事を終了します。

今回のアプリは以下からダウンロードできます。(そのうち消します)
http://dl.dropbox.com/u/1558735/web/irController.apk

投稿者:島田

2011年7月29日金曜日

Google+ (非公式御免)API

話題のGoogle+ですが、早くもAPIが出てきています(PHP版)。とりあえずメモ。
Google+非公式API

投稿者:てらだ

2011年7月21日木曜日

自由に使える地図データ

地図といえば、GoogleMapですが、もっと自由に地図のデータを使いたい場合には「OpenStreetMap(OSMと略すらしいです)」というものが使えそうです。

http://openstreetmap.jp/

「こんな良いものがあるなら、是非Androidアプリとして実装してみたい」
と思い、早速調べてみました。


googleで「openstreetmap api」で検索すると、
「JA:Xapi - OpenStreetMap Wiki」なるサイトがヒットしました。早速、サイトを開くと、地図データの取得方法が載っていました。

GoogleMapのようにメッシュ状にデータを取得したい場合、以下のようにするようです。
http://www.informationfreeway.org/api/0.6/map?bbox=11.54,48.14,11.543,48.145

ブラウザのURL欄に上記URLを入力すると・・・

はいっ!XMLでデータが受信できるようです。(なんだかモーレツに遅いです)

XMLには緯度経度らしきデータなどがありますので、

このデータを使えば地図が書けそうです。


マッハでAndroidアプリを作ります。

はじめに、OSMからデータを取得します。取得できたデータがXMLなのでXMLパーサーを作らなくてはなりません。

次に、地図の絵を作ります。緯度経度を座標にマップして、いっぱい線を描いていきます。緯度経度のマッピングに頭を悩ませました。(拡大したり移動したり大変でした)

また、線の種類によって色を変えたり、太さを変えたりしました。(高速道路は水色で太くなど・・・)

メッシュ画像ができたら、画面に並べていくと・・・
おぉぉぉ!地図が書けた。

ここまで来たら後はスクロール処理を実装すれば完成です。


以上、作った地図表示アプリをキャプチャーしました。



具体的な実装については、またの機会に投稿したいと思います。

投稿者:島田

2011年7月20日水曜日

Mercurial + Bitbucket でいつでも幸せ(いつでも地獄、とも言う)。

みなさんこんにちは。

震災の影響もあって、開発リソースの一極集中によるリスクの軽減に関心が高まっています。
多少の災害程度で開発に支障をきたすようでは、21世紀の開発者の名が廃るとも言われ、
「どこにいても、何が起こっても開発を止めない」ため、分散・クラウド・モバイル環境下でのソフト開発もあたりまえ(?)な時代になってきました。


私も、移動中の電車やカフェ、自宅等での開発作業をすることが多くなりました。
その場合に問題になるのが、開発用サーバ、特にソースコードリポジトリへのアクセスです。

会社のオフィスでは定番の Subversion を使っていますが、オフィスの外からはアクセスできないようにしてあります。
したがって、外出先で差分を見たり、コミットして安心?したりすることができないという問題がありました。

その暫定対策として、これまでは、開発中のリポジトリ一式をコピーして持ち出し、ノートPC等の「ローカル」環境では、Mercurial を使って一時的にバージョン管理をしていました。
しかしこの管理はあくまでもローカルなので、常にオンラインで同期できているリポジトリよりは、やはり使い勝手の面で劣ります。
また、サーバにコミットすることによる「バックアップ」効果も得られないので、PCがクラッシュした場合も心配です。

そこで、以前から気になっていた Bitbucket を使ってみることにしました。

Bitbucketは、5ユーザまでなら無料・容量無制限(!)で使うことができる Mercurialベースの 開発リポジトリサービス(free code hosting)です。
(こんなものまで無料で使えるようになるとは、いい時代になったものです。)5人以上ならば人数によって段階的に課金されますが、
人数無制限となる最高課金レベルでも月額80ドルなので、自力でこれだけのサーバを維持することを考えるとかなり魅力的な価格設定です。

Bitbucketは、パブリックはもちろん、プライベートのリポジトリも作成できますし、WikiやIssue Trackingシステムも一緒に使うことができるので、小規模チームで分散開発するには十分な機能を持っているといえるでしょう。
オンラインで一瞬でサインアップも完了するので、Mercurialに慣れている人ならすぐにでも使い始めることができると思います。
(Mercurialを知らない人も、Subversionを既に知っていれば学習は容易だと思います。)

とりあえず、現状は、自分の作業ブランチを社内SVNから取得して、それを Bitbucketに上げて日々の作業しています。
(日々のコード差分は Bitbucket側にコミット)。
そして、ひととおり開発の区切りが付いたところで社内SVN側にもコミットする形としています。
これで社内・社外を問わず、連続的に開発作業を継続できるようになったので、とても快適です。

今後は、Mercurial側のSubversion連携機能を活かして、より有機的にリポジトリを扱えるようにしてみたいと思います。




投稿者:てらだ

2011年7月13日水曜日

動けばいいってもんじゃない!(ソフトウェア設計の話)

こんにちは。
オブジェクト指向プログラミングの本質(の一つ)は、「クラスやメソッド等の言語要素を持ち込んで、コードを冗長にするコストを払ってでも、コードを読みやすくする」ことなのですが、このことをあまり理解せずに実装しているのを今だに良く見かけます。最近見かけた例を紹介しましょう。

この例では、「2つの異なるトリガーによって起動される処理Aをどう実装するか」がテーマです。
具体的には以下のような要件でした。
(1)トリガー1:タイマーがタイムアウト
(2)トリガー2:ユーザが終了ボタンをクリック
(3)処理A:ファイルを閉じてプログラムを終了する。
(4)シナリオ:トリガー1又はトリガー2が発生したら、処理Aを実行する。

これを某プログラマさん(外注さんです。すみません。)は下記のように実装していました。動作としてはバグはなく、一応要件を満たしてはいましたが、コードを見ると、動作シナリオが読み取りにくいものになっていました。(言語はJavaで、関係のない細かいところは省略しています。)どこが良くないか分かりますか?

// アプリメイン(よくない例)
class App implements TimeoutListener, OnClickListenr {
  ...
  // タイムアウト時のコールバック
  public void onTimeout() {
    // 全てのファイルを閉じる
    closeAllFiles() ;
    // アプリを終了する。
    exitApp() ;
  }
  // ボタンクリック時のコールバック
 public void onClick( Button btn ) {
    if( btn == this.btnCommandQuit ) { // 終了ボタンが押された・・
       onTimeout() ;//XXX
    }
  } 
}
答えは、//XXXというコメントをつけた行です。終了ボタンのクリックを検知したら、onTimeout()を呼び出していますよね? このソースを普通に読み下すと「終了ボタンがクリックされたら、タイムアウトコールバックを呼び出す」となりますが、意味的に要件と直結しないので、「どうしてこうなっているのだろう?」と読者を悩ませることになってしまいます。

私なら以下のようにコーディングしたいところです。
// アプリメイン(改良版)
class App implements TimeoutListener, OnClickListenr {
  ...
  // タイムアウト時のコールバック
  public void onTimeout() {
    // 処理Aを実行
    procedureA();
  }
  // ボタンクリック時のコールバック
 public void onClick( Button btn ) {
    if( btn == this.btnCommandQuit ) { // 終了ボタンが押された・・
      // 処理Aを実行
      procedureA();
    }
  }
  // 処理A
 protected void procedureA() {
    // 全てのファイルを閉じる
    closeAllFiles() ;
    // アプリを終了する。
    exitApp() ; 
  }
}
上記のコードならば、要件を直接対応づけて、容易にコードを読むことができるはずです。

「これぐらい大した違いじゃない」と思われる方もいるかも知れません。しかし、大規模なプログラムになってきて、こうした問題があちこちに現れてくると、一気に解読コストが上がっていきます。「ちりも積れば・・・」のことわざ通りです。派生開発や保守・引き継ぎ等で非常に骨の折れるコードは、大抵こういった問題を抱えたものが多いのです。

読みやすいコードを書いておけば、拡張がしやすく、引き継ぎもしやすくなり、最終的には自分の身を助けることにつながります。設計力の向上にもつながるので常に意識しておきたい課題です。

投稿者:寺田

2011年6月29日水曜日

2011 CVPR の Longuet-Higgins 賞は、あの論文です。

最も権威ある画像処理関係の学会の一つであるCVPR。

Longuet-Higgins 賞は、過去10年のCVPR論文のなかから「最も根源的なインパクトを与えた研究」に対して毎年授与される賞です。

今年の Longuet-Higgins 賞は、現代の顔認識アルゴリズムの基礎&定番を生み出した、あの Viola & Jonesの "Rapid Object Detection using a Boosted Cascade of Simple Features" に授与されました。まさに満を持してというか、当然の受賞という感じですね。

投稿者:寺田

Eclipse Indigo がリリースされてました。

ちょっと後追いになりますが、Eclipse の最新版、Indigoがリリースされたようです。

個人的には、GUIテストの自動化ツールに興味があります。(Jubulaベースとのこと)。

"Automated functional GUI testing for Java and HTML applications is included via Jubula"

投稿者:寺田

2011年6月22日水曜日

Google App Engine 1.5.1 リリース

GAEの1.5.1がリリースされたそうです。Google の blog のリリース記事を適当に訳してみました。



GoogleI/O 2011からそろそろ1ヶ月経ち、頃合いなので新しいリリースを行おうと思う。
詳細は下記の通り。

提供される変更点

Geolocation ヘッダー

App Engineは、あらゆるリクエストについて、その発信元アドレスを元に、(可能なかぎり)その国の識別情報をヘッダーを含むこととした。このヘッダは、"X-AppEngine-country"でタグ付けされており、開発者はこの情報を使ってカスタマイズすることができる。これにより、「あなたの地域を選択してください」などいうドロップダウンメニューが必要なくなると嬉しい。


Java および Python APIの更新

チャネル API (Presence サポート)

アプリのインバウンドサービスについて、チャンネルAPIに対するユーザの「存在」を検知するように設定できるようになった。これにより、ユーザがチャンネルに接続したり切断したりした際に何らかのアクションを取ることができる。

イメージAPI(WebP サポート)

イメージAPIは、WebP画像をサポートするようになった。まだ知らない人に説明すると、WebP は Googleがオープンソースとして今年初旬に公開した新しい画像フォーマットで、同じ画質ならばJPEGよりも39%以上ファイルサイズを小さくすることができる。(ロスあり圧縮)


新しいPython API群

ProtoRPC
ProtoRPCは、「明確に定義された使いやすい WebベースのRPCサービス」を構築できるオープンソースフレームワークである。サービスを定義する上では、Google Protocol Buffersを使うのと似ているが、ProtoRPCの目的は、開発者がWebサービスの定義を簡単にはじめられるようにすることである。これらのサービスは、もちろんスケールすることができるし、時間とともに発展することもできる。

データストア

SDKでの高速応答
高速応答データストアのリリース以来、我々は、アプリの開発を通じて、開発者が新しい一貫性モデルを理解しテストすることを手助けするようなツールを提供したいと考えていた。JavaおよびPython向け1.5.1SDKは、HRD一貫性モデルをエミュレートできるようになった。これにより、適切なSDKオプションを設定することにより、エンティティグループを横断するクエリは、最後に書きこまれたデータを必ずしも反映しない結果を返すようになる。開発者は、この一貫性モデルに対して、アプリケーションをより回復力のあるものとすることができるだろう。

全てのリリースと同様、1.5.1は小さな追加的な機能や多くのバグフィクスを含んでおり、その全リストは、PythonおよびJava用のリリースノートに記載されている。できれば、このリリースが有用であってほしい。どちらにせよ、何か考えるところがあれば気軽にApp Engine groupを通じて我々に連絡してほしい。


投稿者:てらだ

2011年6月21日火曜日

Androidで ツリー表示可能なリストビュー

はじめまして。アクアキャスト島田です。
アンドロイド端末のアプリを作成している者です。

仕事でGUIを考えているとき、「ツリー表示ができるビューはないのか!?」と思い探してみました。
GoogleCodeで検索すると出てきたのですが、やはり自分で作ってしまおうと思い作ってみました。

まずはコアとなるアダプタクラスです。(ビュークラスはListViewを使います)

import java.util.ArrayList;
import java.util.List;
import android.widget.BaseAdapter;

public abstract class BaseTreeAdapter extends BaseAdapter {
    TreeEntry rootEntry = null; 

    public TreeEntry add(Object entry) {
        if(rootEntry == null) rootEntry = new TreeEntry();
        return rootEntry.add(entry);
    }

    @Override
    public int getCount() {
      return rootEntry == null ? 0 : rootEntry.getCount();
    } 

 @Override
 public Object getItem(int position) {
  // position=0はrootEntryとなってしまうため  
  // インクリメントする  
  return rootEntry == null ? null : rootEntry.getItem(position + 1) ; 
 } 

 @Override
 public long getItemId(int position) {
  //TreeEntry treeEntry = (TreeEntry)getItem(position);
  //return treeEntry.getId();
  return 0;
 } 

 public class TreeEntry {
  private List treeEntries = null;  
  private int depth = -1; // マイナスはルートオブジェクトのみ  

  private boolean isExpanded = false;
  private Object data = null;
  private TreeEntry() {   // ルートオブジェクト専用   
   isExpanded = true;
  }  

  private TreeEntry(TreeEntry parentEntry, Object data) {
   if(parentEntry != null) depth = parentEntry.depth + 1;
   this.data = data;
  }  

  public TreeEntry add(Object entry) {   
   if(treeEntries == null) treeEntries = new ArrayList();
   TreeEntry treeEntry = new TreeEntry(this, entry);
   treeEntries.add(treeEntry);
   return treeEntry;
  }  

  private int getCount() {
   if(treeEntries == null || !isExpanded) return 0;
   // 自分の持っているエントリの内、   
   // 開いている数を返す   
   int count = treeEntries.size();
   for(TreeEntry entry : treeEntries) count += entry.getCount();
   return count;  
  }  

  private TreeEntry getItem(int position) {
   if(position == 0) return this; // position=0は自分を返す
   if(treeEntries == null || !isExpanded) return null;
   for(int i = 0; position >= 0 && i < treeEntries.size(); ++ i) {
    TreeEntry entry = treeEntries.get(i);
    -- position;    // 子について、
    // エントリが取得できない場合、
    // 孫をpositionから引く  
    int count = entry.getCount();
    if(count >= position) {
     // 子以降で必ずアイテムが見つかる
     return entry.getItem(position);
    }
    position -= count;
   }
   // ここは、アイテム数より大きなpositionを
   // 指定された場合のみ
   return null;
  }  

  public void expand() {
   if(!hasChild()) return; // 子を持っていない場合、開けない
   if(isExpanded) return;
   isExpanded = true;
   notifyDataSetChanged();
  }

  public void collapse() {
   if(!isExpanded) return;
   isExpanded = false;
   notifyDataSetChanged();
  }

  public boolean isExpanded() {
   return isExpanded;
  }

  public int getDepth() {
   return depth;
  }
  
  public Object getData() {
   return data;
  }

  public boolean hasChild() {
   return treeEntries != null && !treeEntries.isEmpty();
  } 
 }

}

次に上記アダプタクラスから派生したクラスです。
public class MyTreeAdapter extends BaseTreeAdapter { 
 private LayoutInflater inflater = null;

 @Override
public View getView(int position, View convertView, ViewGroup parent) {
  if(convertView == null) {
   if(inflater == null) {
    inflater = (LayoutInflater)parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   }
   convertView = inflater.inflate(R.layout.entry, null);
  }

  TreeEntry treeEntry = (TreeEntry)getItem(position);
  TextView text = (TextView)convertView.findViewById(R.id.text);
  Object data = treeEntry.getData();
  String str = (String)data;
  text.setText(str);
  text.setPadding(treeEntry.getDepth() * 20, text.getPaddingTop(), text.getPaddingRight(), text.getPaddingBottom());

  return convertView;
 }
}

最後にアクティビティクラスです。

public class Main extends Activity { 
 private Map expandMap = new HashMap();

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  final ListView listView = (ListView)findViewById(R.id.list);

  listView.setOnItemClickListener(new OnItemClickListener() {

    @Override
    public void onItemClick(AdapterView parent, View view, int position, long id) {
     TreeEntry treeEntry = (TreeEntry)parent.getItemAtPosition(position);
     int depth = treeEntry.getDepth();
     if(treeEntry.isExpanded()) {
      expandMap.remove(depth);
      treeEntry.collapse(); 
     } else {
      if(treeEntry.hasChild()) {
       if(expandMap.containsKey(depth)) {
        expandMap.get(depth).collapse();
       }
       treeEntry.expand();
       expandMap.put(depth, treeEntry);
      }
     }
    }
   } );

  listView.setAdapter(new MyTreeAdapter());
  MyTreeAdapter adapter = (MyTreeAdapter)listView.getAdapter();

  TreeEntry root = adapter.add("1階層目-1");
  TreeEntry days = root.add("2階層目-1");
  days = root.add("2階層目-2");
  days.add("3階層目-1");
  days.add("3階層目-2");
  root = adapter.add("1階層目-2");
  days = root.add("2階層目-3");
  days.add("3階層目-3");
  days.add("3階層目-4");
  days.add("3階層目-5");
  days = root.add("2階層目-4");
  days.add("3階層目-6");
  days.add("3階層目-7");
  days.add("3階層目-8");
  days.add("3階層目-9");
 }
}

実行した画面をキャプチャーしました。

アダプタクラスだけで、できてしまいました。




投稿者:島田

2011年6月17日金曜日

Pythonのジェネレータと、RubyのFiber

Rubyは以前ちょっと仕事で使ってました。最近は GAE を齧ったりしているので Python を良く使っています。
私の頭のなかでは、両者はそんなに大きく違いません。使ってみた時の「感触」がとても近いと思っています。周辺技術も含めた実用性で少し Python が先行している感じでしょうか。

Rubyは使用頻度が減りましたが、いまも気になる言語です。Rubyist Magazineもちょくちょく覗いています。
今回は Rubyist Magazine からのネタです。下記の記事が非常に面白くて分かりやすい内容でした。
「関数型」と「手続き型」の発想を対比しながら Fiberと Procを説明しています。

Fiber と Proc ―― 手続きを抽象化する二つの機能

Python には「ジェネレータ」という便利な機能があります。これと同じことをしているのが Rubyの Fiberということになります。上記記事の説明によると、Fiberの概念(≒ジェネレータの概念)は関数型の発想ということになります。なるほど。

2つの対比的な概念を並行して説明することで理解しやすくする、というのは上手い説明の手法の一つですね。ジェネレータについていまいちすっきりしてない人は、上記を読んでみると参考になるかもしれません。

投稿者:てらだ

2011年6月16日木曜日

やはりAndroidのタッチ入力は、まだまだです:ムービーあり

開発でAndroid端末に触れることがよくありますが、どうしても気になるのは iPhone/iPad に較べてタッチ入力の感触が良くないことです。

この問題を定量的に調べた、ちょっと面白いデータを見つけたのでご紹介します。

ロボットアームを使って端末のスクリーン上を正確になぞり(ここでは斜めの線を繰り返し描いています)、
その軌跡が入力にどのように反映されるかを見ようというアプローチです。お絵かきソフト風のテストプログラムで入力結果を画像として見せています。

その結果が下記の画像です。



ご覧のとおり、iPhone以外の(一世代前ですが)Android端末の結果は悲惨です。
入力の位置精度も更新レート(毎秒何回位置情報を取り込めるか)も両方良くない感じですね。

新世代の端末や、Android3.xではもう少し改善していると思いますが・・・

下記のムービーは、その計測の様子です。




投稿者:てらだ

2011年6月15日水曜日

手元にあるcsvファイルをGAEにアップロードする方法

表題の件、覚書として書いておきます。

GAE(Google App Engine)のデータストアには2種類あります。
1.High Replication Datastore
2.Master/Slave Datastore

アプリケーション登録時に明示的に指定しないと1が自動的に選択されます。
一度選択してしまうと、後で変更はできません。

ここで示すデータのアップロードは1では動作しませんでした。
2を選択した場合のお話です(本当は1で動作させたいのだけれど)。


【1】以下のようなフォルダ構成を考えます。
    application/
       main.py
       app.yaml
       models.py
       bulkloader/
           link.csv
           loaders.py

main.pyはアプリ本体を記述したファイル、app.yamlは設定ファイルです。

【2】まず最初に、設定ファイルapp.yamlに以下3行を追加します(アプリ本体の設定は省略します)。
    - url: /remote_api
      script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py
      login: admin

ここで注意しなければならない行は
          script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py
です。$PYTHON_LIBは一字一句このまま記述しなければなりません。
勘違いして自分の環境変数に置き換えてはいけません(置き換えるとはまります)。

【3】link.csvは2つのカラムfromidとtoidを持ち、これらはint型であるとします。
このとき、models.py内に以下を実装します。

    from google.appengine.ext import db

    class Link(db.Model):
        fromid = db.IntegerProperty()
        toid = db.IntegerProperty()


【4】続いて、これに対応したloaderクラスをloaders.pyに実装します。

    from google.appengine.tools import bulkloader
    import sys
    import os.path
    sys.path.append(
            os.path.abspath(
                    os.path.dirname(
                            os.path.dirname(
                                    os.path.realpath(__file__)
                            )
                    )
            )
    )

    from models import *

    class LinkLoader(bulkloader.Loader):

        def __init__(self):

            bulkloader.Loader.__init__(
                self
              , "Link"
              , [
                    ("fromid", int)
                  , ("toid", int)
                ]
            )


    loaders = [LinkLoader]

models.pyが見えるように、sys.pathには1つ上のパスを追加しています(もっと良い書き方があると思うけど)。
末尾にグローバルな配列loadersを定義します。


【5】この2つのクラスがcsvからデータストアへの変換法則を決めます。
models.pyはアプリ本体でも参照するファイルなので、bulkloaderフォルダにはおかずにapplicationフォルダに
おきました。

【6】アプリ本体をアップロードします。
    $> appcfg.py update application

【7】次にデータをアップロードします。bulkloaderフォルダに入ったあと以下を実行します。

    $> appcfg.py upload_data --config_file=loaders.py --filename=link.csv \
      --kind=Link ..

    このコマンドは、link.csv内のデータを、クラスLinkとLinkLoaderを使って、bigtable内に格納します。

文責:くまだ