TaskSphere:GitHub プロジェクトにドラフトIssueを追加する Python スクリプト

AI

こんにちは!今日は、GitHub の新しい Project V2 API を使って、プロジェクトにドラフトIssueを追加する Python スクリプトを一緒に作っていきましょう。

デモ動画

必要な環境変数

このスクリプトを実行するには、以下の環境変数が必要です。

  • GITHUB_PERSONAL_ACCESS_TOKEN: GitHub の Personal Access Token
  • GITHUB_USER_LOGIN: GitHub のユーザーログイン名
  • GITHUB_PROJECT_NUMBER: 対象のプロジェクト番号

これらの環境変数は、スクリプトを実行する前に設定しておいてください。

インポートと環境変数の取得

import os
import requests
from termcolor import colored
from art import *

script_name = os.path.basename(__file__)
tprint(script_name)

token = os.environ.get("GITHUB_PERSONAL_ACCESS_TOKEN")
user_login = os.environ.get("GITHUB_USER_LOGIN")
project_number = os.environ.get("GITHUB_PROJECT_NUMBER")

ここでは、必要なライブラリをインポートし、環境変数から必要な情報を取得しています。

  • os: ファイル名を取得するために使用
  • requests: GitHub API にリクエストを送信するために使用
  • termcolor: 出力をカラフルにするために使用
  • art: スクリプト名をアスキーアートで表示するために使用

環境変数の検証

def validate_environment_variables():
    if token is None or user_login is None or project_number is None:
        print("Error: Required environment variables are not set.")
        exit(1)

validate_environment_variables() 関数は、必要な環境変数が設定されているかどうかを確認します。設定されていない場合は、エラーメッセージを表示してスクリプトを終了します。

プロジェクト情報の取得

def get_project_info():
    query_project = """
    query {
      user(login: "USER_LOGIN") {
        projectV2(number: NUMBER) {
          title
          id
        }
      }
    }
    """
    query_project = query_project.replace("USER_LOGIN", user_login)
    query_project = query_project.replace("NUMBER", project_number)

    data_project = {
        "query": query_project
    }

    response_project = requests.post("https://api.github.com/graphql", headers=headers, json=data_project)

    if "errors" in response_project.json():
        print(f"Error: {response_project.json()['errors'][0]['message']}")
        exit(1)

    project_title = response_project.json()["data"]["user"]["projectV2"]["title"]
    project_id = response_project.json()["data"]["user"]["projectV2"]["id"]

    print(colored(f"Project Title: {project_title}", "green"))
    print(colored(f"Project ID: {project_id}", "cyan"))

    return project_id

get_project_info() 関数は、指定されたプロジェクトの情報を取得します。

  1. GraphQL クエリを定義し、USER_LOGINNUMBER をそれぞれ user_loginproject_number に置き換えます。
  2. クエリを実行し、レスポンスをチェックします。エラーがある場合は、エラーメッセージを表示してスクリプトを終了します。
  3. レスポンスからプロジェクトのタイトルとIDを取得し、表示します。
  4. プロジェクトIDを返します。

プロジェクト内のアイテムの取得

def get_project_items(project_id):
    query_items = """
    query {
      node(id: "PROJECT_ID") {
        ... on ProjectV2 {
          items(first: 20) {
            nodes {
              id
              content {
                ... on DraftIssue {
                  title
                  body
                }
                ... on Issue {
                  title
                  number
                  assignees(first: 10) {
                    nodes {
                      login
                    }
                  }
                }
                ... on PullRequest {
                  title
                  number
                  assignees(first: 10) {
                    nodes {
                      login
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    """
    query_items = query_items.replace("PROJECT_ID", project_id)

    data_items = {
        "query": query_items
    }

    response_items = requests.post("https://api.github.com/graphql", headers=headers, json=data_items)

    if "errors" in response_items.json():
        print(f"Error: {response_items.json()['errors'][0]['message']}")
        exit(1)

    items = response_items.json()["data"]["node"]["items"]["nodes"]

    print("---------------------------")
    print(colored("Items:", "magenta"))
    for item in items:
        print(colored(f"Item ID: {item['id'][:6]}_XXXXXXXXXXXX", "cyan"))

        content = item["content"]
        if "title" in content:
            print(colored(f"Title: {content['title']}", "green"))
        if "body" in content:
            print(colored(f"Body: {content['body']}", "green"))
        if "number" in content:
            print(colored(f"Number: {content['number']}", "green"))

        if "assignees" in content:
            assignees = [assignee["login"] for assignee in content["assignees"]["nodes"]]
            print(colored(f"Assignees: {', '.join(assignees)}", "green"))

        print("---")

get_project_items() 関数は、指定されたプロジェクト内のアイテムを取得します。

  1. GraphQL クエリを定義し、PROJECT_ID をプロジェクトIDに置き換えます。
  2. クエリを実行し、レスポンスをチェックします。エラーがある場合は、エラーメッセージを表示してスクリプトを終了します。
  3. レスポンスからアイテムのリストを取得します。
  4. 各アイテムの情報(ID、タイトル、本文、番号、担当者)を表示します。

プロジェクトにドラフトIssueを追加

def add_draft_issue_to_project(project_id):
    draft_title = input("Enter the title for the new draft issue: ")
    draft_body = input("Enter the body for the new draft issue: ")

    mutation_add_draft_issue = """
    mutation {
      addProjectV2DraftIssue(input: {projectId: "PROJECT_ID", title: "TITLE", body: "BODY"}) {
        projectItem {
          id
        }
      }
    }
    """
    mutation_add_draft_issue = mutation_add_draft_issue.replace("PROJECT_ID", project_id)
    mutation_add_draft_issue = mutation_add_draft_issue.replace("TITLE", draft_title)
    mutation_add_draft_issue = mutation_add_draft_issue.replace("BODY", draft_body)

    data_add_draft_issue = {
        "query": mutation_add_draft_issue
    }

    response_add_draft_issue = requests.post("https://api.github.com/graphql", headers=headers, json=data_add_draft_issue)

    if "errors" in response_add_draft_issue.json():
        print(f"Error: {response_add_draft_issue.json()['errors'][0]['message']}")
    else:
        added_draft_issue_id = response_add_draft_issue.json()["data"]["addProjectV2DraftIssue"]["projectItem"]["id"]
        print(colored(f"Added draft issue with ID: {added_draft_issue_id}", "green"))

add_draft_issue_to_project() 関数は、指定されたプロジェクトにドラフトIssueを追加します。

  1. ユーザーにドラフトIssueのタイトルと本文の入力を求めます。
  2. GraphQL ミューテーションを定義し、PROJECT_IDTITLEBODY をそれぞれプロジェクトID、ドラフトIssueのタイトル、ドラフトIssueの本文に置き換えます。
  3. ミューテーションを実行し、レスポンスをチェックします。エラーがある場合は、エラーメッセージを表示します。
  4. レスポンスから追加されたドラフトIssueのIDを取得し、表示します。

メイン処理

if __name__ == "__main__":
    validate_environment_variables()

    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }

    project_id = get_project_info()
    get_project_items(project_id)
    add_draft_issue_to_project(project_id)

メイン処理では、以下のステップを実行します。

  1. 環境変数を検証します。
  2. リクエストヘッダーを設定します。
  3. プロジェクト情報を取得します。
  4. プロジェクト内のアイテムを取得します。
  5. プロジェクトにドラフトIssueを追加します。

これで、GitHub プロジェクトにドラフトIssueを追加する Python スクリプトの説明は以上となります。このスクリプトを実行することで、GitHub の Project V2 API を使ってプロジェクトにドラフトIssueを簡単に追加することができます。

実行方法

  1. 必要な環境変数を設定します。

    • GITHUB_PERSONAL_ACCESS_TOKEN: GitHub の Personal Access Token
    • GITHUB_USER_LOGIN: GitHub のユーザーログイン名
    • GITHUB_PROJECT_NUMBER: 対象のプロジェクト番号
  2. スクリプトを実行します。

    python script_name.py
  3. スクリプトが実行されると、以下のような出力が表示されます。

    Project Title: My Project
    Project ID: PVT_kwDOABCD1234567890
    ---------------------------
    Items:
    Item ID: ABC123_XXXXXXXXXXXX
    Title: Issue 1
    Number: 1
    Assignees: user1, user2
    ---
    Item ID: DEF456_XXXXXXXXXXXX
    Title: Pull Request 1
    Number: 2
    Assignees: user3
    ---
    Enter the title for the new draft issue: Draft Issue 1
    Enter the body for the new draft issue: This is a draft issue.
    Added draft issue with ID: GHI789_XXXXXXXXXXXX
  4. プロジェクトにドラフトIssueが追加されたことを確認します。

注意点

  • このスクリプトを実行するには、適切な権限を持つ Personal Access Token が必要です。トークンには、read:orgproject のスコープが必要です。
  • プロジェクト番号は、プロジェクトの URL から取得できます。例えば、https://github.com/orgs/my-org/projects/1 の場合、プロジェクト番号は 1 になります。
  • このスクリプトは、プロジェクト内の最初の 20 個のアイテムのみを取得します。より多くのアイテムを取得するには、items(first: 20) の数値を変更してください。

全体コード

import os
import requests
from termcolor import colored
from art import *

script_name = os.path.basename(__file__)
tprint(script_name)

token = os.environ.get("GITHUB_PERSONAL_ACCESS_TOKEN")
user_login = os.environ.get("GITHUB_USER_LOGIN")
project_number = os.environ.get("GITHUB_PROJECT_NUMBER")

def validate_environment_variables():
    if token is None or user_login is None or project_number is None:
        print("Error: Required environment variables are not set.")
        exit(1)

def get_project_info():
    query_project = """
    query {
      user(login: "USER_LOGIN") {
        projectV2(number: NUMBER) {
          title
          id
        }
      }
    }
    """
    query_project = query_project.replace("USER_LOGIN", user_login)
    query_project = query_project.replace("NUMBER", project_number)

    data_project = {
        "query": query_project
    }

    response_project = requests.post("https://api.github.com/graphql", headers=headers, json=data_project)

    if "errors" in response_project.json():
        print(f"Error: {response_project.json()['errors'][0]['message']}")
        exit(1)

    project_title = response_project.json()["data"]["user"]["projectV2"]["title"]
    project_id = response_project.json()["data"]["user"]["projectV2"]["id"]

    print(colored(f"Project Title: {project_title}", "green"))
    print(colored(f"Project ID: {project_id}", "cyan"))

    return project_id

def get_project_items(project_id):
    query_items = """
    query {
      node(id: "PROJECT_ID") {
        ... on ProjectV2 {
          items(first: 20) {
            nodes {
              id
              content {
                ... on DraftIssue {
                  title
                  body
                }
                ... on Issue {
                  title
                  number
                  assignees(first: 10) {
                    nodes {
                      login
                    }
                  }
                }
                ... on PullRequest {
                  title
                  number
                  assignees(first: 10) {
                    nodes {
                      login
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    """
    query_items = query_items.replace("PROJECT_ID", project_id)

    data_items = {
        "query": query_items
    }

    response_items = requests.post("https://api.github.com/graphql", headers=headers, json=data_items)

    if "errors" in response_items.json():
        print(f"Error: {response_items.json()['errors'][0]['message']}")
        exit(1)

    items = response_items.json()["data"]["node"]["items"]["nodes"]

    print("---------------------------")
    print(colored("Items:", "magenta"))
    for item in items:
        print(colored(f"Item ID: {item['id'][:6]}_XXXXXXXXXXXX", "cyan"))

        content = item["content"]
        if "title" in content:
            print(colored(f"Title: {content['title']}", "green"))
        if "body" in content:
            print(colored(f"Body: {content['body']}", "green"))
        if "number" in content:
            print(colored(f"Number: {content['number']}", "green"))

        if "assignees" in content:
            assignees = [assignee["login"] for assignee in content["assignees"]["nodes"]]
            print(colored(f"Assignees: {', '.join(assignees)}", "green"))

        print("---")

def add_draft_issue_to_project(project_id):
    draft_title = input("Enter the title for the new draft issue: ")
    draft_body = input("Enter the body for the new draft issue: ")

    mutation_add_draft_issue = """
    mutation {
      addProjectV2DraftIssue(input: {projectId: "PROJECT_ID", title: "TITLE", body: "BODY"}) {
        projectItem {
          id
        }
      }
    }
    """
    mutation_add_draft_issue = mutation_add_draft_issue.replace("PROJECT_ID", project_id)
    mutation_add_draft_issue = mutation_add_draft_issue.replace("TITLE", draft_title)
    mutation_add_draft_issue = mutation_add_draft_issue.replace("BODY", draft_body)

    data_add_draft_issue = {
        "query": mutation_add_draft_issue
    }

    response_add_draft_issue = requests.post("https://api.github.com/graphql", headers=headers, json=data_add_draft_issue)

    if "errors" in response_add_draft_issue.json():
        print(f"Error: {response_add_draft_issue.json()['errors'][0]['message']}")
    else:
        added_draft_issue_id = response_add_draft_issue.json()["data"]["addProjectV2DraftIssue"]["projectItem"]["id"]
        print(colored(f"Added draft issue with ID: {added_draft_issue_id}", "green"))

if __name__ == "__main__":
    validate_environment_variables()

    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }

    project_id = get_project_info()
    get_project_items(project_id)
    add_draft_issue_to_project(project_id)

まとめ

この記事では、GitHub の Project V2 API を使ってプロジェクトにドラフトIssueを追加する Python スクリプトを作成しました。このスクリプトを使えば、プロジェクトの管理がより簡単になります。

GitHub API には他にも多くの機能があるので、このスクリプトをベースにして、自分のニーズに合わせてカスタマイズしていくことをお勧めします。

Happy coding!

リポジトリ

GitHub - Sunwood-ai-labs/TaskSphere
Contribute to Sunwood-ai-labs/TaskSphere development by creating an account on GitHub.

参考サイト

API を使用して Projects を管理する - GitHub Docs
GraphQL API を使用して、プロジェクトを自動化できます。
GitHub GraphQL APIを利用し、複数リポジトリが紐づくGitHub ProjectsのIssueリストを取得する - いいものを広め隊
はじめに 今回想定するGitHub Projects GitHub GraphQL APIを使うまでの準備 1. アクセストークンの登録 2. Issueを取得したいGitHub Projectsのidを取得する 今回はPythonで実行します GitHub Projectsに紐づくIssue一覧を取得する 特定のステ...
個人用アクセス トークンを管理する - GitHub Docs
コマンド ラインまたは API を使用して GitHub への認証を行うときに、パスワードの代わりに personal access token を使用することができます。

コメント

タイトルとURLをコピーしました