本案例基於 .claude/lib 整體結構,展示如何將內部共用庫打包成可重用的 Python 套件。

先備知識

問題背景

現有設計

.claude/lib 目錄結構:

 1.claude/lib/
 2├── __init__.py              # Package entry point with version
 3├── config_loader.py         # YAML/JSON configuration loader
 4├── git_utils.py             # Git operations (branch, worktree)
 5├── hook_io.py               # Hook I/O standardization
 6├── hook_logging.py          # Logging system for hooks
 7├── hook_validator.py        # Hook compliance validator
 8├── markdown_link_checker.py # Markdown link validation
 9├── README.md                # API documentation
10└── tests/                   # Unit tests
11    ├── __init__.py
12    ├── test_config_loader.py
13    ├── test_git_utils.py
14    ├── test_hook_io.py
15    └── test_hook_logging.py

這是一個典型的內部工具庫,包含四個核心模組:

模組功能相依性
config_loaderYAML/JSON 配置載入PyYAML (optional)
git_utilsGit 命令執行與分支管理無(使用 subprocess)
hook_ioHook 輸入輸出標準化無(使用標準庫)
hook_logging日誌系統設定無(使用標準庫)

現有限制

作為內部目錄的問題:

  • 無法跨專案重用:程式碼被鎖定在單一專案中
  • 沒有版本管理:無法追蹤 API 變更
  • 無法透過 pip 安裝:其他專案必須複製程式碼
  • 相依性管理不明確:PyYAML 是可選還是必要?

進階解決方案

設計目標

  1. 建立標準的 Python 套件結構
  2. 使用 pyproject.toml 管理元資料
  3. 支援 pip install
  4. 建立 CI/CD 發布流程

實作步驟

步驟 1:重組目錄結構

從內部目錄結構轉換為標準的 src layout

 1claude-hooks-lib/
 2├── pyproject.toml           # Package metadata and build config
 3├── README.md                # Package documentation
 4├── LICENSE                  # License file (MIT recommended)
 5├── CHANGELOG.md             # Version history
 6├── src/
 7│   └── claude_hooks_lib/    # Package directory (underscore for import)
 8│       ├── __init__.py      # Public API exports
 9│       ├── config_loader.py
10│       ├── git_utils.py
11│       ├── hook_io.py
12│       ├── hook_logging.py
13│       ├── hook_validator.py
14│       └── py.typed         # PEP 561 marker for type hints
15└── tests/
16    ├── __init__.py
17    ├── conftest.py          # Pytest fixtures
18    ├── test_config_loader.py
19    ├── test_git_utils.py
20    ├── test_hook_io.py
21    └── test_hook_logging.py
為什麼選擇 src layout?
 1# Flat layout (不推薦用於套件發布)
 2my-package/
 3├── my_package/          # Package 直接在根目錄
 4│   └── __init__.py
 5└── tests/
 6
 7# Src layout (推薦)
 8my-package/
 9├── src/
10│   └── my_package/      # Package 在 src/ 下
11│       └── __init__.py
12└── tests/
特性Flat LayoutSrc Layout
測試環境可能意外導入本地版本強制安裝後測試
套件發布容易遺漏檔案明確的套件邊界
複雜度較低稍高
推薦場景簡單專案、應用程式套件發布、函式庫

步驟 2:建立 pyproject.toml

 1[build-system]
 2requires = ["hatchling"]
 3build-backend = "hatchling.build"
 4
 5[project]
 6name = "claude-hooks-lib"
 7version = "0.28.0"
 8description = "Shared utilities for Claude Code hooks"
 9readme = "README.md"
10license = "MIT"
11requires-python = ">=3.10"
12authors = [
13    { name = "Your Name", email = "your.email@example.com" }
14]
15keywords = ["claude", "hooks", "utilities", "git"]
16classifiers = [
17    "Development Status :: 4 - Beta",
18    "Intended Audience :: Developers",
19    "License :: OSI Approved :: MIT License",
20    "Operating System :: OS Independent",
21    "Programming Language :: Python :: 3",
22    "Programming Language :: Python :: 3.10",
23    "Programming Language :: Python :: 3.11",
24    "Programming Language :: Python :: 3.12",
25    "Programming Language :: Python :: 3.13",
26    "Typing :: Typed",
27]
28
29# Core dependencies (minimal)
30dependencies = []
31
32[project.optional-dependencies]
33# YAML support
34yaml = ["PyYAML>=6.0"]
35
36# Development dependencies
37dev = [
38    "pytest>=8.0",
39    "pytest-cov>=4.0",
40    "mypy>=1.0",
41    "ruff>=0.4",
42]
43
44# All optional features
45all = ["claude-hooks-lib[yaml]"]
46
47[project.urls]
48Homepage = "https://github.com/yourname/claude-hooks-lib"
49Documentation = "https://github.com/yourname/claude-hooks-lib#readme"
50Repository = "https://github.com/yourname/claude-hooks-lib.git"
51Changelog = "https://github.com/yourname/claude-hooks-lib/blob/main/CHANGELOG.md"
52
53[project.scripts]
54# Command-line entry points
55hook-validator = "claude_hooks_lib.hook_validator:main"
56
57[tool.hatch.build.targets.sdist]
58include = [
59    "/src",
60    "/tests",
61]
62
63[tool.hatch.build.targets.wheel]
64packages = ["src/claude_hooks_lib"]
關鍵設定說明
  1. build-system:使用 Hatch 作為建構後端(現代、快速)
  2. requires-python:指定最低 Python 版本
  3. dependencies:核心相依性保持為空(僅使用標準庫)
  4. optional-dependencies:將 PyYAML 設為可選
  5. project.scripts:定義命令列工具入口點

步驟 3:處理相依性

相依性分層策略:

 1[project]
 2# 核心相依性:僅標準庫
 3dependencies = []
 4
 5[project.optional-dependencies]
 6# 功能性相依性(用戶根據需求安裝)
 7yaml = ["PyYAML>=6.0"]
 8
 9# 開發相依性(僅開發者需要)
10dev = [
11    "pytest>=8.0",
12    "pytest-cov>=4.0",
13    "mypy>=1.0",
14    "ruff>=0.4",
15]
16
17# 測試相依性(CI/CD 需要)
18test = [
19    "pytest>=8.0",
20    "pytest-cov>=4.0",
21]
22
23# 文件相依性
24docs = [
25    "mkdocs>=1.5",
26    "mkdocs-material>=9.0",
27]
28
29# 完整安裝
30all = [
31    "claude-hooks-lib[yaml,dev,docs]",
32]
安裝方式範例
 1# 基本安裝(無可選相依性)
 2pip install claude-hooks-lib
 3
 4# 包含 YAML 支援
 5pip install "claude-hooks-lib[yaml]"
 6
 7# 開發者安裝
 8pip install -e ".[dev]"
 9
10# 完整安裝
11pip install "claude-hooks-lib[all]"

步驟 4:版本管理策略

方法 A:單一來源版本(推薦)

__init__.py 中定義版本:

 1# src/claude_hooks_lib/__init__.py
 2"""
 3Claude Hooks Library
 4
 5Shared utilities for building Claude Code hooks.
 6"""
 7
 8__version__ = "0.28.0"
 9__all__ = [
10    # Version
11    "__version__",
12    # git_utils
13    "run_git_command",
14    "get_current_branch",
15    "get_project_root",
16    "get_worktree_list",
17    "is_protected_branch",
18    "is_allowed_branch",
19    # hook_logging
20    "setup_hook_logging",
21    # hook_io
22    "read_hook_input",
23    "write_hook_output",
24    "create_pretooluse_output",
25    "create_posttooluse_output",
26    # config_loader
27    "load_config",
28    "load_agents_config",
29    "load_quality_rules",
30    "clear_config_cache",
31]
32
33from .git_utils import (
34    run_git_command,
35    get_current_branch,
36    get_project_root,
37    get_worktree_list,
38    is_protected_branch,
39    is_allowed_branch,
40)
41
42from .hook_logging import setup_hook_logging
43
44from .hook_io import (
45    read_hook_input,
46    write_hook_output,
47    create_pretooluse_output,
48    create_posttooluse_output,
49)
50
51from .config_loader import (
52    load_config,
53    load_agents_config,
54    load_quality_rules,
55    clear_config_cache,
56)

pyproject.toml 中使用動態版本:

1[project]
2name = "claude-hooks-lib"
3dynamic = ["version"]
4
5[tool.hatch.version]
6path = "src/claude_hooks_lib/__init__.py"
方法 B:使用 hatch-vcs(Git tag 版本)
 1[build-system]
 2requires = ["hatchling", "hatch-vcs"]
 3build-backend = "hatchling.build"
 4
 5[project]
 6dynamic = ["version"]
 7
 8[tool.hatch.version]
 9source = "vcs"
10
11[tool.hatch.build.hooks.vcs]
12version-file = "src/claude_hooks_lib/_version.py"
1# Create version tag
2git tag v0.28.0
3git push --tags

步驟 5:建立發布流程

.github/workflows/publish.yml

 1name: Publish to PyPI
 2
 3on:
 4  release:
 5    types: [published]
 6
 7permissions:
 8  contents: read
 9  id-token: write  # Required for trusted publishing
10
11jobs:
12  build:
13    runs-on: ubuntu-latest
14    steps:
15      - uses: actions/checkout@v4
16
17      - name: Set up Python
18        uses: actions/setup-python@v5
19        with:
20          python-version: "3.12"
21
22      - name: Install build tools
23        run: |
24          python -m pip install --upgrade pip
25          pip install build
26
27      - name: Build package
28        run: python -m build
29
30      - name: Upload artifacts
31        uses: actions/upload-artifact@v4
32        with:
33          name: dist
34          path: dist/
35
36  test:
37    runs-on: ubuntu-latest
38    strategy:
39      matrix:
40        python-version: ["3.10", "3.11", "3.12", "3.13"]
41    steps:
42      - uses: actions/checkout@v4
43
44      - name: Set up Python ${{ matrix.python-version }}
45        uses: actions/setup-python@v5
46        with:
47          python-version: ${{ matrix.python-version }}
48
49      - name: Install dependencies
50        run: |
51          python -m pip install --upgrade pip
52          pip install -e ".[dev,yaml]"
53
54      - name: Run tests
55        run: pytest tests/ -v --cov=src/claude_hooks_lib
56
57      - name: Type check
58        run: mypy src/claude_hooks_lib
59
60  publish-testpypi:
61    needs: [build, test]
62    runs-on: ubuntu-latest
63    environment: testpypi
64    steps:
65      - name: Download artifacts
66        uses: actions/download-artifact@v4
67        with:
68          name: dist
69          path: dist/
70
71      - name: Publish to TestPyPI
72        uses: pypa/gh-action-pypi-publish@release/v1
73        with:
74          repository-url: https://test.pypi.org/legacy/
75
76  publish-pypi:
77    needs: [publish-testpypi]
78    runs-on: ubuntu-latest
79    environment: pypi
80    steps:
81      - name: Download artifacts
82        uses: actions/download-artifact@v4
83        with:
84          name: dist
85          path: dist/
86
87      - name: Publish to PyPI
88        uses: pypa/gh-action-pypi-publish@release/v1

CI 測試工作流程(.github/workflows/test.yml)

 1name: Tests
 2
 3on:
 4  push:
 5    branches: [main]
 6  pull_request:
 7    branches: [main]
 8
 9jobs:
10  test:
11    runs-on: ${{ matrix.os }}
12    strategy:
13      fail-fast: false
14      matrix:
15        os: [ubuntu-latest, macos-latest, windows-latest]
16        python-version: ["3.10", "3.11", "3.12", "3.13"]
17
18    steps:
19      - uses: actions/checkout@v4
20
21      - name: Set up Python ${{ matrix.python-version }}
22        uses: actions/setup-python@v5
23        with:
24          python-version: ${{ matrix.python-version }}
25
26      - name: Install dependencies
27        run: |
28          python -m pip install --upgrade pip
29          pip install -e ".[dev,yaml]"
30
31      - name: Lint with ruff
32        run: ruff check src/ tests/
33
34      - name: Format check with ruff
35        run: ruff format --check src/ tests/
36
37      - name: Type check with mypy
38        run: mypy src/claude_hooks_lib
39
40      - name: Run tests with coverage
41        run: |
42          pytest tests/ -v --cov=src/claude_hooks_lib --cov-report=xml
43
44      - name: Upload coverage
45        uses: codecov/codecov-action@v4
46        with:
47          files: ./coverage.xml

完整程式碼

完整的 pyproject.toml

  1[build-system]
  2requires = ["hatchling"]
  3build-backend = "hatchling.build"
  4
  5[project]
  6name = "claude-hooks-lib"
  7dynamic = ["version"]
  8description = "Shared utilities for Claude Code hooks"
  9readme = "README.md"
 10license = "MIT"
 11requires-python = ">=3.10"
 12authors = [
 13    { name = "Your Name", email = "your.email@example.com" }
 14]
 15keywords = ["claude", "hooks", "utilities", "git", "automation"]
 16classifiers = [
 17    "Development Status :: 4 - Beta",
 18    "Intended Audience :: Developers",
 19    "License :: OSI Approved :: MIT License",
 20    "Operating System :: OS Independent",
 21    "Programming Language :: Python :: 3",
 22    "Programming Language :: Python :: 3.10",
 23    "Programming Language :: Python :: 3.11",
 24    "Programming Language :: Python :: 3.12",
 25    "Programming Language :: Python :: 3.13",
 26    "Typing :: Typed",
 27    "Topic :: Software Development :: Libraries :: Python Modules",
 28]
 29
 30dependencies = []
 31
 32[project.optional-dependencies]
 33yaml = ["PyYAML>=6.0"]
 34dev = [
 35    "pytest>=8.0",
 36    "pytest-cov>=4.0",
 37    "mypy>=1.0",
 38    "ruff>=0.4",
 39    "PyYAML>=6.0",
 40]
 41docs = [
 42    "mkdocs>=1.5",
 43    "mkdocs-material>=9.0",
 44]
 45all = ["claude-hooks-lib[yaml,dev,docs]"]
 46
 47[project.urls]
 48Homepage = "https://github.com/yourname/claude-hooks-lib"
 49Documentation = "https://github.com/yourname/claude-hooks-lib#readme"
 50Repository = "https://github.com/yourname/claude-hooks-lib.git"
 51Changelog = "https://github.com/yourname/claude-hooks-lib/blob/main/CHANGELOG.md"
 52
 53[project.scripts]
 54hook-validator = "claude_hooks_lib.hook_validator:main"
 55
 56# ===== Build Configuration =====
 57
 58[tool.hatch.version]
 59path = "src/claude_hooks_lib/__init__.py"
 60
 61[tool.hatch.build.targets.sdist]
 62include = ["/src", "/tests", "/README.md", "/LICENSE"]
 63
 64[tool.hatch.build.targets.wheel]
 65packages = ["src/claude_hooks_lib"]
 66
 67# ===== Tool Configuration =====
 68
 69[tool.ruff]
 70src = ["src"]
 71line-length = 88
 72target-version = "py310"
 73
 74[tool.ruff.lint]
 75select = [
 76    "E",      # pycodestyle errors
 77    "W",      # pycodestyle warnings
 78    "F",      # pyflakes
 79    "I",      # isort
 80    "B",      # flake8-bugbear
 81    "C4",     # flake8-comprehensions
 82    "UP",     # pyupgrade
 83]
 84ignore = ["E501"]  # Line too long (handled by formatter)
 85
 86[tool.ruff.lint.isort]
 87known-first-party = ["claude_hooks_lib"]
 88
 89[tool.mypy]
 90python_version = "3.10"
 91warn_return_any = true
 92warn_unused_ignores = true
 93disallow_untyped_defs = true
 94strict = true
 95
 96[[tool.mypy.overrides]]
 97module = "yaml"
 98ignore_missing_imports = true
 99
100[tool.pytest.ini_options]
101testpaths = ["tests"]
102python_files = "test_*.py"
103python_functions = "test_*"
104addopts = "-v --tb=short"
105
106[tool.coverage.run]
107source = ["src/claude_hooks_lib"]
108branch = true
109
110[tool.coverage.report]
111exclude_lines = [
112    "pragma: no cover",
113    "if TYPE_CHECKING:",
114    "raise NotImplementedError",
115]

使用範例

安裝套件

 1# From PyPI (after publishing)
 2pip install claude-hooks-lib
 3
 4# With YAML support
 5pip install "claude-hooks-lib[yaml]"
 6
 7# Development installation (from source)
 8git clone https://github.com/yourname/claude-hooks-lib
 9cd claude-hooks-lib
10pip install -e ".[dev]"
Python 使用範例
 1# Basic usage
 2from claude_hooks_lib import (
 3    get_current_branch,
 4    is_protected_branch,
 5    setup_hook_logging,
 6    read_hook_input,
 7    write_hook_output,
 8    create_pretooluse_output,
 9)
10
11# Initialize logging
12logger = setup_hook_logging("my-custom-hook")
13
14# Check branch protection
15branch = get_current_branch()
16if branch and is_protected_branch(branch):
17    logger.warning(f"Operating on protected branch: {branch}")
18
19# Process hook input
20input_data = read_hook_input()
21tool_name = input_data.get("tool_name", "")
22
23# Generate output
24output = create_pretooluse_output(
25    decision="allow",
26    reason="All checks passed"
27)
28write_hook_output(output)

命令列工具使用

 1# Validate a single hook
 2hook-validator .claude/hooks/my-hook.py
 3
 4# Validate all hooks
 5hook-validator --all
 6
 7# Output as JSON
 8hook-validator --all --json
 9
10# Strict mode (warnings as errors)
11hook-validator --all --strict

設計權衡

面向內部目錄獨立套件
重用性僅限單專案跨專案共用
版本管理與專案綁定獨立語意化版本
維護成本低(無發布流程)中(需維護 CI/CD)
相依管理隱式(需手動追蹤)顯式(pyproject.toml)
安裝方式複製程式碼或 sys.pathpip install
測試隔離可能測試到本地版本強制測試安裝版本
API 穩定性無保證版本號約束

專案結構比較

Layout適用場景優點缺點
Flat Layout簡單應用、腳本簡單直覺測試可能導入錯誤版本
Src Layout函式庫、套件發布測試隔離、明確邊界額外一層目錄

建構工具比較

工具特點推薦場景
setuptools成熟穩定、生態最大需要相容舊專案
Hatch現代、快速、功能完整新專案首選
Poetry依賴鎖定、虛擬環境管理需要嚴格依賴控制
Flit極簡、僅純 Python簡單函式庫

什麼時候該打包成套件?

適合打包

  • 多個專案需要使用相同程式碼
  • 程式碼相對穩定,API 不常變動
  • 需要版本控制和變更追蹤
  • 希望其他人能 pip install 使用
  • 需要明確的相依性管理

不建議打包

  • 僅單一專案使用
  • 程式碼還在快速迭代
  • 與專案緊密耦合(如特定的配置路徑)
  • 維護成本超過重用收益

決策流程圖

1程式碼需要跨專案使用?
2├── 否 → 保持內部目錄
3└── 是 → API 穩定嗎?
4         ├── 否 → 等待穩定後再打包
5         └── 是 → 有維護能力嗎?
6                  ├── 否 → 考慮 monorepo
7                  └── 是 → 打包發布

練習

基礎練習:建立最小的 pyproject.toml

目標:為一個簡單的工具函式庫建立 pyproject.toml

1# src/my_utils/__init__.py
2"""Simple utilities."""
3
4__version__ = "0.1.0"
5
6def greet(name: str) -> str:
7    """Return a greeting message."""
8    return f"Hello, {name}!"

要求

  1. 使用 hatchling 作為建構後端
  2. 設定專案名稱、版本、描述
  3. 指定 Python 版本要求(>=3.10)
參考答案
 1[build-system]
 2requires = ["hatchling"]
 3build-backend = "hatchling.build"
 4
 5[project]
 6name = "my-utils"
 7version = "0.1.0"
 8description = "Simple utility functions"
 9requires-python = ">=3.10"
10
11[tool.hatch.build.targets.wheel]
12packages = ["src/my_utils"]

進階練習:新增 optional dependencies

目標:擴展上面的套件,加入可選的功能

要求

  1. 新增一個需要 requests 的函式
  2. requests 設為可選相依性
  3. 加入開發相依性(pytest, ruff)
參考答案
 1[build-system]
 2requires = ["hatchling"]
 3build-backend = "hatchling.build"
 4
 5[project]
 6name = "my-utils"
 7version = "0.1.0"
 8description = "Simple utility functions"
 9requires-python = ">=3.10"
10dependencies = []
11
12[project.optional-dependencies]
13http = ["requests>=2.28"]
14dev = [
15    "pytest>=8.0",
16    "ruff>=0.4",
17    "my-utils[http]",  # Include http for testing
18]
19
20[tool.hatch.build.targets.wheel]
21packages = ["src/my_utils"]
 1# src/my_utils/http.py
 2"""HTTP utilities (requires requests)."""
 3
 4try:
 5    import requests
 6    HAS_REQUESTS = True
 7except ImportError:
 8    HAS_REQUESTS = False
 9
10def fetch_json(url: str) -> dict:
11    """Fetch JSON from URL."""
12    if not HAS_REQUESTS:
13        raise ImportError(
14            "requests is required for this feature. "
15            "Install with: pip install my-utils[http]"
16        )
17    response = requests.get(url)
18    response.raise_for_status()
19    return response.json()

挑戰題:設定 GitHub Actions 自動發布到 PyPI

目標:建立完整的 CI/CD 流程

要求

  1. Pull Request 時執行測試
  2. 建立 Release 時自動發布到 PyPI
  3. 使用 Trusted Publishing(不需要 API Token)
  4. 多版本 Python 測試矩陣

提示

  • 需要在 PyPI 設定 Trusted Publisher
  • 使用 pypa/gh-action-pypi-publish@release/v1
  • 設定 id-token: write 權限
參考答案
  1. 先在 PyPI 設定 Trusted Publisher:

    • 前往 pypi.org
    • 新增 GitHub publisher
    • 填入 repository owner、name、workflow 路徑
  2. 建立工作流程檔案:

 1# .github/workflows/ci.yml
 2name: CI
 3
 4on:
 5  push:
 6    branches: [main]
 7  pull_request:
 8    branches: [main]
 9
10jobs:
11  test:
12    runs-on: ubuntu-latest
13    strategy:
14      matrix:
15        python-version: ["3.10", "3.11", "3.12", "3.13"]
16    steps:
17      - uses: actions/checkout@v4
18      - uses: actions/setup-python@v5
19        with:
20          python-version: ${{ matrix.python-version }}
21      - run: pip install -e ".[dev]"
22      - run: ruff check src/ tests/
23      - run: pytest tests/ -v
 1# .github/workflows/publish.yml
 2name: Publish
 3
 4on:
 5  release:
 6    types: [published]
 7
 8permissions:
 9  contents: read
10  id-token: write
11
12jobs:
13  publish:
14    runs-on: ubuntu-latest
15    environment: pypi
16    steps:
17      - uses: actions/checkout@v4
18      - uses: actions/setup-python@v5
19        with:
20          python-version: "3.12"
21      - run: pip install build
22      - run: python -m build
23      - uses: pypa/gh-action-pypi-publish@release/v1

延伸閱讀


返回:模組六:打包與發布