Hugging Face Access Token取得支援ツール(ソースコードと説明と利用ガイド)

プログラム利用ガイド

1. このプログラムの利用シーン

Hugging Face にアクセスするためのAccess Tokenを取得・管理することを支援するツールです。

2. 主な機能

3. 基本的な使い方

4. 便利な機能

Python開発環境,ライブラリ類

ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。

Python 3.12 のインストール

インストール済みの場合は実行不要。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。

REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul

関連する外部ページ

Python の公式ページ: https://www.python.org/

AI エディタ Windsurf のインストール

Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。

winget install --scope machine Codeium.Windsurf -e --silent

関連する外部ページ

Windsurf の公式ページ: https://windsurf.com/

[1] Python Software Foundation. (2025). webbrowser — Convenient web-browser controller. Python 3.13.7 documentation. https://docs.python.org/3/library/webbrowser.html

[2] Python Software Foundation. (2025). getpass — Portable password input. Python 3.13.7 documentation. https://docs.python.org/3/library/getpass.html

Hugging Face Access Token取得支援プログラム

概要

このプログラムは、Hugging Face Access Tokenの取得と管理を支援するコマンドライン型ツールである。ユーザーがブラウザでHugging Faceのサービスから手動でトークンを取得し、環境設定ファイル(.env)に保存する機能を提供する。

主要技術

webbrowser モジュール

Pythonの標準ライブラリに含まれるクロスプラットフォーム対応のブラウザ制御モジュール[1]。デフォルトブラウザを自動検出して使用する。新しいタブ、新しいウィンドウ、既定のブラウザでのURL表示が可能である。

getpass モジュール

パスワード入力時のエコーを無効化するセキュアな入力機能を提供する標準モジュール[2]。入力された文字が画面に表示されないため機密情報の入力に適している。端末制御によって安全な入力を実現し、クロスプラットフォーム対応である。

実装の特色

入力検証とサニタイゼーション

環境変数名のPOSIX準拠チェックと自動サニタイゼーション機能を実装している。不正な文字の除去を行う。

セキュリティ配慮

トークンの部分マスク表示により、ログやコンソール出力での機密情報漏洩を防止している。

参考文献

[1] Python Software Foundation. (2025). webbrowser — Convenient web-browser controller. Python 3.13.7 documentation. https://docs.python.org/3/library/webbrowser.html

[2] Python Software Foundation. (2025). getpass — Portable password input. Python 3.13.7 documentation. https://docs.python.org/3/library/getpass.html

ソースコード


"""
Hugging Face Access Token取得支援プログラム

特徴技術名: webbrowserモジュール
出典: Python Software Foundation. (2025). webbrowser — Convenient web-browser controller. Python 3.13.5 documentation. https://docs.python.org/3/library/webbrowser.html

特徴機能: プラットフォーム独立のブラウザ制御
webbrowserモジュールは、Unix、Windows、macOSで動作し、各プラットフォームのデフォルトブラウザを自動的に検出して使用する機能を提供する。

学習済みモデル: 使用していない

方式設計:
- 関連利用技術:
  - getpassモジュール: パスワード入力時のエコー無効化によるセキュアな入力機能
  - pathlibモジュール: オブジェクト指向ファイルパス操作とファイル読み書き機能
  - reモジュール: .env内の環境変数行の検出(export有無や空白を許容)
  - shutilモジュール: .envのバックアップ作成
- 入力と出力:
  入力:
  • 環境変数名の選択(HF_TOKEN/HUGGINGFACE_HUB_TOKEN/HUGGINGFACE_API_TOKEN/カスタム)
  • 保存先ファイルの選択(.env/.env.development/.env.production/カスタム)
  • ブラウザの開き方の選択(既定/新しいタブ/新しいウィンドウ)
  • ユーザーによる選択操作(ブラウザでのAccess Token取得、コンソールでのトークン入力)
  出力: ブラウザでのHugging Face Access Tokenページ表示、トークンの.envファイル保存、保存後のガイダンス表示
- 処理手順:
  1. 環境変数名と保存先ファイルを選択する
  2. 既存Access Tokenの確認と選択肢提示
  3. ブラウザでHugging Face Access Tokenページを開く
  4. ユーザーによる手動でのAccess Token取得
  5. getpassを使用したセキュアなトークン入力
  6. .envをバックアップし、トークンの.envファイルへの保存
  7. 保存後の確認メッセージと利用ガイダンス表示
- 前処理: 既存.envファイルの読み込みとAccess Token存在確認
- 後処理: トークン保存後の確認メッセージ表示
- 追加処理: トークンの部分マスク表示によるセキュリティ配慮、バックアップ作成

- 調整を必要とする設定値:
  - var_name: 保存する環境変数名(HF_TOKEN/HUGGINGFACE_HUB_TOKEN/HUGGINGFACE_API_TOKEN/カスタム)
  - filename: トークンを保存するファイル名(デフォルト: .env)
  - browser_mode: ブラウザ起動モード(既定/新しいタブ/新しいウィンドウ)

将来方策: 既存の.envファイルの読み込みと同一Access Tokenの存在確認は現行実装で対応済み

その他の重要事項:
- getpassモジュールによりAccess Token入力時の画面表示を防止
- バックアップ作成機能により既存ファイルの安全性を確保
- 入力中断処理でユーザビリティを向上
- Hugging Face Access Tokenは通常"hf_"で始まる形式
- 例外処理を強化してプログラムの安定性を向上
- 冗長なコードを統合して保守性を向上

前準備: なし(標準ライブラリのみ使用)
"""

import webbrowser
from pathlib import Path
import getpass
import re
import shutil
import sys
import time

# 設定定数
DEFAULT_ENV_FILE = '.env'
DEFAULT_ENV_CHOICES = ['.env', '.env.development', '.env.production']
DEFAULT_KEY_CHOICES = ['HF_TOKEN', 'HUGGINGFACE_HUB_TOKEN', 'HUGGINGFACE_API_TOKEN']
HUGGINGFACE_API_URL = 'https://huggingface.co/settings/tokens'


def safe_input(prompt, default=None):
    """入力中断時の共通処理"""
    try:
        return input(prompt)
    except (KeyboardInterrupt, EOFError):
        if default is not None:
            print('\n入力が中断されたため既定値を用いる')
            return default
        print('\n処理を中断しました。プログラムを終了します。')
        sys.exit(1)


def handle_file_operation_error(operation_name, error):
    """ファイル操作エラーの共通処理"""
    print(f'{operation_name}でエラー: {error}')
    return False


def get_user_choice(title, choices, default_key, custom_prompt=None):
    """ユーザー選択処理の統一関数"""
    print(title)

    # 既定の選択肢を表示
    for i, choice in enumerate(choices, 1):
        print(f'{i}. {choice}')

    # カスタム選択肢がある場合
    if custom_prompt:
        print(f'{len(choices) + 1}. {custom_prompt}')

    # 入力受付
    valid_keys = [str(i) for i in range(1, len(choices) + 1)]
    if custom_prompt:
        valid_keys.append(str(len(choices) + 1))

    prompt = f"選択 ({'/'.join(valid_keys)}, 既定: {default_key}): "
    key = safe_input(prompt, default=default_key).strip() or default_key

    # 既定選択肢の処理
    try:
        index = int(key) - 1
        if 0 <= index < len(choices):
            return choices[index], False  # 選択された値, カスタムフラグ
    except ValueError:
        pass

    # カスタム選択の処理
    if custom_prompt and key == str(len(choices) + 1):
        return None, True  # カスタム入力が必要

    # デフォルト値の処理
    try:
        default_index = int(default_key) - 1
        if 0 <= default_index < len(choices):
            return choices[default_index], False
    except ValueError:
        pass

    return choices[0], False  # フォールバック


def wait_enter(prompt):
    """Enterキー待ちの共通処理"""
    safe_input(prompt)


def sanitize_var_name(name):
    """環境変数名を検証・サニタイズする。無効な場合は既定名にフォールバックするための情報を返す。"""
    n = (name or '').strip()
    # 先頭の 'export' を除去(あれば)
    lowered = n.lower()
    if lowered.startswith('export'):
        parts = n.split(None, 1)
        n = parts[1] if len(parts) == 2 else ''
    # '=' を含む名称は不正(Windows仕様)
    if '=' in n:
        return (DEFAULT_KEY_CHOICES[0], True, "変数名に'='は使用できない")
    # POSIX慣用: 先頭は英字またはアンダースコア、その後は英数字とアンダースコアのみ
    if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', n):
        if n == '':
            return (DEFAULT_KEY_CHOICES[0], True, '空の変数名')
        return (DEFAULT_KEY_CHOICES[0], True, '許可文字は英字・数字・アンダースコア。先頭は英字またはアンダースコア')
    return (n, False, '')


def check_access_token(filename=DEFAULT_ENV_FILE, var_name='HF_TOKEN'):
    """既存のAccess Tokenを確認する"""
    file_path = Path(filename)
    if not file_path.exists():
        return None

    try:
        content = file_path.read_text(encoding='utf-8')
        lines = content.splitlines()

        for line in lines:
            line = line.strip()
            if line.startswith(f'{var_name}='):
                access_token = line.split('=', 1)[1].strip()
                if access_token:
                    return access_token
        return None
    except (OSError, IOError, UnicodeDecodeError, PermissionError) as e:
        handle_file_operation_error('ファイル読み込み', e)
        return None


def open_token_page(mode='default'):
    """Hugging FaceのAccess Tokenページを開く"""
    print('ブラウザでHugging FaceのAccess Tokenページを開きます...')

    try:
        funcs = {'new_tab': webbrowser.open_new_tab, 'new_window': webbrowser.open_new}
        opened = funcs.get(mode, webbrowser.open)(HUGGINGFACE_API_URL)

        if not opened:
            print('ブラウザを開けなかった可能性がある。URLを手動で開くこと:')
            print(HUGGINGFACE_API_URL)
            return False

        # ブラウザが起動するまで少し待つ
        time.sleep(1)
        return True

    except (webbrowser.Error, OSError) as e:
        print(f'ブラウザの起動でエラーが発生: {e}')
        print(f'URLを手動で開いてください: {HUGGINGFACE_API_URL}')
        return False


def get_access_token():
    """ユーザーからAccess Tokenを取得"""
    print('コピーしたAccess Tokenを貼り付けてください')
    try:
        access_token = getpass.getpass('Access Token: ')
        return access_token.strip()
    except KeyboardInterrupt:
        print('\n入力がキャンセルされました')
        return None
    except EOFError:
        print('\n入力がキャンセルされました')
        return None
    except Exception as e:
        print(f'入力でエラーが発生: {e}')
        return None


def save_access_token(access_token, filename=DEFAULT_ENV_FILE, var_name='HF_TOKEN'):
    """Access Tokenをファイルに保存(上書きモード)"""
    try:
        file_path = Path(filename)

        # バックアップ作成
        if file_path.exists():
            backup_path = Path(str(file_path) + '.bak')
            try:
                shutil.copyfile(str(file_path), str(backup_path))
                print(f'バックアップを作成しました: {backup_path}')
            except (shutil.SameFileError, IsADirectoryError, PermissionError,
                    FileNotFoundError, OSError) as e:
                handle_file_operation_error('バックアップ作成', e)
                print('バックアップの作成に失敗しましたが、処理を続行します')

        existing_lines = []
        if file_path.exists():
            try:
                content = file_path.read_text(encoding='utf-8')
                for line in content.splitlines():
                    if not line.strip().startswith(f'{var_name}='):
                        existing_lines.append(line + '\n')
            except (OSError, IOError, UnicodeDecodeError, PermissionError) as e:
                handle_file_operation_error('既存ファイルの読み込み', e)
                return False

        # 新しいAccess Tokenを追加
        content = ''.join(existing_lines)
        if content and not content.endswith('\n'):
            content += '\n'
        content += f'{var_name}={access_token}\n'

        # 親ディレクトリがない場合は作成
        try:
            file_path.parent.mkdir(parents=True, exist_ok=True)
        except (FileExistsError, PermissionError, OSError) as e:
            handle_file_operation_error('ディレクトリ作成', e)
            return False

        try:
            file_path.write_text(content, encoding='utf-8', newline='\n')
            print(f'Access Tokenを {file_path.resolve()} に保存しました')
            return True
        except (OSError, IOError, PermissionError) as e:
            handle_file_operation_error('ファイル保存', e)
            return False

    except Exception as e:
        print(f'予期しないエラー: {e}')
        return False


def choose_env_file():
    """保存先のenvファイルを選択する"""
    result, is_custom = get_user_choice(
        '保存先ファイルを選択する',
        DEFAULT_ENV_CHOICES,
        '1',
        'カスタムパスを指定する'
    )

    if is_custom:
        custom = safe_input('保存先ファイル名を入力: ', default=DEFAULT_ENV_FILE).strip()
        return custom or DEFAULT_ENV_FILE

    return result


def choose_var_name():
    """保存する環境変数名を選択する"""
    result, is_custom = get_user_choice(
        '保存する環境変数名を選択する',
        DEFAULT_KEY_CHOICES,
        '1',
        'カスタム名を指定する'
    )

    if is_custom:
        custom = safe_input('変数名を入力(例: HF_TOKEN): ',
                          default=DEFAULT_KEY_CHOICES[0]).strip()
        var_name, is_fallback, reason = sanitize_var_name(custom)
        if is_fallback:
            print(f"無効な環境変数名のため既定名にフォールバック: 入力='{custom}' "
                  f"理由={reason} → 使用名='{var_name}'")
        return var_name

    return result


def choose_browser_mode():
    """ブラウザの開き方を選択する"""
    choices = ['既定モードで開く', '新しいタブで開く', '新しいウィンドウで開く']
    modes = ['default', 'new_tab', 'new_window']

    result, _ = get_user_choice(
        'ブラウザの開き方を選択する',
        choices,
        '1'
    )

    # 選択された表示名に対応するモードを返す
    try:
        index = choices.index(result)
        return modes[index]
    except ValueError:
        return 'default'


def main():
    print('=== Hugging Face Access Token取得支援ツール ===\n')

    try:
        # 変数名と保存先ファイルを選択
        var_name = choose_var_name()
        env_file = choose_env_file()

        # 既存のAccess Tokenを確認
        existing_token = check_access_token(env_file, var_name)

        if existing_token:
            print(f'既存のAccess Tokenが見つかりました: {existing_token[:8]}...{existing_token[-4:]}')
            print('1. 既存のAccess Tokenを使用する')
            print('2. 新しいAccess Tokenで上書きする')

            choice = safe_input('選択 (1/2, 既定: 1): ', default='1').strip() or '1'

            if choice == '1':
                print('既存のAccess Tokenを使用します')
                print('\n処理を終了します')
                return
            elif choice != '2':
                print('無効な選択です')
                print('\n処理を終了します')
                return

        # ブラウザを開く
        mode = choose_browser_mode()
        wait_enter('Enterキーを押すとブラウザが開きます...')
        browser_opened = open_token_page(mode)

        if not browser_opened:
            choice = safe_input('ブラウザの起動に失敗しました。処理を続行しますか? (y/n, 既定: y): ',
                              default='y').strip().lower()
            if choice not in ('y', ''):
                print('処理を中止します')
                return

        # 手動操作の指示を表示
        print('\n=== 手動操作の手順 ===')
        print('1. ブラウザでHugging Faceアカウントにログインしてください')
        print('   ※ アカウントがない場合は先にhttps://huggingface.co/joinで作成してください')
        print('2. メール確認が済んでいない場合は先にメール認証を完了してください')
        print("3. 'New token'ボタンをクリック")
        print('4. トークンの名前を入力(例:MyProject)')
        print('5. ロール(Role)を選択(通常はreadで十分)')
        print("6. 'Generate a token'ボタンをクリック")
        print('7. 表示されたAccess Token(hf_で始まる文字列)をコピーしてください')
        print('   ※ このトークンは一度しか表示されません!')
        print('=======================================\n')

        # Access Token取得の確認
        wait_enter('Access TokenをコピーしたらEnterキーを押してください...')

        # Access Tokenを取得
        access_token = get_access_token()

        if access_token:
            print(f'\n取得したAccess Token: {access_token[:8]}...{access_token[-4:]}')

            # ファイルに保存
            save_choice = safe_input('\nファイルに保存しますか? (y/n, 既定: y): ',
                                   default='y').strip().lower()
            if save_choice in ('y', ''):
                saved = save_access_token(access_token, env_file, var_name)
                if saved:
                    # 保存後の参考情報を表示
                    print('\n=== 利用の参考情報 ===')
                    resolved = str(Path(env_file).resolve())
                    print(f'保存パス: {resolved}')
                    print(f'環境変数名: {var_name}')
                    print('- Pythonでの使用例:')
                    print(f'  from huggingface_hub import login')
                    print(f'  login(token="{var_name}")')
                    print('- 環境変数での使用例:')
                    print(f'  export $(grep -v "^#" {env_file} | xargs)')
                    print('======================\n')
                else:
                    print('ファイルの保存に失敗しました')
        else:
            print('Access Tokenが入力されませんでした')

    except Exception as e:
        print(f'予期しないエラーが発生しました: {e}')
        print('プログラムを終了します')

    print('\n処理を終了します')


if __name__ == '__main__':
    try:
        main()
    except (KeyboardInterrupt, EOFError):
        print('\n処理を中断しました。プログラムを終了します。')
    except Exception as e:
        print(f'\n致命的なエラーが発生しました: {e}')
        print('プログラムを終了します。')