読者です 読者をやめる 読者になる 読者になる

HPCメモ

HPC(High Performance Computing)に関連したりしなかったりすることのメモ書き

python3でtrelloに登録したtask(=カード)を読み出す

googleカレンダーのお気に入りの機能のひとつに、「毎日指定した時間にその日の予定をメールで送ってくる」というのがあります。
気分的には、オフィスのエレベーターを降りて自席まで歩いている間に、クリップボードを持った秘書さんが付いてきて今日の予定を読み上げてくって感じ*1なんですが、
残念ながら我々エンジニアはカレンダーに入っているスケジュール済タスクだけをチェックしていると、色々と破綻します。

私の場合は、カレンダーの他にプロジェクト毎の課題管理のためにtrelloを使っていて案件毎に個別のボードにカンバン方式でタスクを登録しています。
ところが、trelloでボードをまたいでカードを見ようとすると自分にアサイン済のカード一覧を見るしかないので、BacklogからTodoへ(もしくはWIPへ)カードを移動させては自分へアサインし、Doneリストへ移動させてはアサインを外すという面倒なことをやっていました。
しばらくはこの方式で頑張ってたんですが、割り込みタスクのカードを作ったりすると徐々に破綻していって、今となっては自分にアサインされたカードの一覧を見ても入っていないタスクの方が多い有様です。

というわけで、一念発起して自分の担当案件のうち、作業中のタスクとToDo項目をtrelloから取得して表示するプログラムを作ってみました。

基本方針

  • python3で作成
  • urllibで頑張る(requestは使わない)
  • 出力先はとりあえずコンソール

trelloのカードを取得する準備

trelloのカードを取得するためには、APIキーとTokenを用意する必要があります。
まずは、次のURLへアクセスします。
https://trello.com/app-key
一番上に「キー:」と書かれた下に文字列があるので、これをメモっておきます。
続いて、「Token:」と書かれたセクションに"Token"というリンクがあるので、ここをクリックするといつもの認証画面に遷移します。
f:id:n_so5:20160906212740p:plain
ここで許可とすると

You have granted access to your Trello information.

To complete the process, please give this token:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

という形でトークンの文字列が表示されるので、最後の行の文字列をメモっておきます。
以降のスクリプトでは、鍵をtrello_api_keyという変数に、トークンをtrello_tokenという変数に保存している前提で書きます。

ボードの一覧を取得

ボードの一覧を取得するには、次のAPIを呼びます。
https://developers.trello.com/advanced-reference/member#get-1-members-idmember-or-username-boards

コードで書くとこんな感じになります。

import urllib.request
import json

boards=[]
with  urllib.request.urlopen("https://api.trello.com/1/members/me/boards?key={}&token={}&filter=open&fields=name".format(trello_api_key, trello_token)) as r:
    tmp=r.read().decode('utf-8')
    boards=json.loads(tmp)

optionとして渡している文字列のうち、filter=openはOpenなボードのみ取得することを、fields=nameはボードの名前とidのみを取得することを意味しています。
これで、boardsには各ボードのidと名前を格納した辞書のリストが入ります。

次に進む前に、trelloのAPIを叩いて返ってきたjsonpythonのオブジェクトに変換して返すところまでをクラスにしておきましょう。

class TrelloClient:
    def __init__(self, api_key, token):
        self.api_key=api_key
        self.token=token
        self.base_url="https://api.trello.com/1"
    def fetch(self, category, api, target_id, option=""):
        url='{}/{}/{}/{}?key={}&token={}{}'.format(self.base_url,category,target_id,api,self.api_key,self.token,option)
        with  urllib.request.urlopen(url) as r:
            return json.loads(r.read().decode('utf-8'))

tc=TrelloClient(trello_api_key, trello_token)
boards=tc.fetch(category="member", api="boards", target_id="me", option="&filter=open&fields=name")

Taskの取得

ここまででボードのID(と名前)の一覧が取得できたので、次は各ボードに対してこちらのAPIを使ってリストの一覧を取得します。
https://developers.trello.com/advanced-reference/board#get-1-boards-board-id-lists
この時オプション引数のcardsとcard_fieldsを使うと、各リストに含まれるカードの一覧も取得できるので、これを使ってまとめてボード内のカードを取得します。ただし、この方法で取得すると全てのリストに入っているカードが返ってきてるので、TodoかWIPのリストに入っているカードだけを後から抽出する必要があります。
コードはこんな感じになります。(前のコードの続きです。)

todo={}
wip={}
for board in boards:
    lists=tc.fetch(category="boards", target_id=board["id"], api="lists", option="&filter=open&fields=name&cards=open&card_fields=name")
    for list in lists:
        if len(list['cards']) > 0:
            if list["name"] in ("ToDo", "todo", "Todo", "To do", "to do"):
                    todo[board["name"]]=list['cards']
            if list["name"] in ("WIP", "W.I.P.", "W.I.P", "Doing", "doing"):
                    wip[board["name"]]=list['cards']

リスト名の表記ゆれを考慮して一応それっぽい名前のものは全部対象のリストとみなすようにしてあります。

出力

こんな感じで取得したTodoとWIPのカード名を順にprintしていきます。

print("現在作業中のタスク")
for project, tasks in wip.items():
    print(project,":")
    for task in tasks:
        print("  -",task['name'])
    print("")

print("Todo項目")
for project, tasks in todo.items():
    print(project,":")
    for task in tasks:
        print("  -",task['name'])
    print("")

おまけ

このコードでは、ボード内の全カードを取得してから、不要な(対象外のリストに入っている)カードの除いていますが、trello APIで同時に取得できるアイテムの上限は1000なので1000以上のカードが入っているボードがあると全部のtaskを拾うことができません。こういう状況が起き得る場合は、ボード内のリストを取得する時に以下のように一旦リスト名の一覧を取得してから、必要なリストのカードを改めて取得する必要があります。

for board in boards:
    lists=tc.fetch(category="boards", target_id=board["id"], api="lists", option="&filter=open&fields=name)
    for list in lists:
        cards=tc.fetch(category="lists", target_id=list["id"], api="cards", option="&filter=open&fields=name")

私の環境だと、こっちの方がだいたい倍くらい遅かったんですが、もともと6~7秒で終わっていたのが10秒程度に伸びるくらいの感覚なので今のところこっちを使っています。

*1:洋画とかでよくやってるイメージ