7rikazhexde’s tech log

技術的な興味関心、備忘録、アウトプットなどを書いています。

【Windows】PowerShellスクリプトを実行するショートカットを作成するスクリプトについて

経緯

ChatGPT等の生成AIにより、PowerShellスクリプトシェルスクリプトを作成する機会が増えました。一方でスクリプトの実行には、ターミナルアプリやPowerShellコマンドで実行する必要があります。

都度実行するのは手間ですが、WindowsではPowerShellスクリプトを実行するショートカットを作成できます。しかし、作成するためにいくつか操作が必要です。

そこで、PowerShellスクリプトの実行をより簡単に作成するためのGUIツールを作成しましたので、本記事で紹介します。

スクリプトと内部処理について

スクリプトの概要

スクリプト名は create-ps1-shortcut.ps1 で、GUIインターフェースを通じて以下の機能を提供します。

  • ファイル選択: Show-FileDialog関数を使用して.ps1ファイルを選択。
  • ショートカット作成: New-ScriptShortcut関数で、選択したスクリプトと同じディレクトリにショートカット(.lnk)を生成。
  • カスタムダイアログ: Show-CustomDialog関数で、作成完了後ガイドを表示し、保存先を開くオプションを提供。

また、スクリプトは下記GitHubにもコミットしています。

github.com

内部処理の詳細

  • PowerShell環境の検出: Get-PowerShellPath関数で、PowerShell Core(pwsh)が利用可能か確認し、なければ powershell.exeにフォールバック。
  • WScript.Shellの利用: COMオブジェクトを使用してショートカットを作成し、ターゲットパスや引数を設定。
  • WPFベースのGUI: XAMLを使用してカスタムダイアログを生成し、ボタンイベントで保存先を開く機能を実装。
  • デバッグ対応: -Debugパラメータで詳細ログを表示可能。

使用方法

create-ps1-shortcut.ps1 > 右クリック > PoweShellで実行 > ショートカットを作成したいPowerShellスクリプトを選択

PowerShellスクリプトショートカット作成結果

まとめ

WindowsPowerShellスクリプトを実行するショートカットを作成するPowerShellスクリプトについて紹介しました。

生成AIを活用することで、アプリやスクリプトが作成しやすくなりましたが、それを効率的に実行していく方法も必要だと思います。本スクリプトがそのようなケースの一助となれば幸いです。

以上です。

iOSショートカットアプリでDiscordにWebhook経由でメッセージを送る方法

はじめに

個別に電車の運行情報を通知するShortcutを作成しました。取得した情報はShortcutsアプリの通知アクション(バナー通知)などで通知可能ですが、ネットの情報やGitHub Actionsの通知などはDiscordで個別に作成したチャンネルにまとめているため、電車の運行情報もDiscordで通知管理することを考えました。

そして、Discord通知は電車の運行情報以外にも使用できると考えたため、通知部分のみ個別のShortcutとして切り出すことにしました。本記事はその方法とShortcutについての紹介記事になります。

免責事項

本記事に記載された内容やShortcutによって生じたいかなる損害についても責任を負いません。使用する際は自己責任でお願いします。

Discordの設定(Webhook)

Discordで通知先のチャンネルでWebhookを作成し、そのURLを使用します。

Webhookについては以下の記事を確認ください。

kintone-blog.cybozu.co.jp

Webookの設定は以下のDiscordの公式ドキュメントを確認ください。

support.discord.com

Shortcutsアプリの設定

以下内容でheaderとbodyを指定してWebhook URLにPOST送信します。 Shortcutsのアプリでは、URLの内容を取得アクションではキーテキスト指定で設定します。

  • header: Key(Content-Type),Value(application/json)
  • body: Key(Content),Value(送信したいテキスト)

指定内容としては以下記事が参考になりますので合わせて確認ください。(Discord公式のDocsのURLも記載されています。)

qiita.com

作成したShortcutの実行結果例

Discord通知ショートカット(Notify Discord (Webhook))

取得したWebhook URLはテキストに入力します。

Notify Discord (Webhook)

電車運行情報通知ショートカット

Discordに通知したいメッセージ(テキスト)を電車運行情報通知ショートカットの入力としてアクションを設定します。

電車運行情報通知ショートカット

Discordへの通知結果

入力されたテキストがDiscordのチャネルに投稿されていることを確認できました。

Discord / train-info-bot

まとめ

iOSショートカットアプリでDiscordにWebhook経由でメッセージを送る方法を紹介しました。

紹介した内容ではテキストのみで内容をリッチにしなくても良いので最低限の情報のみで紹介しました。

汎用性を考えれば、headerとbodyの内容を選択式にすると良いかもしれません。

以上です。

Shortcutsアプリを使用したObsidianのノート作成方法について

免責事項

本記事に記載された内容やShortcutによって生じたいかなる損害についても責任を負いません。使用する際は自己責任でお願いします。

前置き

Obsidianを利用されている方は、ノートをどのように作成し、管理していますか?
多くの場合は保存先のフォルダを作成し、新規ノートから作成しているのではないでしょうか?

一方で、フォルダ管理しない方が良いという主張もあります。
個人でノートを管理している方はわかるかもしれませんが、フォルダ作成のルールを決めないと、管理が煩雑になったり、重複したりして、正しくノートを管理できなくなる場合があります。

私はどうしているかというと、性格的にフォルダ管理しないと気持ちがわるく、フォルダを作成して、ノートを作成/管理しています。

また、Obsidianでは、タグ管理ができるので、タグ指定で検索してファイルを探すことができます。また、ノートのリンク機能によりノート同士を関連付けることができるため、それらの機能と併用することでノート管理の一貫性を担保できます。詳細は公式ドキュメントを参照ください。

publish.obsidian.md

タグ作成も、以下の記事で紹介しているテンプレート機能を使用すれば簡単に作成できるのでお勧めです。

7rikazhexde-techlog.hatenablog.com

さて、ここまで前置きが長くなりましたが、ここからが本題です。

上記のように機能は良いのですが、しばらく使用していたところ、
フォルダが増えると階層が深くなるのでノート作成がしづらいという不満に当たりました。

これはフォルダ管理の性質であり、ドキュメント管理の仕様なので仕方がないことですが、Obsisidanではノート作成すると一番上の階層で固定となっているので、フォルダ管理する場合は、ノートを指定のフォルダに移動しなければならずストレスを感じることがありました。

つまり、一部のUXが悪いのです。もしかすると設定やプラグインで変更できるかもしれませんが、選択や移動させることは機能として問題はないので、仕方ないことだとは思ってます。

そこで、ノート作成方法(操作)を良くすることできないかと考えたところ、Shortcutsアプリを使用することで実現できました。

本記事では実現方法と実際に作成したShortcutを公開しましたので紹介します。

Obsidianのノート作成方法をShortcutsアプリを使用して改良する方法について

結論から言うと、Shortcutsアプリを使用して空のMarkdownファイルを作成し、それを指定のフォルダに保存する方法で実現しました。

Obsidian VaultをiCoudやiPhone/iPad内に設定している場合は、フォルダアプリでアクセス可能1なため、保存されたMarkdownファイルはObsidianアプリ上でも確認できます。

方法自体はシンプルですが、Obsidianでノート作成しないのでファイル移動などしなくて済む点がメリットです。

処理内容(キャプチャ)

キャプチャの通りですが、ポイントは空のテキスト指定でファイル作成し、拡張子を.txtから.mdに変更している部分です。

ファイル作成ではデフォルトでは.txtとなるのでそこだけ変換して保存するアクションで対応しています。

また、保存処理では、保存アクションを使用してテキスト指定で、保存先を訪ねるを有効にします。

これでフォルダアプリから保存先を指定することで、作成時に保存先を指定し、保存後のファイル移動を省略しています。

Create_Markdown_Shortcut

Shortcut(iCloudリンク)

作成したshortcutは下記iCloudリンクから取得可能です。気になれば使用してみてください。

※リンクから追加画面に遷移しない場合はメモにURLを貼り付けてからアクセスしてください。

www.icloud.com

まとめ

Shortcutsアプリを使用したObsidianのノート作成方法について紹介しました。

公開したショートカットをホーム画面などに追加すれば、即起動できるので使い勝手は良いと思います。

1点不満な点として、shortcutの機能で作成したノートをObsidianから起動できれば良かったんだですが、そこまでは実現できませんでした。

それでもObsidianアプリからノート移動よりは楽なので許容範囲です。

Obsidianアプリは気に入っているので、今後も改良できる機能やプラグインなど考えたい思います。

以上です。


  1. ⚠️Macでは未確認です。

a-Shellとobsidian-gitを使用してプライベートリポジトリで管理するMarkdownファイル(Mkdocs)をマルチデバイス(Windows/Mac/iPhone/iPad)で更新する方法

経緯

  • 技術系のTips記事(静的サイト)をMaterial for Mkdocs(以降MkDocsと表現する)で作成したドキュメント(Markdown)をGitHubで管理し、Netlifyでデプロイすることでwebサイトとして公開していた。
  • 個人の作業ログやメモはObsidianアプリを使用して管理していた。
  • データはWindows/Mac/iPhone/iPadで参照、更新するため、データの保存先(保管庫:Obsidian vault)はiCloudを指定して保存していた。
  • MkDocsはWebページとして生成されるが、materialテーマにより統一的なデザインで構成されるためスマートフォンやPCで見やすい。ただし、ドキュメント更新は主にPCでVSCodeから行う必要があった。
  • 文章量が多い場合はPCでも良いが、毎回起動するのは手間だと感じていた。そこで、スマートフォンでもドキュメントを更新する方法がないか検討していた。
  • 一例として、はてなブログでは、スマートフォン向けもアプリが公開されており、PC、スマートフォンで両方で更新可能な例も存在する。

仕様検討

要件

  • ドキュメント表示はMkDocs、更新はObsidian、または、VSCodeとする。
  • ドキュメント(コード)はGitで管理し、セキュリティを考慮してGitHubのプライベートリポジトリで管理する。
  • 費用はなるべく抑えたい。希望は無料で利用できること。

技術選定

  • Obsidianでは、ウェブサイト上に公開するサービスとしてObsidian Publishが提供されているが、月額制のサービスであるため対象から除いた。1

  • マルチデバイスで更新可能にするために保管庫はiCloudで設定することが前提になる。ローカル保管庫の変更を自動的に同期するサービス(リモートでも設定次第で可能)として、Obsidian Syncが提供されているが、こちらも有料の月額サービスであるため対象から除いた。2

  • ObsidianでGitを利用する方法として、Obsidian-Gitプラグインを利用することで、元データをgitのリポジトリの情報で設定できる。PCでは任意のローカルディレクトリを指定可能なので問題なし。iPhone,iPadの場合は、ファイル(ローカル)のobsidianフォルダ以下のみ対象であるため、ここにgit cloneする必要があるが、これはa-Shellアプリを使用すれば対応可能であった。3

設定方法

以降の内容は以下のページを参照ください。

7rikazhexde-pkm-obsidian-mkdocs.netlify.app

以上です。


  1. 興味はあるので今後使用する可能性はある。調べると、tadashi-aikawaのPKMとして公開されているMinervaではObsidian Publishを使用して運用されている。使用する場合は内容が参考になるため共有させていただきます。
  2. Obsidian Syncに記載の通り、機能も充実しているので利用価値は高いと言える。
  3. gitコマンドのインストールが必要(インストール方法詳細)

Pythonの安定版バージョンをGitHub APIを使用して取得する方法

概要

Pythonでパッケージ公開するプロジェクトでは、安定版バージョンのサポートも重要です。

GitHubリポジトリを管理している場合、多くはGitHub Actionsを使用してCI/CDで更新管理していると思います。

例えば、Pythonではsetup-pythonアクションを使用して、複数のOS,Version情報をmatrix構文を指定して、テストを実行することでサポートバージョンで開発、保守することが挙げられます。

github.com

docs.github.com

しかし、matrix構文ではバージョン情報を静的に記述しなければならず、新しい安定板バージョンが公開されても、ユーザー自身でバージョンを更新管理する必要があるため手間が発生します。

一方でGitHubでは、GitHub API(以降API)が公開されており、APIを活用すれば、Pythonの安定版バージョンを取得することができます。

この記事では、GitHub APIcurlコマンドを使用してPythonの安定版バージョンを取得する方法を紹介し、バージョン情報(安定板/最新版)を静的/動的更新に、且つ、複数言語に対応した高機能な自作のGitHub Actionであるjson2vars-setterについて紹介します。

GitHub APIの基本

GitHub APIは、GitHubで管理するデータやリソース情報を取得できます。バージョン情報はリポジトリのタグ情報から取得します。そして取得するためにRest APIを使用します。

エンドポイント

GET /repos/{owner}/{repo}/tags

このエンドポイントは、指定したリポジトリのすべてのタグを一覧表示します。

基本的なcurlコマンド

curl -s https://api.github.com/repos/{owner}/{repo}/tags

認証付きの場合:

export GITHUB_TOKEN=pat_hoge
curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/{owner}/{repo}/tags

APIレート制限について

GitHub APIにはレート制限があります。認証方法によって制限回数が異なります。比較のため他のAPIタイプも記載します。

API タイプ エンドポイント APIレート(認証なし) APIレート(認証あり)
REST API https://api.github.com 60リクエスト/時間 5,000リクエスト/時間
Search API https://api.github.com/search 10リクエスト/分 30リクエスト/分
GraphQL API https://api.github.com/graphql 利用不可 5,000リクエスト/時間

APIレートを確認する方法

# 環境変数を使った認証
export GITHUB_TOKEN=pat_hoge
curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/rate_limit | jq '.rate'

# 認証なしの場合
curl -s https://api.github.com/rate_limit | jq '.rate'

レート制限を超えると、APIは403エラーを返します。安定版の取得処理では複数のリクエストが必要になる場合があるため、認証付きでのリクエストが必要になります。

タグから安定版バージョンを取得する

Pythonはタグを使ってリリースを管理しています。安定版バージョンを取得するには、以下のステップに従います。

  1. リポジトリのタグ一覧を取得する
  2. 安定版と見なせるタグをフィルタリングする
  3. 必要に応じてバージョン情報を解析・ソートする

安定版の判断基準

Pythonの安定版バージョンは一般的に以下のパターンに従います:

  • セマンティックバージョニング(例: v3.10.0
  • プレリリース識別子がない(alpha, beta, rc などを含まない)
  • 通常 v から始まり、その後にメジャー.マイナー.パッチ番号が続く

Pythonの安定版取得用curlコマンド

Pythonの安定版バージョンを効率的に取得するためのcurlコマンドを紹介します。このコマンドはシェルスクリプトなどに組み込んで利用できます。

環境変数の設定

まず、GitHub APIトークンを環境変数に設定します。 APIトークンはGitHubの設定から取得してください。

export GITHUB_TOKEN=pat_xxx

トークンは個人アクセストークン(PAT)を使用します。これにより、レート制限が大幅に緩和されます。

基本的なPython安定版取得コマンド

Pythonの最新5つの安定版バージョンを取得:

curl -s -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/python/cpython/tags?per_page=100" | \
  jq '[.[] | select(.name | test("^v[0-9]+\\.[0-9]+\\.[0-9]+$"))] | sort_by(.name | sub("^v"; "") | split(".") | map(tonumber)) | reverse | .[0:5]'

このコマンドでは以下の処理を行なっています:

  1. per_page=100 パラメータでページあたり100件のタグを取得
  2. 正規表現 ^v[0-9]+\.[0-9]+\.[0-9]+$ でセマンティックバージョニング形式のタグのみ選択(例: v3.10.0
  3. バージョン番号によるソートとリバース処理で最新版順に配列
  4. 最新5つのバージョンのみ取得

出力フォーマットのカスタマイズ

必要な情報のみを抽出する方法

curl -s -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/python/cpython/tags?per_page=100" | \
  jq '[.[] | select(.name | test("^v[0-9]+\\.[0-9]+\\.[0-9]+$"))] | sort_by(.name | sub("^v"; "") | split(".") | map(tonumber)) | reverse | .[0:5] | map({version: .name, url: .zipball_url, commit: .commit.sha})'

このコマンドでは、各バージョンの名前、ZIPダウンロードURL、コミットハッシュを抽出しています。

エラーハンドリングの実装

実際の運用では、エラーハンドリングを適切に実装することが重要です。以下にエラーハンドリングを組み込んだコマンドを示します。

curl -s -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/python/cpython/tags?per_page=100" | \
  jq 'if type=="object" and has("message") then 
        {error: .message, documentation_url: .documentation_url} 
      else 
        [.[] | select(.name | test("^v[0-9]+\\.[0-9]+\\.[0-9]+$"))] | 
        sort_by(.name | sub("^v"; "") | split(".") | map(tonumber)) | 
        reverse | .[0:5] 
      end'

このコマンドでは、GitHub APIがエラーを返した場合(レート制限超過など)にエラー情報を適切に表示できます。

実行結果の例

適切に実行された場合、以下のような出力が得られます。

[
  {
    "name": "v3.13.2",
    "zipball_url": "https://api.github.com/repos/python/cpython/zipball/refs/tags/v3.13.2",
    "tarball_url": "https://api.github.com/repos/python/cpython/tarball/refs/tags/v3.13.2",
    "commit": {
      "sha": "4f8bb3947cfbc20f970ff9d9531e1132a9e95396",
      "url": "https://api.github.com/repos/python/cpython/commits/4f8bb3947cfbc20f970ff9d9531e1132a9e95396"
    },
    "node_id": "MDM6UmVmODE1OTg5NjE6cmVmcy90YWdzL3YzLjEzLjI="
  },
  {
    "name": "v3.13.1",
    "zipball_url": "https://api.github.com/repos/python/cpython/zipball/refs/tags/v3.13.1",
    "tarball_url": "https://api.github.com/repos/python/cpython/tarball/refs/tags/v3.13.1",
    "commit": {
      "sha": "067145177975eadd61a0c907d0d177f7b6a5a3de",
      "url": "https://api.github.com/repos/python/cpython/commits/067145177975eadd61a0c907d0d177f7b6a5a3de"
    },
    "node_id": "MDM6UmVmODE1OTg5NjE6cmVmcy90YWdzL3YzLjEzLjE="
  },
  ...
]

補足: APIレート制限の確認

APIレート使用状況は以下で確認できます。

# レート制限の確認
curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/rate_limit | jq '.resources.core'

参考: workflowによるAPIレートの確認結果

ファイル(check_github_api_rate_limits.yml)
name: Check GitHub API Rate Limits
on:
  workflow_dispatch:
permissions:
  contents: read
jobs:
  check_rate_limits:
    runs-on: ubuntu-latest
    steps:
      - name: Check GitHub API Rate Limits
        run: |
          echo "=== GitHub API Rate Limits with Token ==="
          curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/rate_limit | jq '.rate'

          echo -e "\n=== Search API Rate Limits with Token ==="
          curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/rate_limit | jq '.resources.search'

          echo -e "\n=== GraphQL API Rate Limits with Token ==="
          curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/rate_limit | jq '.resources.graphql'

          echo -e "\n=== GitHub API Rate Limits without Token ==="
          curl -s https://api.github.com/rate_limit | jq '.rate'

          echo -e "\n=== Search API Rate Limits without Token ==="
          curl -s https://api.github.com/rate_limit | jq '.resources.search'

          echo -e "\n=== GraphQL API Rate Limits without Token ==="
          curl -s https://api.github.com/rate_limit | jq '.resources.graphql'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
実行結果
Run echo "=== GitHub API Rate Limits with Token ==="
=== GitHub API Rate Limits with Token ===
{
  "limit": 5000,
  "used": 0,
  "remaining": 5000,
  "reset": 1742012041
}

=== Search API Rate Limits with Token ===
{
  "limit": 30,
  "used": 0,
  "remaining": 30,
  "reset": 1742008501
}

=== GraphQL API Rate Limits with Token ===
{
  "limit": 5000,
  "used": 0,
  "remaining": 5000,
  "reset": 1742012041
}

=== GitHub API Rate Limits without Token ===
{
  "limit": 60,
  "remaining": 54,
  "reset": 1742008504,
  "used": 6,
  "resource": "core"
}

=== Search API Rate Limits without Token ===
{
  "limit": 10,
  "remaining": 10,
  "reset": 1742008501,
  "used": 0,
  "resource": "search"
}

=== GraphQL API Rate Limits without Token ===
{
  "limit": 0,
  "remaining": 0,
  "reset": 1742012041,
  "used": 0,
  "resource": "graphql"
}

GitHub Actionsでの使用例

GitHub Actionsでは、ワークフローの中でPythonの安定版バージョンを取得して活用することができます。以下に具体的な例を示します。

Pythonの最新安定版を取得するワークフロー

name: Get Python Stable Versions
on:
  workflow_dispatch:
  schedule:
    - cron: '0 0 * * 1'  # 毎週月曜日に実行

permissions:
  contents: read

jobs:
  get-python-versions:
    runs-on: ubuntu-latest
    steps:
      - name: Check API Rate Limits
        run: |
          curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/rate_limit | jq '.resources.core'
      
      - name: Get Latest Python Stable Versions
        id: get_versions
        run: |
          # 最新の5つの安定版バージョンを取得
          VERSIONS=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
            "https://api.github.com/repos/python/cpython/tags?per_page=100" | \
            jq -c 'if type=="object" and has("message") then 
                {error: .message, documentation_url: .documentation_url} 
              else 
                [.[] | select(.name | test("^v[0-9]+\\.[0-9]+\\.[0-9]+$"))] | 
                sort_by(.name | sub("^v"; "") | split(".") | map(tonumber)) | 
                reverse | .[0:5] | map(.name) 
              end')
          
          # 結果を出力
          echo "Python stable versions: $VERSIONS"
          
          # 安定板バージョンの内の最新バージョンを環境変数に設定
          LATEST=$(echo $VERSIONS | jq -r '.[0]')
          echo "LATEST_PYTHON_VERSION=${LATEST#v}" >> $GITHUB_ENV
          echo "latest_version=${LATEST#v}" >> $GITHUB_OUTPUT
      
      - name: Use Latest Python Version
        run: |
          echo "Latest stable Python version: ${{ env.LATEST_PYTHON_VERSION }}"
          echo "This information can be used for further steps in the workflow"
      
      - name: Check API Rate Remaining
        run: |
          curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/rate_limit | jq '.resources.core'

このワークフローについて

  1. GitHub Actionsの GITHUB_TOKEN を使用して認証
  2. 最新の5つの安定版Pythonバージョンを取得
  3. 取得した安定バージョンの内の最新バージョンを環境変数とワークフロー出力に設定
  4. APIレート制限の使用状況を確認

実用的な応用例:最新バージョンでのテスト実行

name: Test with Latest Python
on:
  workflow_dispatch:
  push:
    branches: [ main ]

jobs:
  get-version:
    runs-on: ubuntu-latest
    outputs:
      python_version: ${{ steps.get_version.outputs.latest_version }}
    steps:
      - name: Get Latest Python Version
        id: get_version
        run: |
          LATEST=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
            "https://api.github.com/repos/python/cpython/tags?per_page=100" | \
            jq -r '[.[] | select(.name | test("^v[0-9]+\\.[0-9]+\\.[0-9]+$"))] | 
                sort_by(.name | sub("^v"; "") | split(".") | map(tonumber)) | 
                reverse | .[0].name' | sed 's/^v//')
          echo "latest_version=$LATEST" >> $GITHUB_OUTPUT
          echo "Latest Python version: $LATEST"
  
  test:
    needs: get-version
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ needs.get-version.outputs.python_version }}
      
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pytest
          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
      
      - name: Test with pytest
        run: |
          pytest

この例では、最初のジョブで最新のPythonバージョンを取得し、それを使用して次のジョブでテストを実行しています。

より高度な選択肢

ここまで説明したcurlコマンドやGitHub Actionsの実装はPython専用であればこれでも十分です。しかし、実際のプロジェクトでは、python以外の言語を使用していることもあると思います。

また、これは私の場合ですが、workflowの内容に応じて、件数の指定や、バージョンを静的/動的に管理したいことや、毎回APIを呼びたくないなど、バージョン管理機能として満足できないことがありました。

そこで私はこれらの機能を考慮したGitHub Actionsの専用カスタムアクションとして、「json2vars-setter」を作ることにしました。

このアクションは上記不満点を解決し、CLIにも対応しています。より使い勝手の良いバージョン管理機能となっていますので、是非以下の内容を確認して、気になれば使ってみてください。

json2vars-setter - 複数言語対応のjsonファイルによるバージョン管理ツール

github.com

json2vars-setterドキュメント

7rikazhexde.github.io

主な特長
  • 複数言語サポート: Python, Node.js, Ruby, Go, Rust など複数の言語に対応
  • 柔軟なバージョン取得:
    • 静的定義: 明示的にバージョンを指定
    • 動的定義: stable, latest タグから自動取得
    • 件数指定: 最新N件の取得が可能
  • キャッシュ管理: API制限を回避するインテリジェントなキャッシュ機能
  • JSON統合: すべてのバージョン情報をJSONで管理
  • 簡単設定: 複雑なJQフィルタなしで簡単に設定可能
使用例
name: Example workflow with json2vars-setter
on:
  workflow_dispatch:

jobs:
  setup:
    runs-on: ubuntu-latest
    steps:
      - name: Setup Variables with json2vars-setter
        uses: 7rikazhexde/json2vars-setter@v1
        with:
          json-file-path: 'versions.json'
          cache-path: 'cached-versions.json'
          token: ${{ secrets.GITHUB_TOKEN }}
          
      - name: Use Python Version
        run: |
          echo "Python version: ${{ env.PYTHON_VERSION }}"
          # その他の処理...
json2vars-setterの利点
  • 実装の手間削減: 本記事で説明したような複雑なJQフィルタ処理を自分で実装する必要がない
  • レート制限対策: キャッシュ機能によりGitHub APIのレート制限問題を解決
  • 複数言語対応: 1つのツールで複数言語のバージョン管理が可能
  • CI/CD安定化: バージョン管理を安定させ、CI/CDパイプラインの信頼性を向上

このカスタムアクションはGitHub Actionsのワークフローをシンプルかつ堅牢にし、バージョン管理の煩わしさから解放されます。ぜひ試してみてください。また、良いと感じたら、是非GitHubでスターもいただけると嬉しいです。

まとめ

GitHub APIを使用すると、Pythonの安定版バージョン情報を効率的に取得できます。この記事で紹介した方法を活用すれば、CI/CDパイプラインの構築、互換性チェック、開発環境の整備など、様々な用途に役立てることができます。

実装ポイントをまとめ。

  1. 環境変数 export GITHUB_TOKEN=pat_hoge を使って認証情報を管理する
  2. curlとjqを組み合わせてGitHub APIからタグ情報を取得・フィルタリングする
  3. Python命名規則に合わせた正規表現^v[0-9]+\.[0-9]+\.[0-9]+$)で安定版を特定する
  4. APIレート制限を考慮し、ページネーションパラメータ(per_page=100)を活用する
  5. エラーハンドリングを実装し、APIエラーに適切に対応する
  6. GitHub Actionsのワークフローに組み込み、自動化プロセスに活用する

これらの点に注意すれば、シンプルなcurlコマンドだけで、信頼性の高いPython安定版バージョン取得システムを構築できます。

なお、他の言語やフレームワークでも同様のアプローチは適用可能ですが、タグの命名規則が異なるため、正規表現やフィルタリングのロジックを調整する必要があります。

GitHubのスパムIssueを自動削除するスクリプト/ワークフローについて

はじめに

最近、GitHubのissuesでスパムが登録されるようになったので、タイトル、本文で指定キーワードに該当したらissueを削除するworkflowを作成したので紹介します。

注意事項

  • 本記事は2025/03/16時点の情報です。
  • 本記事に記載された内容やコードによって生じたいかなる損害についても責任を負いません。使用する際は自己責任でお願いします。
  • 使用する場合は、事前にローカルでspam_issue_deleter.pyを実行して動作確認することを推奨します。

使用方法

スクリプト/ワークフローファイルではGitHub APIのアクセストークンが必要になります。詳細はスクリプトを参照してください。

スクリプト(spam_issue_deleter.py)

コマンド例:python scripts/spam_issue_deleter.py

ワークフロー(delete-spam-issues.yml)

  • スクリプトとワークフローファイルコミット後にActions > Delete Spam Issues > Run workflow > mainで手動実行できます。
  • 定期実行(cron)もサポートしています。必要に応じて変更してください。

実行例

実行結果(左:issueの削除結果, 右:workflow実行結果)

まとめ

  • GitHubのissuesでスパムが登録されるようになったので、タイトル、本文で指定キーワードに該当したらissueを削除するスクリプトとワークフローを紹介しました。
  • issueはブラウザであれば手動削除も可能ですが、iOSスマホアプリでは対応していません。正直、この状況は良くないので、ブラウザ同等に削除できるように機能要望を出したいと考えています。
  • また、スパムは報告することも可能ですが、運営はすぐレスポンスしてくれるとは限りません。見るよりもまず削除することが大事だと思いますので、リスクは排除し安全は自分で守ることにしましょう。

以上。

Poetryを使用して自作パッケージをTestPyPI/PyPIに公開する方法について

概要

  • 自作したパッケージをTestPyPI/PyPIに公開する方法を紹介します。
  • パッケージの作成とTestPyPI/PyPIへの公開はPoetryを使用します。

注意事項

  • 本記事は2025/02/24時点の情報です。最新情報とは異なる場合があります。記載内容と異なる場合は公式の最新情報を確認してくだい。
  • Poetryのver2.0.0でPEP621対応がサポートされ、pyproject.tomlは[project]セクションでプロジェクト情報を管理するようになりました。1
    ver2.0.0より前のバージョンでは[tool.poetry]セクションで管理する仕様のため利用状況に合わせてpyproject.tomlの作成が必要になります。
  • 本記事では両方の記法を紹介しますが、最新版では仕様が変わる可能性があるため、事前に動作確認(TestPyPIへの公開)をすることを推奨します。

各種情報

Poetryのバージョン

$ poetry --version
Poetry (version 2.1.0)

プロジェクト情報

GitHubリポジトリ

下記リポジトリPyPIに公開します。

github.com

プロジェクトについて簡単に説明すると、Python系のドキュメントで使用されているmkdocsの拡張機能です。mkdocs-macros-pluginを使用していくつか拡張機能を追加しています。

フォルダ構成

基本構成は下記の通りです。詳細はここでは記載しませんが、PythonJavaScript(Node.js も含む)の構成になっています。

mkdocs-macros-utils $ tree -L 1
.
├── LICENCE
├── README.md
├── README_PyPI.md
├── coverage
├── docs
├── htmlcov
├── jest.config.js
├── mkdocs.yml
├── mkdocs_macros_utils
│   ├── __init__.py
│   ├── __pycache__
│   ├── debug_logger.py
│   ├── gist_codeblock.py
│   ├── link_card.py
│   ├── static
│   └── x_twitter_card.py
├── node_modules
├── overrides
├── package-lock.json
├── package.json
├── poetry.lock
├── pyproject.toml
├── requirements-dev.txt
├── requirements.txt
├── scripts
├── site
└── tests
    ├── __pycache__
    ├── js
    │   ├── setup.js
    │   └── x-twitter-widget.test.js
    └── python
        ├── __init__.py
        ├── __pycache__
        ├── conftest.py
        └── mkdocs_macros_utils

設定ファイル(pyproject.toml)

poetry version(>=2.0.0)の場合
  • Poetryのver2.0.0でサポートされたPEP621の構成2は下記の通りです(poetry newコマンド実行時の構成です。)
  • [project.urls]は必須ではありませんが、追加するとPyPI側にも反映されます。
# For poetry version(>=2.0.0)
[project]
name = "mkdocs-macros-utils"
version = "0.0.7"
description = "mkdocs-macros-utils is a mkdocs-macros-plugin based project that provides macros to extend cards, code blocks, etc, in MkDocs documents."
authors = [
    {name = "7rikazhexde", email = "33836132+7rikazhexde@users.noreply.github.com"}
]
license = {text = "MIT"}
readme = "README_PyPI.md"
requires-python = ">=3.10,<4.0"
dependencies = [
    "mkdocs-macros-plugin>=1.3.7,<2.0.0",
    "mkdocs-material>=9.6.1,<10.0.0",
    "requests>=2.25.0",
    "jinja2>=3.0.0",
    "pygments>=2.19.1",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.3.4",
    "mypy>=1.14.1",
    "ruff>=0.9.4",
    "pre-commit>=4.1.0",
    "pytest-cov>=6.0.0",
    "pytest-mock>=3.14.0",
    "pytest-xdist>=3.6.1",
    "taskipy>=1.14.1",
]

[project.urls]
homepage = "https://github.com/7rikazhexde/mkdocs-macros-utils"
repository = "https://github.com/7rikazhexde/mkdocs-macros-utils"
documentation = "https://7rikazhexde.github.io/mkdocs-macros-utils/"

# Project setup for Poetry
[tool.poetry]
# Operating modes
packages = [
    { include = "mkdocs_macros_utils" }
]

# Build system configuration
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"

# Task runner configuration
[tool.taskipy.tasks]
test_coverage_verbose = "pytest -s -vv --cov=mkdocs_macros_utils --cov-branch --cov-report term-missing --cov-report html"
test_html_report = "pytest --html=htmlcov/report_page.html"
test_ci_xml = "python scripts/run_tests.py --report xml"
test_ci_term = "python scripts/run_tests.py --report term"
test_coverage = "pytest --cov=mkdocs_macros_utils --cov-branch --cov-report=term-missing --cov-report=html"

# Type checking configuration
[tool.mypy]
files = ["mkdocs_macros_utils", "tests"]
explicit_package_bases = true
python_version = "3.12"
show_error_context = true
show_column_numbers = true
ignore_missing_imports = true
disallow_untyped_defs = true
no_implicit_optional = true
warn_return_any = true
warn_unused_ignores = true
warn_redundant_casts = true

# Overrides mypy ignore import settings
[[tool.mypy.overrides]]
module = ["mkdocs.*","mkdocs_macros.*","jinja2.*","ruamel.*","pygments.*"]
ignore_missing_imports = true

# Test configuration
[tool.pytest.ini_options]
testpaths = ["tests/python"]
poetry version(<2.0.0)の場合
  • Poetryのver2.0.0より前のVertsionでは[tool.poetry]で作成します3
  • プロジェクトのURLなどは[tool.poetry]に含めます。
# For poetry version(<2.0.0)
[tool.poetry]
name = "mkdocs-macros-utils"
version = "0.0.7"
description = "mkdocs-macros-utils is a mkdocs-macros-plugin based project that provides macros to extend cards, code blocks, etc, in MkDocs documents."
authors = ["7rikazhexde <33836132+7rikazhexde@users.noreply.github.com>"]
packages = [
    { include = "mkdocs_macros_utils" }
]
readme = "README_PyPI.md"
homepage = "https://github.com/7rikazhexde/mkdocs-macros-utils"
repository = "https://github.com/7rikazhexde/mkdocs-macros-utils"
documentation = "https://7rikazhexde.github.io/mkdocs-macros-utils/"

[tool.poetry.dependencies]
python = ">=3.10,<4.0"
mkdocs-macros-plugin = ">=1.3.7,<2.0.0"
mkdocs-material = ">=9.6.1,<10.0.0"
requests = ">=2.25.0"
jinja2 = ">=3.0.0"
pygments = ">=2.19.1"

# Development dependencies - these are not included in the final package
[tool.poetry.group.dev.dependencies]
pytest = "^8.3.4"
mypy = "^1.14.1"
ruff = "^0.9.4"
pre-commit = "^4.1.0"
pytest-cov = "^6.0.0"
pytest-mock = "^3.14.0"
pytest-xdist = "^3.6.1"
taskipy = "^1.14.1"

# Build system configuration(「For poetry version(>=2.0.0)」と同じ)
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"

# Task runner configuration(「For poetry version(>=2.0.0)」と同じ)
[tool.taskipy.tasks]
test_coverage_verbose = "pytest -s -vv --cov=mkdocs_macros_utils --cov-branch --cov-report term-missing --cov-report html"
test_html_report = "pytest --html=htmlcov/report_page.html"
test_ci_xml = "python scripts/run_tests.py --report xml"
test_ci_term = "python scripts/run_tests.py --report term"
test_coverage = "pytest --cov=mkdocs_macros_utils --cov-branch --cov-report=term-missing --cov-report=html"

# Type checking configuration(「For poetry version(>=2.0.0)」と同じ)
[tool.mypy]
files = ["mkdocs_macros_utils", "tests"]
explicit_package_bases = true
python_version = "3.12"
show_error_context = true
show_column_numbers = true
ignore_missing_imports = true
disallow_untyped_defs = true
no_implicit_optional = true
warn_return_any = true
warn_unused_ignores = true
warn_redundant_casts = true

# Overrides mypy ignore import settings(「For poetry version(>=2.0.0)」と同じ)
[[tool.mypy.overrides]]
module = ["mkdocs.*","mkdocs_macros.*","jinja2.*","ruamel.*","pygments.*"]
ignore_missing_imports = true

# Test configuration(「For poetry version(>=2.0.0)」と同じ)
[tool.pytest.ini_options]
testpaths = ["tests/python"]

パッケージの公開手順

以下の手順で実行します。

  1. 公開設定
  2. TestPyPIソースへの公開
  3. TestPyPIソースからのパッケージインストール
  4. PyPIソースへの公開
  5. PyPIソースからのパッケージインストール

関連ドキュメントは下記です。

packaging.python.org

python-poetry.org

以降はそれぞれの内容について記載します。

公開設定

Pythonのパッケージ管理では、パッケージの公開先としてPyPI(本番環境)とTestPyPI(テスト環境)が存在します。

まず、TestPyPIで基本動作を確認し、問題がなければ、PyPIに公開する手順で実施します。

トークンの設定

PyPI(本番環境)とTestPyPI(テスト環境)への公開では、アカウント登録とAPIトークンの作成、そして、poetryに設定する必要になります。

未実施の場合は以下の記事を参考に実施します。

packaging.python.org

test.pypi.org

トークン取得後は以下Poetryの資格情報のドキュメントを参考に設定します。

python-poetry.org

TestPyPI向けの設定
poetry config pypi-token.testpypi <your-testpypi-token>
PyPI向けの設定
poetry config pypi-token.pypi <your-pypi-token>

設定された情報はauth.tomlから確認できます。

# For WSL: $HOME/.config/pypoetry/auth.toml
[pypi-token]
testpypi = "<your-testpypi-toke>"
pypi = "<your-pypi-token>"

公開先情報の登録

Poetryでは以下の公式ドキュメント記載の通り、index APILegacy Upload API)を使用してパッケージの公開処理を行います。

その際、poetryのconfig情報に公開先情報を設定する必要があります。設定していない場合は、公開処理のpoetry publishコマンド実行時にエラーになります。

python-poetry.org

Poetry treats repositories to which you publish packages as user specific and not project specific configuration unlike package sources. Poetry, today, only supports the Legacy Upload API when publishing your project.

設定方法は下記の通りです。

TestPyPI向けの設定
poetry config repositories.testpypi https://test.pypi.org/legacy/
PyPI向けの設定
poetry config repositories.pypi https://upload.pypi.org/legacy/

設定された情報はconfig.tomlから確認できます。

# For WSL: $HOME/.config/pypoetry/config.toml
[repositories.testpypi]
url = "https://test.pypi.org/legacy/"

[repositories.pypi]
url = "https://upload.pypi.org/legacy/"

TestPyPIソースへの公開

まず、バージョンはprojectのversionキーの値に基づき作成/管理されます。

[project]
name = "mkdocs-macros-utils"
version = "0.0.7" # この値

以下のコマンドを実行してパッケージをビルドします。ビルドが成功するとwhlファイルとtarファイルが作成されます。(version = "0.0.7"の値を元に作成されています。)

詳細は以下のドキュメントを確認ください。

packaging.python.org

$ poetry build
Building mkdocs-macros-utils (0.0.7)
  - Building sdist
  - Built mkdocs_macros_utils-0.0.7.tar.gz
  - Building wheel
  - Built mkdocs_macros_utils-0.0.7-py3-none-any.whl

testpypiに公開する場合は、パッケージを登録するリポジトリ (デフォルトはpypi)を指定して実行します。 その際、configコマンドで設定したリポジトリ名と一致する必要があるので注意が必要です。

poetry publish --repository testpypi

その後、mkdocs-macros-utilsのtestpipyを確認すると実際に公開されていることを確認できます。

TestPyPIソースからのパッケージインストール

まず、poetryのドキュメントに以下の記載があります。

python-poetry.org

By default, Poetry discovers and installs packages from PyPI. But, you want to install a dependency to your project for a simple API repository? Let’s do it.

First, configure the package source as a supplemental (or explicit) package source to your project.

つまり、TestPyPIからパッケージをインストールするためには、インストール元の情報をpyproject.tomlにソースとして設定する必要があります。

そこで、次のコマンドを実行してpyproject.tomlにtestpypiのソース情報を設定します。

poetry source add --priority=supplemental testpypi https://pypi.example.org/simple/

実行すると、pyproject.tomlに以下の情報が追記されます。

[[tool.poetry.source]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
priority = "supplemental"

なお、パッケージのインストールは、poetry addコマンドを使用しますが、デフォルトではPyPIソースを参照します。これは以下の記載から確認できます。

By default, if you have not configured any primary source, Poetry is configured to use the Python ecosystem’s canonical package index PyPI.

そして、PyPIソースの優先順位は--priorityオプションで設定されます。

--priorityオプションは値として、primarysupplemental,explictが指定可能でそれぞれ以下の意味を持ちます。

priority値 意味 動作 使用例
primary 主要ソース - デフォルトのPyPIソースを無効化
- 依存パッケージもこのソースから探す
- priorityを指定しない場合のデフォルト値
プライベートリポジトリを主要なパッケージソースとして使用する場合
supplemental 補完ソース - PyPIソースは無効化されない
- 他のソースで見つからない場合のみ使用
- --source オプションで明示的に指定可能
TestPyPIからの特定パッケージのインストールと、PyPIからの依存パッケージのインストールを併用する場合
explicit 明示的ソース - パッケージ設定で明示的に指定された場合のみ使用
- --source オプションでの指定が必要
PyTorchのGPUパッケージなど、特定のパッケージを特定のソースからのみ取得する場合

関連情報

python-poetry.org

If priority is undefined, the source is considered a primary source, which disables the implicit PyPI source and takes precedence over supplemental sources.

Package sources are considered in the following order:

  1. primary sources or implicit PyPI (if there are no primary sources),
  2. supplemental sources.

ここで、testpypiソースからパッケージをインストールする場合のポイントとして、依存パッケージのインストールがあります。

パッケージ管理について、パッケージはtestpypiとpypiで公開されていると説明しましたが、公開対象パッケージが依存パッケージを含む場合、依存パッケージがtestpypiソースで公開されているかどうかは各プロジェクトに依存しますが、大抵の場合は公開されていません。

これは検索すれば確認できます。また、これは他のパッケージでも同様で、例えばパッケージ更新(poetry update)する場合でも公開されていなければエラーになります。

しかし、実際には動作確認として、テスト用にtestpypiに公開したパッケージを問題なくインストールできるか確認したいわけです。

その場合にどうすればよいかですが、上記記載の通り、--priority=supplementalでソースを登録することで、一時ソースとしてpypiからインストールされるようになるため、依存パッケージを含む自作パッケージをインストールすることができるようになります。

注意点として、testpypiからインストールする場合には、--sorceオプションを指定して取得します。

poetry add --source testpypi mkdocs-macros-utils

以上で、testpipyからのインストールが成功すれば、testpypiでも一連の動作確認ができたことになるので、本番環境(PyPI)での公開を行います。

PyPIソースへの公開

基本的にはTestPyPIソースへの公開手順と同じですが、事前にpoetry removeコマンドでtestpipyで追加した自作パッケージ(mkdocs-macros-utils)とビルドファイル(distフォルダ)を削除します。

poetry remove mkdocs-macros-utils

そして、poetry buildコマンドでパッケージをビルドします。

$ poetry build
Building mkdocs-macros-utils (0.0.7)
  - Building sdist
  - Built mkdocs_macros_utils-0.0.7.tar.gz
  - Building wheel
  - Built mkdocs_macros_utils-0.0.7-py3-none-any.whl

その後、configコマンドで設定したリポジトリ名(pypi)を指定してPyPIに公開します。

poetry publish --repository pypi

また、ビルドと公開手順をまとめて指定することも可能であり、以下コマンドで実行できます。

poetry publish --build --repository pypi

なお、もし誤ってpublishコマンドを実行したとしても同じバージョンがリリースされている場合は400エラーとなり失敗するため、誤って公開されることはありません。

$ poetry publish --build --repository pypi
Building mkdocs-macros-utils (0.0.7)
  - Building sdist
  - Built mkdocs_macros_utils-0.0.7.tar.gz
  - Building wheel
  - Built mkdocs_macros_utils-0.0.7-py3-none-any.whl

Publishing mkdocs-macros-utils (0.0.7) to PyPI
 - Uploading mkdocs_macros_utils-0.0.7-py3-none-any.whl FAILED

HTTP Error 400: File already exists ('mkdocs_macros_utils-0.0.7-py3-none-any.whl', with blake2_256 hash '70ebb1d95f8bb5c724455d1fcbfeba701c7db49b54bb6606259874fd5d847f60'). See https://pypi.org/help/#file-name-reuse for more information. | b"<html>\n <head>\n  <title>400 File already exists ('mkdocs_macros_utils-0.0.7-py3-none-any.whl', with blake2_256 hash '70ebb1d95f8bb5c724455d1fcbfeba701c7db49b54bb6606259874fd5d847f60'). See https://pypi.org/help/#file-name-reuse for more information.

その後、mkdocs-macros-utilsのpipyを確認すると実際に公開されていることを確認できます。

PyPIソースからのパッケージインストール

poetry addコマンドで追加します。

インストール時の動作を確認する場合は、verboseオプション(-v:通常出力,-vv:詳細出力,-vvv:デバッグ出力)を指定することで、詳細を確認できます。

poetry add mkdocs-macros-utils -vvv

まとめ

  • 自作したパッケージをPoetryを使用して、TestPyPI/PyPIに公開する方法を紹介しました。
  • 私はmkdocs-macros-utils以外にも自作のコードをGitHubリポジトリ公開していますが、より多くのユーザーに使用してもらう場合にはPyPIでの公開が重要であると考えています。
  • 公開手順は非常に簡単で公式ドキュメントを見れば必要な情報は記載されているので、参照すれば問題なく公開できました。
  • TestPyPIからのパッケージのインストールでは、いくつかの設定が必要ですが、正しく設定すれば問題なく動作確認できます。
  • テスト環境で事前に動作検証ができることは安心感を持てます。今後もうまく活用して、他のプロジェクトをPyPIに公開していきたいと思いました。

以上です。