目的

Backlogの整理のために公開されているAPIをPythonから使いました。
その備忘録として。

余談

気づけば年末。
話の流れで私が掃除を担当する場所はBacklogになりました。
日頃お世話になっている課題管理システム、色々と溜まっています。

やりたかったこと

担当者未設定の課題チケットの抽出と新担当者の割り当て処理の自動化。
主観で勝手に片付けることはできないので、まずは責任を明確にするために、担当者の割当を行うことに。
ざっと見たところ、担当者未設定のチケットが数十あり、手動で入れていくのめんどくさいと判断。ツールを使って自動でやりたい。

ソース解説

コマンド設定

setup.py
from setuptools import setup

setup(
    name='',
    version='1.0.0',
    author='作った人の名前',
    author_email='連絡先めーるあどれす',
    install_requires=[
        'requests==2.11.1',
        'docopt==0.6.2'
    ],
    entry_points="""\    #←←←←←←←←こーこ!!!!!!!!!!!!
      [console_scripts]
      wariate = update_assignee_id:main
      """,
)

setup.pyには色々便利な機能があって、そのうちの一つがentry_points。
Pyramidのsetup.pyのpserveと同じで、これでわざわざpythonコマンドを実行しなくて良くなる。
細かなものだけど、pythonファイルの名前が長かったり、階層が深かったりした時に便利。

例)
entry_pointsを設定せずにツールを実行する場合、アクティベートしてから

(venv)python /User/tool/backlog/update_assignee_id.py --api-key=uiwejaefdhpasid8fpa8etw3to

entry_pointsを設定してにツールを実行する場合、アクティベートしてから
(venv)wariate --api-key=uiwejaefdhpasid8fpa8etw3to

オプションのパーサ

update_assignee_id.py
"""execute update assignee id

usage: wariate (--api-key=<api-key>)

options:
    --api-key=<api-key>     api key
"""

from docopt import docopt

def main():
    args = docopt(__doc__)
    update_assignee_id(args['--api-key'])

Pythonファイル先頭のドキュメントとメイン関数だけをピックアップ。
こう書いておくと、引数の必須チェックをしてくれる。
他にもいろいろ便利な機能があるので、使うといいよ。
他のパーサと違ってドキュメントを参照するので、ドキュメントだけメンテナンスされずに放置されるなんて事態が避けられるのも強み。

APIの実行

update_assignee_id.py

import json
import requests

__API_KEY = '?apiKey={}'
__BASE_END_POINT = 'https://domein.backlog.jp/api/v2/'
__PROJECT_END_POINT = __BASE_END_POINT + 'projects'

def get_project_ids(api_key):
    r = requests.get(__PROJECT_END_POINT + __API_KEY.format(api_key))
    projects = json.loads(r.text)
    return list(map(lambda project: project['id'], projects))

プロジェクトのIDのリストを取得する部分をピックアップ。
やっぱりrequestsは一押しで、これを使うと直感的にかけて人間が読めるソースになりやすい。
ついでに、このソースはPython3.5で動作確認している。Python2系だったらmap関数の戻り値の型はリストだったような気がするので、2系だった場合、listへの型変換は必要なかったような気がする。多分。

担当者未設定のレコードをピックアップ

update_assignee_id.py

__GET_ISSUE_QUERY = {
    'statusId[]': [1, 2, 3],
    'sort': 'assignee',
    'order': 'asc'
}
__ISSUE_END_POINT = __BASE_END_POINT + 'issues'

def get_issues(api_key, project_ids):
    is_continue = True
    params = __GET_ISSUE_QUERY
    params['projectId[]'] = project_ids
    while is_continue:
        r = requests.get(__ISSUE_END_POINT + __API_KEY.format(api_key), params=params)
        issues = json.loads(r.text)
        for issue in issues:
            if issue['assignee'] is None:
                yield issue
            else:
                is_continue = False
                break

特定の担当者を指定して課題のリストを取得することはできるが、担当者が未設定の課題を意図してピックアップすることはできない(APIの仕様)。なので、あえて担当者は絞らず、担当者を昇順ソートして、担当者未設定の課題を最初に持ってくるようにして対応した。

patchとは

update_assignee_id.py

def update_issue(issue, api_key):
    base_url = '/'.join([__ISSUE_END_POINT, issue['issueKey']]) + __API_KEY.format(api_key)
    params = {
        'assigneeId': issue['createdUser']['id']
    }
    r = requests.patch(base_url, params)
    if r.status_code != 200:
        raise Exception('update error occurred:{}'.format(issue['issueKey']))
    print('updated:{}'.format(issue['issueKey']))


こうしておくと、担当者を作成者で更新できる。
恥ずかしながら、patchというリクエスト区分を初めて知った。postとの使い分けとしては
post→全体更新
patch→一部更新
らしい。勉強になった。

最後に

そんな弊社ですが、もし興味がありましたら、Qiitaの弊社技術者ブログトップページの下の方から連絡いただけましたら嬉しいです。お待ちしています!