pyhaya’s diary

プログラミング、特にPythonについての記事を書きます。Djangoや機械学習などホットな話題をわかりやすく説明していきたいと思います。

GraphQL から GitHub API を叩く

最近、GitHub API を使ってリポジトリ情報を取ることが多いのですが、REST API より GraphQL を使ったほうが必要な情報だけを一気に取れるということを耳にし、試してみることにしました。

作業環境

Python の requests パッケージを使って GitHub API の graphql エンドポイントを叩きます。

準備

GraphQL を使う場合には Access Token が必須になるので発行しておきます。

docs.github.com

Query を作成

GraphQL はクエリの書き方が特殊なので REST API を普段使う人にとっては最初とっつきにくい感じを受けます。最初に指定できるのは Query という分類をされているもので、GitHub API のドキュメントに一覧が載っています。

developer.github.com

この中から欲しい情報を選ぶのが最初のステップです。ここでは user にしてみましょう。上のリンクの一番下に userの説明があります。変数として login のみを指定できるようですね。これは String 型です。試しに私の情報を取ってみます。

{
  user(login: "Hayashi-Yudai")
}

指定できました。これだけでは実行しても指定した情報が足りずエラーが出てしまいますので情報を追加していきます。userUser型を返すと書いてある(user の横に青文字で User と書いてある)のでリンクをクリックして User 型がどのような情報をもっているのか見てみます。

commitCommentsfollowers など色々あります。issuesというのがあるのでこれを取ってみましょう。引数として firstが取れるのでこれを10にして最初の10個を取ってみます。

{
  user(login: "Hayashi-Yudai") {
    issues(first: 10)
  }
}

issues の中には edgesnodesなどが入っています。こんな調子でどんどん深くまでたどっていくと、

{
  user(login: "Hayashi-Yudai") {
    issues(first: 10) {
      nodes {
        body
      }
    }
  }
}

というクエリを書くことができます。一番深いところにある body は型が String なのでこれ以上深いところには何もありません。

Query を実行する

では最後に上で作成したクエリを Python で実行してみます。

import requests
import os

headers = {"Authorization": f"Bearer {os.environ.get('GITHUB_TOKEN')}"}


def run(query): 
    request = requests.post(
        'https://api.github.com/graphql', 
        json={'query': query}, 
        headers=headers
    )
    if request.status_code == 200:
        return request.json()
    else:
        raise Exception(f"Query failed to run by returning code of {request.status_code}. {query}")

       
query = """
{
  user(login: "Hayashi-Yudai") {
    issues(first: 10) {
      nodes {
        body
      }
    }
  }
}
"""

result = run(query)

GitHub Access Token は私の場合には環境変数に指定しているので os.environ.getで取ってきてヘッダに入れています。result の中身を見てみると

{'data': {
  'user': {
    'issues': {
      'nodes': [
        {'body': 'When I run some codes, text garbling happened. I tried hello world program by\r\n\r\n* Python3\r\n* Java\r\n* C\r\n* C++ \r\n\r\nbut, only in C and C++, text garbling happened.'},
        {'body': 'In HTML, the width of `th` component is assigned like "5%", but it does not reflected in the page.'},
        {'body': 'add regularization and batch-normalization to transposed convolution layer'},
        {'body': 'add the pooling layer'},
        {'body': 'create UNet by components I prepared.'},
        {'body': 'To training UNet model, I should change input images to one-hot representation'},
        {'body': 'create training session'},
        {'body': 'To evaluate the model, I should set metrics such that accuracy or loss'},
        {'body': 'show training result as images'},
        {'body': 'To split training and validation of the model, I should give the is_training parameter when running the session.'}
      ]
    }
  }
}}

これを見てみると user -> issues -> nodes -> body という構造になっていてクエリで書いた構造と同じ構造でレスポンスが返ってきていることが分かります。

まとめ

GraphQL を使うと自分のほしい要素だけを取り出せるのでレスポンスがREST API よりスッキリしますね。さらに、上の例ですと、Issue の一覧からそれぞれの body を一回のクエリで取れていますが、REST API ですと users/Hayashi-Yudai/eventsで イベントの一覧を取ってからその中の type: "IssueEvent" のものを取ってそれぞれに対して対応する URL にリクエスト投げて。。。と非常にリクエスト回数が多くなります。アプリケーションの中で使うときにはレスポンスの改善に非常に効果がありそうです。